diff --git a/.cfnlintrc b/.cfnlintrc
index 93cea0a786..3d7f8e7ccd 100644
--- a/.cfnlintrc
+++ b/.cfnlintrc
@@ -3,5 +3,7 @@ templates:
- tests/cloudformation/checks/resource/aws/**/*.yaml
ignore_templates:
- tests/cloudformation/checks/resource/aws/unused/*
+ # https://github.com/aws-cloudformation/cfn-python-lint/issues/1577
+ - tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/*
ignore_checks:
- W
diff --git a/.github/exclude-patterns.txt b/.github/exclude-patterns.txt
index c2752015a7..a08cfb11c5 100644
--- a/.github/exclude-patterns.txt
+++ b/.github/exclude-patterns.txt
@@ -3,7 +3,9 @@ tests/serverless/checks/aws/example_AWSCredentials/AWSCredentials-FAILED-provide
tests/serverless/checks/aws/example_AWSCredentials/AWSCredentials-FAILED-provider_level/serverless.yml
tests/serverless/checks/aws/example_AWSCredentials/AWSCredentials-FAILED-func_level/serverless.yml
tests/serverless/checks/aws/example_AWSCredentials/AWSCredentials-FAILED-provider_level/serverless.yml
+tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-FAILED.yaml
tests/terraform/checks/resource/aws/test_LambdaEnvironmentCredentials.py
+tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/LambdaEnvironmentCredentials.yaml
tests/terraform/runner/resources/example/example.tf
.*Pipfile.lock
tests/terraform/checks/provider/aws/test_credentials.py
@@ -13,3 +15,4 @@ tests/terraform/runner/resources/plan/tfplan.json
tests/terraform/parser/resources/plan_tags/tfplan.json
.*Scans.md
docs/5.Contribution/New-Provider.md
+tests/cloudformation/checks/resource/aws/example_AWSCredentials/EC2Credentials-FAILED.yaml
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000000..7af54c8d21
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,70 @@
+# Configuration for probot-stale - https://github.com/probot/stale
+
+# Number of days of inactivity before an Issue or Pull Request becomes stale
+daysUntilStale: 180
+
+# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
+# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
+daysUntilClose: 14
+
+# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
+onlyLabels: []
+
+# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
+exemptLabels:
+ - pinned
+ - security
+ - nostale
+
+# Set to true to ignore issues in a project (defaults to false)
+exemptProjects: false
+
+# Set to true to ignore issues in a milestone (defaults to false)
+exemptMilestones: false
+
+# Set to true to ignore issues with an assignee (defaults to false)
+exemptAssignees: false
+
+# Label to use when marking as stale
+staleLabel: stale
+
+# Comment to post when marking as stale. Set to `false` to disable
+markComment: >
+ Thanks for contributing to Checkov!
+ We've automatically marked this issue as stale to keep our issues list tidy,
+ because it has not had any activity for 6 months.
+ It will be closed in 14 days if no further activity occurs.
+ Commenting on this issue will remove the stale tag.
+ If you want to talk through the issue or help us understand the priority and context,
+ feel free to add a comment or join us in the Checkov slack channel at https://slack.bridgecrew.io
+
+ Thanks!
+
+# Comment to post when removing the stale label.
+# unmarkComment: >
+# Your comment here.
+
+# Comment to post when closing a stale Issue or Pull Request.
+closeComment: >
+ Closing issue due to inactivity.
+ If you feel this is in error, please re-open, or reach out to the community via slack:
+ https://slack.bridgecrew.io
+ Thanks!
+
+# Limit the number of actions per hour, from 1-30. Default is 30
+limitPerRun: 30
+
+# Limit to only `issues` or `pulls`
+# only: issues
+
+# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
+# pulls:
+# daysUntilStale: 30
+# markComment: >
+# This pull request has been automatically marked as stale because it has not had
+# recent activity. It will be closed if no further activity occurs. Thank you
+# for your contributions.
+
+# issues:
+# exemptLabels:
+# - confirmed
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 057ec1637c..faf86d173d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,124 +1,162 @@
name: build
-on:
+on:
+ workflow_dispatch:
push:
branches:
- - master
+ - master
+ paths-ignore:
+ - 'docs/**'
+ - 'INTHEWILD.md'
+ - 'README.md'
+
jobs:
integration-tests:
+ strategy:
+ fail-fast: true
+ matrix:
+ python: [3.7, 3.8, 3.9]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
- python-version: 3.7
+ python-version: ${{ matrix.python }}
- uses: dschep/install-pipenv-action@v1
+ - uses: actions/setup-node@v2
+ - uses: azure/setup-helm@v1
- name: Build & install checkov package
run: |
+ pipenv --python ${{ matrix.python }}
+ pipenv run pip install --upgrade pip==21.1.1
pipenv run pip install pytest
pipenv run python setup.py sdist bdist_wheel
- pipenv run pip install --upgrade pip==20.2
- pipenv run pip install dist/checkov-*
+ pipenv run pip install dist/checkov-*.whl
- name: Clone Terragoat - vulnerable terraform
- uses: actions/checkout@master
- with:
- repository: bridgecrewio/terragoat # clone https://github.com/bridgecrewio/terragoat/
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'terragoat'
+ run: git clone https://github.com/bridgecrewio/terragoat
- name: Clone Cfngoat - vulnerable cloudformation
- uses: actions/checkout@master
- with:
- repository: bridgecrewio/cfngoat # clone https://github.com/bridgecrewio/cfngoat/
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'cfngoat'
+ run: git clone https://github.com/bridgecrewio/cfngoat
- name: Clone Kubernetes-goat - vulnerable kubernetes
- uses: actions/checkout@master
- with:
- repository: madhuakula/kubernetes-goat # clone https://github.com/madhuakula/kubernetes-goat
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'kubernetes-goat'
+ run: git clone https://github.com/madhuakula/kubernetes-goat
- name: Create checkov reports
run: |
- pipenv run checkov -s -d terragoat/terraform/ -o json > checkov_report_terragoat.json
- pipenv run checkov -s -d cfngoat/ -o json > checkov_report_cfngoat.json
- pipenv run checkov -s -d kubernetes-goat/ -o json > checkov_report_kubernetes-goat.json
-
+ sleep $((RANDOM % 11))
+ ./integration_tests/prepare_data.sh ${{ matrix.python }}
+ env:
+ BC_KEY: ${{ secrets.BC_API_KEY }}
- name: Run integration tests
run: |
pipenv run pytest integration_tests
bump-version:
needs: [integration-tests]
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v2
+ - name: Import GPG key
+ id: import_gpg
+ uses: crazy-max/ghaction-import-gpg@v3
+ with:
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.PASSPHRASE }}
- name: Set up Python 3.7
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v2
with:
python-version: 3.7
- uses: dschep/install-pipenv-action@v1
- name: Install dependencies
run: |
+ pipenv --python 3.7
pipenv install --dev
+ pipenv run pip install pytest
- name: Test with pytest
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pipenv run python -m coverage run -m pytest tests
pipenv run python -m coverage report
pipenv run python -m coverage html
pipenv run python -m coverage_badge -o coverage.svg -f
- - name: update docs and bump version
+ - name: version
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
run: |
- git config --local user.email "action@github.com"
- git config --local user.name "GitHub Action"
+ git config --global user.name 'schosterbarak'
+ git config --global user.email 'schosterbarak@users.noreply.github.com'
+
git fetch --tags
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
echo "latest tag: $latest_tag"
new_tag=$(echo $latest_tag | awk -F. -v a="$1" -v b="$2" -v c="$3" '{printf("%d.%d.%d", $1+a, $2+b , $3+1)}')
+
echo "new tag: $new_tag"
## update docs
export PYTHONPATH='.'
- export scansdoc=docs/3.Scans/resource-scans.md
- echo "---" > $scansdoc
- echo "layout: default" >> $scansdoc
- echo "title: Resource scans" >> $scansdoc
- echo "nav_order: 1" >> $scansdoc
- echo "---" >> $scansdoc
- echo "" >> $scansdoc
- echo "# Resource scans (auto generated)" >> $scansdoc
- echo "" >> $scansdoc
git pull
- pipenv run python ./checkov/common/util/docs_generator.py >> $scansdoc
- git commit --reuse-message=HEAD@{1} $scansdoc || echo "No changes to commit"
+ for i in cloudformation terraform kubernetes serverless arm dockerfile secrets all
+ do
+ export scansdoc="docs/5.Policy Index/$i.md"
+ echo "---" > "$scansdoc"
+ echo "layout: default" >> "$scansdoc"
+ echo "title: $i resource scans" >> "$scansdoc"
+ echo "nav_order: 1" >> "$scansdoc"
+ echo "---" >> "$scansdoc"
+ echo "" >> "$scansdoc"
+ echo "# $i resource scans (auto generated)" >> "$scansdoc"
+ echo "" >> "$scansdoc"
+ pipenv run python checkov/main.py --list --framework "$i" >> "$scansdoc"
+ git commit --reuse-message=HEAD@{1} "$scansdoc" || echo "No changes to commit"
+
+ done
+
+ #add cloudformation scans to serverless
+ export scansdoc="docs/5.Policy Index/serverless.md"
+ pipenv run python checkov/main.py --list --framework cloudformation >> "$scansdoc"
+ git commit --reuse-message=HEAD@{1} "$scansdoc" || echo "No changes to commit"
## update python version
echo "version = '$new_tag'" > 'checkov/version.py'
echo "checkov==$new_tag" > 'kubernetes/requirements.txt'
+ # git commit --reuse-message=HEAD@{1} checkov/version.py kubernetes/requirements.txt coverage.svg || echo "No changes to commit"
git commit --reuse-message=HEAD@{1} checkov/version.py kubernetes/requirements.txt coverage.svg || echo "No changes to commit"
git push origin
git tag $new_tag
- git push origin $new_tag
-
+ git push --tags
+ RELEASE_NOTE=$(git log -1 --pretty=%B)
+ echo "::set-output name=version::$new_tag"
+ echo "::set-output name=notes::$RELEASE_NOTE"
+ id: version
+ - name: release
+ uses: actions/create-release@v1
+ with:
+ draft: false
+ prerelease: false
+ release_name: ${{ steps.version.outputs.version }}
+ tag_name: ${{ steps.version.outputs.version }}
+ body: ${{ steps.version.outputs.notes }}
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ outputs:
+ version: ${{ steps.version.outputs.version }}
publish-package:
needs: bump-version
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v2
+ - name: Import GPG key
+ id: import_gpg
+ uses: crazy-max/ghaction-import-gpg@v3
+ with:
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.PASSPHRASE }}
- name: Set up Python 3.7
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v2
with:
python-version: 3.7
- uses: dschep/install-pipenv-action@v1
- name: Install dependencies
run: |
+ pipenv --python 3.7
pipenv install --dev
- name: create python package
run: |
@@ -135,53 +173,82 @@ jobs:
- name: sleep and wait for package to refresh
run: |
sleep 2m
- update-bridgecrew-projects:
- needs: publish-package
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@master
- - name: update checkov release
- run: |
- curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/bridgecrew-py/dispatches --data '{"event_type": "build"}'
- curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/checkov-action/dispatches --data '{"event_type": "build"}'
- curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/checkovdb/dispatches --data '{"event_type": "build"}'
- curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/relations-graph/dispatches --data '{"event_type": "checkov-upgrade"}'
+ outputs:
+ version: ${{ needs.bump-version.outputs.version }}
publish-checkov-dockerhub:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
needs: publish-package
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v2
- name: Get release version
id: get_version
run: |
- checkov_version=$(curl -sL https://api.github.com/repos/bridgecrewio/checkov/tags | jq -r '.[0]["name"]' )
+ checkov_version=${{ needs.publish-package.outputs.version }}
+ checkov_major_version=$(echo "${checkov_version}" | head -c1)
echo ::set-env name=RELEASE_VERSION::$(echo $checkov_version)
+ echo ::set-env name=RELEASE_MAJOR_VERSION::$(echo $checkov_major_version)
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: Publish to Registry
- uses: elgohr/Publish-Docker-Github-Action@master
+ uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: bridgecrew/checkov
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- tags: "latest,${{ env.RELEASE_VERSION }}"
+ buildoptions: "--no-cache"
+ tags: "latest,${{ env.RELEASE_VERSION }},${{ env.RELEASE_MAJOR_VERSION }}"
publish-checkov-k8s-dockerhub:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
needs: publish-package
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v2
- name: update checkov-k8s version
run: |
- checkov_version=$(curl -sL https://api.github.com/repos/bridgecrewio/checkov/tags | jq -r '.[0]["name"]' )
+ checkov_version=${{ needs.publish-package.outputs.version }}
+ checkov_major_version=$(echo "${checkov_version}" | head -c1)
echo ::set-env name=RELEASE_VERSION::$(echo $checkov_version)
+ echo ::set-env name=RELEASE_MAJOR_VERSION::$(echo $checkov_major_version)
echo $RELEASE_VERSION
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: Publish to Registry
- uses: elgohr/Publish-Docker-Github-Action@master
+ uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: bridgecrew/checkov-k8s
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- tags: "latest,${{ env.RELEASE_VERSION }}"
+ tags: "latest,${{ env.RELEASE_VERSION }},${{ env.RELEASE_MAJOR_VERSION }}"
dockerfile: kubernetes/Dockerfile
+ buildoptions: "--no-cache"
+ publish-to-brew:
+ runs-on: macos-latest
+ needs: publish-package
+ steps:
+ - name: Get release version
+ id: get_version
+ run: |
+ checkov_version=${{ needs.publish-package.outputs.version }}
+ micro_version=$(echo $checkov_version | awk -F. -v a="$1" -v b="$2" -v c="$3" '{printf("%d", $3)}')
+ echo $micro_version
+ ZERO_IF_SHOULD_RELEASE=$(($micro_version%15))
+ echo $ZERO_IF_SHOULD_RELEASE
+ echo ::set-env name=ZERO_IF_SHOULD_RELEASE::$(echo $ZERO_IF_SHOULD_RELEASE)
+ env:
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
+ - name: release homebrew
+ uses: dawidd6/action-homebrew-bump-formula@v3.8.0
+ if: env.ZERO_IF_SHOULD_RELEASE == 0
+ with:
+ formula: checkov
+ token: ${{ secrets.PAT_TOKEN }}
+ update-bridgecrew-projects:
+ needs: publish-checkov-dockerhub
+ runs-on: [self-hosted, public, linux, x64]
+ steps:
+ - uses: actions/checkout@v2
+ - name: update checkov release
+ run: |
+ curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/bridgecrew-py/dispatches --data '{"event_type": "build"}'
+ curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/checkov-action/dispatches --data '{"event_type": "build"}'
+ curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/checkovdb/dispatches --data '{"event_type": "build"}'
+ curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.PAT_TOKEN}}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/bridgecrewio/platform/dispatches --data '{"event_type": "checkov-upgrade"}'
diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml
deleted file mode 100644
index 8e3b37c1ab..0000000000
--- a/.github/workflows/homebrew.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-name: homebrew
-
-on:
- push:
- tags: '*'
-
-jobs:
- homebrew:
- name: Bump Homebrew formula
- runs-on: ubuntu-latest
- steps:
- - uses: mislav/bump-homebrew-formula-action@v1
- with:
- # A PR will be sent to github.com/Homebrew/homebrew-core to update this formula:
- formula-name: checkov
- env:
- COMMITTER_TOKEN: ${{ secrets.PAT_TOKEN }}
diff --git a/.github/workflows/pipenv-update.yml b/.github/workflows/pipenv-update.yml
new file mode 100644
index 0000000000..52146959d6
--- /dev/null
+++ b/.github/workflows/pipenv-update.yml
@@ -0,0 +1,43 @@
+name: pipenv-update
+on:
+ schedule:
+ - cron: '8 22 * * 1'
+ workflow_dispatch:
+
+jobs:
+ pipenv-update:
+ runs-on: [self-hosted, public, linux, x64]
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.head_ref }}
+ - name: Import GPG key
+ id: import_gpg
+ uses: crazy-max/ghaction-import-gpg@v3
+ with:
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.PASSPHRASE }}
+ - uses: actions/setup-python@v2
+ with:
+ python-version: 3.7
+ - uses: dschep/install-pipenv-action@v1
+ - run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ pipenv update
+ git add -u
+ git commit -m "update pipenv packages"
+ env:
+ GITHUB_TOKEN: ${{ github.PAT_TOKEN }}
+ - name: Create Pull Request
+ id: cpr
+ uses: peter-evans/create-pull-request@v3
+ with:
+ token: ${{ secrets.PAT_TOKEN }}
+ title: '[AUTO-PR] Update pipenv packages'
+ body: |
+ bump pipenv packages
+ - Auto-generated by [pipenv-update github action](https://github.com/bridgecrewio/checkov/blob/master/.github/workflows/pipenv-update.yml)
+ labels: automated pr
+ branch: pipenvfix
+ branch-suffix: timestamp
diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml
index 083799a518..20e9969a14 100644
--- a/.github/workflows/pr-test.yml
+++ b/.github/workflows/pr-test.yml
@@ -4,7 +4,7 @@ on: pull_request
jobs:
cfn-lint:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
@@ -12,22 +12,27 @@ jobs:
python-version: 3.7
- name: Install cfn-lint
run: |
- pip install cfn-lint==0.41.0
+ pip install cfn-lint==0.51.0
- name: Lint Cloudformation templates
run: |
cfn-lint tests/cloudformation/checks/resource/aws/**/* -i W
unit-tests:
- runs-on: ubuntu-latest
+ strategy:
+ fail-fast: true
+ matrix:
+ python: [3.7, 3.8, 3.9]
+ runs-on: [self-hosted, public, linux, x64]
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.7
+ - name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2
with:
- python-version: 3.7
+ python-version: ${{ matrix.python }}
- uses: dschep/install-pipenv-action@v1
- name: Install dependencies
run: |
+ pipenv --python ${{ matrix.python }}
pipenv install --dev
- name: Unit tests
env:
@@ -38,49 +43,39 @@ jobs:
pipenv run python -m coverage html
integration-tests:
+ strategy:
+ fail-fast: true
+ matrix:
+ python: [3.7, 3.8, 3.9]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
- python-version: 3.7
+ python-version: ${{ matrix.python }}
+ - uses: actions/setup-node@v2
+ - uses: azure/setup-helm@v1
- uses: dschep/install-pipenv-action@v1
- name: Build & install checkov package
run: |
+ pipenv --python ${{ matrix.python }}
+ pipenv run pip install --upgrade pip==21.1.1
pipenv run pip install pytest
pipenv run python setup.py sdist bdist_wheel
- pipenv run pip install --upgrade pip==20.2
- pipenv run pip install dist/checkov-*
+ pipenv run pip install dist/checkov-*.whl
- name: Clone Terragoat - vulnerable terraform
- uses: actions/checkout@master
- with:
- repository: bridgecrewio/terragoat # clone https://github.com/bridgecrewio/terragoat/
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'terragoat'
+ run: git clone https://github.com/bridgecrewio/terragoat
- name: Clone Cfngoat - vulnerable cloudformation
- uses: actions/checkout@master
- with:
- repository: bridgecrewio/cfngoat # clone https://github.com/bridgecrewio/cfngoat/
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'cfngoat'
+ run: git clone https://github.com/bridgecrewio/cfngoat
- name: Clone Kubernetes-goat - vulnerable kubernetes
- uses: actions/checkout@master
- with:
- repository: madhuakula/kubernetes-goat # clone https://github.com/madhuakula/kubernetes-goat
- token: ${{ secrets.GITHUB_TOKEN }}
- clean: false
- path: 'kubernetes-goat'
+ run: git clone https://github.com/madhuakula/kubernetes-goat
- name: Create checkov reports
+ env:
+ LOG_LEVEL: INFO
+ BC_KEY: ${{ secrets.BC_API_KEY }}
run: |
- pipenv run checkov -s -d terragoat/terraform/ -o json > checkov_report_terragoat.json
- pipenv run checkov -s -d cfngoat/ -o json > checkov_report_cfngoat.json
- pipenv run checkov -s -d kubernetes-goat/ -o json > checkov_report_kubernetes-goat.json
-
+ sleep $((RANDOM % 11))
+ ./integration_tests/prepare_data.sh 3.8 # Just making sure the API key tests don't run on PRs
- name: Run integration tests
run: |
- pipenv run pytest integration_tests
-
-
-
+ pipenv run pytest integration_tests -k 'not api_key'
diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml
index 9e8304eb69..8ec847c0d8 100644
--- a/.github/workflows/security.yaml
+++ b/.github/workflows/security.yaml
@@ -9,7 +9,7 @@ on:
- master
jobs:
bandit:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
steps:
- uses: actions/checkout@v1
- name: security test
@@ -17,7 +17,7 @@ jobs:
with:
path: 'checkov'
detect-secrets:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, public, linux, x64]
steps:
- uses: actions/checkout@v1
- name: detect secrets
diff --git a/.gitignore b/.gitignore
index cbb310861c..5be0a86dbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@ __pycache__/
# Terraform
*.tfstate*
*.terraform*
-*.tfvars
*.tfbackend
# git
@@ -58,6 +57,7 @@ nosetests.xml
coverage.xml
*,cover
.hypothesis/
+.external_modules/
# Translations
*.mo
@@ -159,4 +159,7 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
-fabric.properties
\ No newline at end of file
+fabric.properties
+
+# Checkov baseline file
+.checkov.baseline
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 02980e2810..c2c292b42a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,31 +41,57 @@ and in aggregate solve a broader issue.
### Test where it matters
-1. Unit: Unit tests are stored in checkov/tests/.
+1. Unit: Unit tests, including check tests, are stored in checkov/tests/.
2. E2E: End-to-end tests will help us establish if the feature is in high readiness. They are not required for simple
or straight forward features but will help us in evaluating the PR.
+#### Tests for new checks
+
+When you add a new check, please write a test for it. While there are many different ways that tests have been written in the past, we have standardized on [this](https://github.com/bridgecrewio/checkov/blob/master/tests/terraform/checks/resource/aws/test_IAMAdminPolicyDocument.py) format. The key points are:
+
+* The test defines templates as strings (in this case, in separate files, but hardcoding a string is also acceptable) and parses them using the runner. The configuration should NOT be hard-coded as an object, as in [this](https://github.com/bridgecrewio/checkov/blob/master/tests/terraform/checks/resource/aws/test_ALBListenerHTTPS.py) example. The reason is that parsers sometimes produce unexpected object structures, so it is quite common that hardcoding the object allows the test to pass but causes the check to be incorrect in practice.
+* The test explicitly lists which resources should pass and which should fail. Merely checking the count of passes and failures is not enough. While rare, in the past this has resulted in tests that pass but checks that are incorrect in practice.
+
+#### Running tests
+
Continuous integration will run these tests either as pre-submits on PRs and post-submits against master branch.
Results will appear under [actions](https://github.com/bridgecrewio/checkov/actions).
To run tests locally use the following commands (install dev dependencies, run tests and compute tests coverage):
+If you are using conda, create a new environment with Python 3.7.10 version:
+```sh
+conda create -n python37 --m python=3.7.10
+conda activate python37
+```
+Then, we need pipenv installation and run the tests and coverage modules
```sh
+pip install pipenv
pipenv install --dev
pipenv run python -m coverage run -m pytest tests
```
### Build package locally
-To build package locally run the following on Checkov root folder:
+Change the version number on the file with your version : `/checkov/version.py`
+To build package locally run the following on `` root folder:
+
```sh
pipenv run python setup.py sdist bdist_wheel
```
- This will create a `*.whl` package under a new folder named `dist`
-To install package from local directory run:
+To install package from local directory, update the release version value and run the installation:
```sh
+RELEASE_VERSION='xxx'
pip install dist/checkov-${RELEASE_VERSION}-py3-none-any.whl
```
+### Test the package
+First verify you have the right version installed:
+```sh
+checkov --version
+```
+Then, optionally, you can run on a terraform file/directory with your success and failure test scenarios.
+
### Documentation is awesome
Contributing to the documentation is not mandatory but it will ensure people are aware of your important contribution.
diff --git a/Dockerfile b/Dockerfile
index 6734eb4845..e39ad6592a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,9 +1,13 @@
-FROM python:3.8-slim-buster
-
-RUN apt-get update && apt install -y \
- git \
- && rm -rf /var/lib/apt/lists/*
-
-RUN pip install -U checkov
-
-ENTRYPOINT ["checkov"]
+FROM python:3.7-alpine
+
+RUN apk update && apk add --no-cache git util-linux bash openssl
+
+RUN pip install --no-cache-dir -U checkov
+RUN wget -q -O get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3; chmod 700 get_helm.sh; VERIFY_CHECKSUM=true ./get_helm.sh; rm ./get_helm.sh
+
+COPY ./github_action_resources/entrypoint.sh /entrypoint.sh
+COPY ./github_action_resources/checkov-problem-matcher.json /usr/local/lib/checkov-problem-matcher.json
+COPY ./github_action_resources/checkov-problem-matcher-softfail.json /usr/local/lib/checkov-problem-matcher-softfail.json
+
+# Code file to execute when the docker container starts up (`entrypoint.sh`)
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/INTHEWILD.md b/INTHEWILD.md
new file mode 100644
index 0000000000..c7c59dcef9
--- /dev/null
+++ b/INTHEWILD.md
@@ -0,0 +1,20 @@
+# Who uses checkov?
+
+As the checkov community grows, we'd like to keep track of who is using the OSS tool.
+Please send a PR with your company name and @githubhandle.
+
+## Currently, officially using Checkov:
+
+1. [Bridgecrew](https://bridgecrew.io/) [[@schosterbarak](https://github.com/schosterbarak)]
+1. [Nationwide Building Society](https://www.nationwide.co.uk/) [[@njgibbon](https://github.com/njgibbon)]
+1. [globaldatanet](https://globaldatanet.com/) [[@gruebel](https://github.com/gruebel)]
+1. [Steamhaus](https://www.steamhaus.co.uk/) [[@bilco105](https://github.com/bilco105)]
+1. [Jim Smith](https://www.linkedin.com/in/mr-j-smith/) [[@jimsmith](https://github.com/jimsmith)]
+1. [Chaser Systems](https://chasersystems.com/) [[@new23d](https://github.com/new23d)]
+1. [Palo Alto Networks](https://www.paloaltonetworks.com/) [[@jameswoolfenden](https://github.com/JamesWoolfenden)]
+1. [Appvia](https://www.appvia.io/) [[@abdelhegazi](https://github.com/abdelhegazi)]
+1. [Square](https://squareup.com/) [[@ac-square](https://github.com/ac-square), [@santoshankr](https://github.com/santoshankr)]
+1. [Madhu Akula](https://madhuakula.com/) [[@madhuakula](https://github.com/madhuakula)]
+1. [Royal Vopak N.V.](https://vopak.com/) [[@xmariopereira](https://github.com/xmariopereira)]
+1. [Punk Security (UK)](https://punksecurity.co.uk/) [[@punksecurity](https://github.com/punk-security)]
+
diff --git a/Manifest.in b/Manifest.in
new file mode 100644
index 0000000000..7d99432ed0
--- /dev/null
+++ b/Manifest.in
@@ -0,0 +1 @@
+recursive-include checkov/terraform/checks/graph_checks *.yaml *.yml
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
index 4de656a882..438611179a 100644
--- a/Pipfile
+++ b/Pipfile
@@ -10,31 +10,40 @@ verify_ssl = true
pytest = "*"
coverage ="*"
coverage-badge = "*"
-pipenv-setup = "*"
GitPython = "*"
bandit = "*"
urllib3-mock = "*"
-
+jsonschema = "*"
[packages]
#
# REMINDER: Update "install_requires" deps on setup.py when changing
#
-bc-python-hcl2 = "*"
+bc-python-hcl2 = ">=0.3.18"
deep_merge = "*"
tabulate = "*"
colorama="*"
termcolor="*"
-junit-xml ="*"
+junit-xml = ">=1.9"
dpath = ">=1.5.0,<2"
-pyyaml = ">=5.3.1"
-boto3 = "*"
+pyyaml = ">=5.4.1"
+boto3 = "==1.17.*"
GitPython = "*"
-six = "==1.15.0"
+six = "==1.16.0"
jmespath = "*"
tqdm = "*"
update_checker = "*"
semantic_version = "*"
packaging = "*"
+cloudsplaining = ">=0.4.3"
+networkx = "*"
+dockerfile-parse ="*"
+docker = "*"
+configargparse = "*"
+detect_secrets = "*"
+policyuniverse = "*"
+typing-extensions = "*"
+importlib-metadata = ">=0.12"
+cfn-lint = "==0.53.*"
[requires]
python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
index d0a88cedf9..614344ddcc 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "d48bdf7be0ef344ff37e353cabbe325ea4f276dbdb48bf53dcc981bebae5ae5b"
+ "sha256": "f2d62b9a4a56a6781ec34b7e7d2f8a77a0cbb42813f18b8889d8facf2b6dcb93"
},
"pipfile-spec": 6,
"requires": {
@@ -16,43 +16,107 @@
]
},
"default": {
+ "attrs": {
+ "hashes": [
+ "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+ "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==21.2.0"
+ },
+ "aws-sam-translator": {
+ "hashes": [
+ "sha256:0ecadda9cf5ab2318f57f1253181a2151e4c53cd35d21717a923c075a5a65cb6",
+ "sha256:dc6b816bb5cfd9709299f9b263fc0cf5ae60aca4166d1c90413ece651f1556bb",
+ "sha256:ee7c7c5e44ec67202622ca877140545496527ffcc45da3beeda966f007443a88"
+ ],
+ "version": "==1.38.0"
+ },
"bc-python-hcl2": {
"hashes": [
- "sha256:17b02a2dda3a299ff981c0e3e3b29818d88dc3dd9ba7d552d7d8e048a3183a5b",
- "sha256:3ed75abbb45fcf941c97b599b15a237cb3f8fbc5d2b9c80dc0b7d1053c61fc1c"
+ "sha256:46f525676842d5c232752f9655f138665a1fa317b04e26efee3f82101dae204b",
+ "sha256:768c1a3c00db3cb9be7bd1e08c20813cf1e86d4a3cc47a76f7f97172d7b7d432"
],
"index": "pypi",
- "version": "==0.3.12"
+ "version": "==0.3.18"
+ },
+ "beautifulsoup4": {
+ "hashes": [
+ "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35",
+ "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25",
+ "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"
+ ],
+ "version": "==4.9.3"
},
"boto3": {
"hashes": [
- "sha256:71a0c22a040ac3a785f558628abfea8be86bb30b29003ebd124c51aba97dfeb8",
- "sha256:b1e91860fe2cae986f8e8238c12724f7fe4631a183e2c6f6b86714cc98645a6a"
+ "sha256:08b6dacbe7ebe57ae8acfb7106b2728d946ae1e0c3da270caee1deb79ccbd8af",
+ "sha256:8716465313c50ad9e5c2ac1767642ca0ddf7d1729c3d5c884d82880c1a15a310"
],
"index": "pypi",
- "version": "==1.16.53"
+ "version": "==1.17.112"
},
"botocore": {
"hashes": [
- "sha256:21677cda7b32492a1a74ac40e51d691a6623578d6700feb9976966d26a576414",
- "sha256:e93539781c43bd64291798a01cc6df2c0ff98e01ae7fe48286942ca8fa351680"
+ "sha256:6d51de0981a3ef19da9e6a3c73b5ab427e3c0c8b92200ebd38d087299683dd2b",
+ "sha256:d0b9b70b6eb5b65bb7162da2aaf04b6b086b15cc7ea322ddc3ef2f5e07944dcf"
],
- "version": "==1.19.53"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==1.20.112"
+ },
+ "cached-property": {
+ "hashes": [
+ "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130",
+ "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"
+ ],
+ "version": "==1.5.2"
},
"certifi": {
"hashes": [
- "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
- "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
+ "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
+ "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
],
- "version": "==2020.12.5"
+ "version": "==2021.5.30"
},
- "chardet": {
+ "cfn-lint": {
"hashes": [
- "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
- "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ "sha256:b7f5964842f7a44c5af9c61d64308dc4bcb718cf5de5428781d5564e9663463d",
+ "sha256:d17359e3ca9477eccaea700fac4bf028f5bc368a338c017adde5187f2691cab8"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==4.0.0"
+ "index": "pypi",
+ "version": "==0.53.0"
+ },
+ "charset-normalizer": {
+ "hashes": [
+ "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
+ "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
+ ],
+ "markers": "python_version >= '3'",
+ "version": "==2.0.4"
+ },
+ "click": {
+ "hashes": [
+ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
+ "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==8.0.1"
+ },
+ "click-option-group": {
+ "hashes": [
+ "sha256:9653a2297357335d7325a1827e71ac1245d91c97d959346a7decabd4a52d5354",
+ "sha256:a6e924f3c46b657feb5b72679f7e930f8e5b224b766ab35c91ae4019b4e0615e"
+ ],
+ "markers": "python_version >= '3.6' and python_version < '4'",
+ "version": "==0.5.3"
+ },
+ "cloudsplaining": {
+ "hashes": [
+ "sha256:95fc4634865df002a45c65ab3fa8ac5c324e7c5504468e3255d50d6e9eab1a0f",
+ "sha256:c5ad2248e4c40aa5af9cee9f67772545e803d81f14cb43e86d3d161237de4503"
+ ],
+ "index": "pypi",
+ "version": "==0.4.4"
},
"colorama": {
"hashes": [
@@ -62,6 +126,22 @@
"index": "pypi",
"version": "==0.4.4"
},
+ "configargparse": {
+ "hashes": [
+ "sha256:c39540eb4843883d526beeed912dc80c92481b0c13c9787c91e614a624de3666",
+ "sha256:f75b235a13dba6692ee9e019470e7bce41861d09606c39c41facb347c24ca3cf"
+ ],
+ "index": "pypi",
+ "version": "==1.5.2"
+ },
+ "contextlib2": {
+ "hashes": [
+ "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f",
+ "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==21.6.0"
+ },
"deep-merge": {
"hashes": [
"sha256:8056b4b43c6dfddf5c7b1feb3a09f1ab1cbd74e8382e43736ea8c5619e8e5a4e",
@@ -70,6 +150,30 @@
"index": "pypi",
"version": "==0.0.4"
},
+ "detect-secrets": {
+ "hashes": [
+ "sha256:68250b31bc108f665f05f0ecfb34f92423280e48e65adbb887fdf721ed909627",
+ "sha256:be8cca3dc65f6fd637f5dec9f583f1cf4a680dc1a580b3d2e65a5ac7a277456a"
+ ],
+ "index": "pypi",
+ "version": "==1.1.0"
+ },
+ "docker": {
+ "hashes": [
+ "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5",
+ "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"
+ ],
+ "index": "pypi",
+ "version": "==5.0.0"
+ },
+ "dockerfile-parse": {
+ "hashes": [
+ "sha256:07e65eec313978e877da819855870b3ae47f3fac94a40a965b9ede10484dacc5",
+ "sha256:c3fc8f491e1af8cb5f9e23ea6437a2913467b88a4be143095f150330b090be7e"
+ ],
+ "index": "pypi",
+ "version": "==1.2.0"
+ },
"dpath": {
"hashes": [
"sha256:496615b4ea84236d18e0d286122de74869a60e0f87e2c7ec6787ff286c48361b"
@@ -79,27 +183,43 @@
},
"gitdb": {
"hashes": [
- "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
- "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
+ "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
+ "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
],
"markers": "python_version >= '3.4'",
- "version": "==4.0.5"
+ "version": "==4.0.7"
},
"gitpython": {
"hashes": [
- "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac",
- "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5"
+ "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
+ "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
],
"index": "pypi",
- "version": "==3.1.12"
+ "version": "==3.1.18"
},
"idna": {
"hashes": [
- "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
- "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
+ "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.10"
+ "markers": "python_version >= '3'",
+ "version": "==3.2"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:0645585859e9a6689c523927a5032f2ba5919f1f7d0e84bd4533312320de1ff9",
+ "sha256:51c6635429c77cf1ae634c997ff9e53ca3438b495f10a55ba28594dd69764a8b"
+ ],
+ "index": "pypi",
+ "version": "==4.6.3"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
+ "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.0.1"
},
"jmespath": {
"hashes": [
@@ -109,6 +229,29 @@
"index": "pypi",
"version": "==0.10.0"
},
+ "jsonpatch": {
+ "hashes": [
+ "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397",
+ "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==1.32"
+ },
+ "jsonpointer": {
+ "hashes": [
+ "sha256:150f80c5badd02c757da6644852f612f88e8b4bc2f9852dcbf557c8738919686",
+ "sha256:5a34b698db1eb79ceac454159d3f7c12a451a91f6334a4f638454327b7a89962"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.1"
+ },
+ "jsonschema": {
+ "hashes": [
+ "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
+ "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
+ ],
+ "version": "==3.2.0"
+ },
"junit-xml": {
"hashes": [
"sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"
@@ -118,17 +261,89 @@
},
"lark-parser": {
"hashes": [
- "sha256:26215ebb157e6fb2ee74319aa4445b9f3b7e456e26be215ce19fdaaa901c20a4"
+ "sha256:42f367612a1bbc4cf9d8c8eb1b209d8a9b397d55af75620c9e6f53e502235996"
],
- "version": "==0.7.8"
+ "version": "==0.10.1"
+ },
+ "markdown": {
+ "hashes": [
+ "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49",
+ "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.3.4"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
+ "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
+ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
+ "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
+ "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
+ "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
+ "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
+ "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
+ "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
+ "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
+ "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
+ "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
+ "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
+ "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
+ "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
+ "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
+ "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
+ "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
+ "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
+ "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
+ "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
+ "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
+ "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
+ "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
+ "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
+ "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
+ "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
+ "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
+ "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
+ "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
+ "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
+ "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
+ "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
+ "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.1"
+ },
+ "networkx": {
+ "hashes": [
+ "sha256:2306f1950ce772c5a59a57f5486d59bb9cab98497c45fc49cbc45ac0dec119bb",
+ "sha256:5fcb7004be69e8fbdf07dcb502efa5c77cadcaad6982164134eeb9721f826c2e"
+ ],
+ "index": "pypi",
+ "version": "==2.6.2"
},
"packaging": {
"hashes": [
- "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
- "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
+ "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+ "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
+ ],
+ "index": "pypi",
+ "version": "==21.0"
+ },
+ "policy-sentry": {
+ "hashes": [
+ "sha256:49d154f87154fd409c57c71d4035d802d9a05667c328ba5c9ce2bef4d9de2a0a",
+ "sha256:8b88cb58a390ae7e0e06db13b3bbb5ece0d32d7d7c38a92259eb9c4722198fb5"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.11.16"
+ },
+ "policyuniverse": {
+ "hashes": [
+ "sha256:40ba6f9b8da755d3d7a5ccddcd5e095704f89deb86058dc43cdbb9c09140878a",
+ "sha256:f61552c1eb6d31f437ca6de7d74ba84f0d9772d9d8c61ceb4f49529a90693f97"
],
"index": "pypi",
- "version": "==20.8"
+ "version": "==1.4.0.20210730"
},
"pyparsing": {
"hashes": [
@@ -138,47 +353,97 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
+ "pyrsistent": {
+ "hashes": [
+ "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2",
+ "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7",
+ "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea",
+ "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426",
+ "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710",
+ "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1",
+ "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396",
+ "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2",
+ "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680",
+ "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35",
+ "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427",
+ "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b",
+ "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b",
+ "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f",
+ "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef",
+ "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c",
+ "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4",
+ "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d",
+ "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78",
+ "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b",
+ "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.18.0"
+ },
"python-dateutil": {
"hashes": [
- "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
- "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
+ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
+ "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.8.1"
+ "version": "==2.8.2"
},
"pyyaml": {
"hashes": [
- "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
- "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
- "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
- "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
- "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
- "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
- "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
- "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
- "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
- "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
- "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
- "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
- "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
+ "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
+ "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
+ "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
+ "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
+ "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
+ "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
+ "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
+ "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
+ "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
+ "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
+ "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
+ "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
+ "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
+ "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
+ "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
+ "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
+ "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
+ "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
+ "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
+ "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
+ "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
+ "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
+ "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
+ "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
+ "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
+ "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
+ "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
+ "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
+ "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"index": "pypi",
- "version": "==5.3.1"
+ "version": "==5.4.1"
},
"requests": {
"hashes": [
- "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
- "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
+ "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==2.25.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==2.26.0"
},
"s3transfer": {
"hashes": [
- "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed",
- "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2"
+ "sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
+ "sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2"
+ ],
+ "version": "==0.4.2"
+ },
+ "schema": {
+ "hashes": [
+ "sha256:cf97e4cd27e203ab6bb35968532de1ed8991bce542a646f0ff1d643629a4945d",
+ "sha256:fbb6a52eb2d9facf292f233adcc6008cffd94343c63ccac9a1cb1f3e6de1db17"
],
- "version": "==0.3.4"
+ "version": "==0.7.4"
},
"semantic-version": {
"hashes": [
@@ -190,27 +455,35 @@
},
"six": {
"hashes": [
- "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
- "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"index": "pypi",
- "version": "==1.15.0"
+ "version": "==1.16.0"
},
"smmap": {
"hashes": [
- "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
- "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
+ "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
+ "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==3.0.4"
+ "markers": "python_version >= '3.5'",
+ "version": "==4.0.0"
+ },
+ "soupsieve": {
+ "hashes": [
+ "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc",
+ "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"
+ ],
+ "markers": "python_version >= '3'",
+ "version": "==2.2.1"
},
"tabulate": {
"hashes": [
- "sha256:ac64cb76d53b1231d364babcd72abbb16855adac7de6665122f97b593f1eb2ba",
- "sha256:db2723a20d04bcda8522165c73eea7c300eda74e0ce852d9022e0159d7895007"
+ "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4",
+ "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"
],
"index": "pypi",
- "version": "==0.8.7"
+ "version": "==0.8.9"
},
"termcolor": {
"hashes": [
@@ -221,11 +494,20 @@
},
"tqdm": {
"hashes": [
- "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a",
- "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"
+ "sha256:3642d483b558eec80d3c831e23953582c34d7e4540db86d9e5ed9dad238dabc6",
+ "sha256:706dea48ee05ba16e936ee91cb3791cd2ea6da348a0e50b46863ff4363ff4340"
+ ],
+ "index": "pypi",
+ "version": "==4.62.0"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
+ "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
+ "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
],
"index": "pypi",
- "version": "==4.56.0"
+ "version": "==3.10.0.0"
},
"update-checker": {
"hashes": [
@@ -237,28 +519,37 @@
},
"urllib3": {
"hashes": [
- "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
- "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
+ "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
+ "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
],
- "markers": "python_version != '3.4'",
- "version": "==1.26.2"
- }
- },
- "develop": {
- "appdirs": {
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+ "version": "==1.26.6"
+ },
+ "websocket-client": {
"hashes": [
- "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
- "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
+ "sha256:4cf754af7e3b3ba76589d49f9e09fd9a6c0aae9b799a89124d656009c01a261d",
+ "sha256:8d07f155f8ed14ae3ced97bd7582b08f280bb1bfd27945f023ba2aceff05ab52"
],
- "version": "==1.4.4"
+ "markers": "python_version >= '3.6'",
+ "version": "==1.1.1"
},
+ "zipp": {
+ "hashes": [
+ "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
+ "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.5.0"
+ }
+ },
+ "develop": {
"attrs": {
"hashes": [
- "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
- "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
+ "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+ "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==20.3.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==21.2.0"
},
"bandit": {
"hashes": [
@@ -268,112 +559,63 @@
"index": "pypi",
"version": "==1.7.0"
},
- "black": {
- "hashes": [
- "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
- "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==19.10b0"
- },
- "cached-property": {
- "hashes": [
- "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130",
- "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"
- ],
- "version": "==1.5.2"
- },
- "cerberus": {
- "hashes": [
- "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589"
- ],
- "version": "==1.3.2"
- },
- "certifi": {
- "hashes": [
- "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
- "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
- ],
- "version": "==2020.12.5"
- },
- "chardet": {
- "hashes": [
- "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
- "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==4.0.0"
- },
- "click": {
- "hashes": [
- "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
- "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==7.1.2"
- },
- "colorama": {
+ "coverage": {
"hashes": [
- "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
- "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
+ "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
+ "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
+ "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
+ "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
+ "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
+ "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
+ "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
+ "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
+ "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
+ "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
+ "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
+ "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
+ "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
+ "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
+ "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
+ "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
+ "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
+ "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
+ "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
+ "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
+ "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
+ "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
+ "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
+ "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
+ "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
+ "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
+ "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
+ "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
+ "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
+ "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
+ "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
+ "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
+ "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
+ "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
+ "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
+ "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
+ "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
+ "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
+ "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
+ "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
+ "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
+ "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
+ "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
+ "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
+ "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
+ "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
+ "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
+ "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
+ "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
+ "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
+ "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
+ "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
],
"index": "pypi",
- "version": "==0.4.4"
- },
- "coverage": {
- "hashes": [
- "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297",
- "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1",
- "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497",
- "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606",
- "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528",
- "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b",
- "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4",
- "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830",
- "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1",
- "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f",
- "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d",
- "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3",
- "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8",
- "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500",
- "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7",
- "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb",
- "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b",
- "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059",
- "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b",
- "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72",
- "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36",
- "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277",
- "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c",
- "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631",
- "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff",
- "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8",
- "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec",
- "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b",
- "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7",
- "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105",
- "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b",
- "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c",
- "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b",
- "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98",
- "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4",
- "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879",
- "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f",
- "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4",
- "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044",
- "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e",
- "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899",
- "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f",
- "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448",
- "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714",
- "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2",
- "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d",
- "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd",
- "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7",
- "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"
- ],
- "index": "pypi",
- "version": "==5.3.1"
+ "version": "==5.5"
},
"coverage-badge": {
"hashes": [
@@ -383,44 +625,21 @@
"index": "pypi",
"version": "==1.0.1"
},
- "distlib": {
- "hashes": [
- "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb",
- "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"
- ],
- "version": "==0.3.1"
- },
"gitdb": {
"hashes": [
- "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
- "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
+ "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
+ "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
],
"markers": "python_version >= '3.4'",
- "version": "==4.0.5"
+ "version": "==4.0.7"
},
"gitpython": {
"hashes": [
- "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac",
- "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5"
+ "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
+ "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
],
"index": "pypi",
- "version": "==3.1.12"
- },
- "idna": {
- "hashes": [
- "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
- "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.10"
- },
- "importlib-metadata": {
- "hashes": [
- "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771",
- "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"
- ],
- "markers": "python_version < '3.8'",
- "version": "==3.4.0"
+ "version": "==3.1.18"
},
"iniconfig": {
"hashes": [
@@ -429,75 +648,28 @@
],
"version": "==1.1.1"
},
- "orderedmultidict": {
+ "jsonschema": {
"hashes": [
- "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad",
- "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3"
+ "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
+ "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
],
- "version": "==1.0.1"
+ "version": "==3.2.0"
},
"packaging": {
"hashes": [
- "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
- "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
+ "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+ "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"index": "pypi",
- "version": "==20.8"
- },
- "pathspec": {
- "hashes": [
- "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
- "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
- ],
- "version": "==0.8.1"
+ "version": "==21.0"
},
"pbr": {
"hashes": [
- "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
- "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
+ "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd",
+ "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"
],
"markers": "python_version >= '2.6'",
- "version": "==5.5.1"
- },
- "pep517": {
- "hashes": [
- "sha256:3985b91ebf576883efe5fa501f42a16de2607684f3797ddba7202b71b7d0da51",
- "sha256:aeb78601f2d1aa461960b43add204cc7955667687fbcf9cdb5170f00556f117f"
- ],
- "version": "==0.9.1"
- },
- "pip-shims": {
- "hashes": [
- "sha256:05b00ade9d1e686a98bb656dd9b0608a933897283dc21913fad6ea5409ff7e91",
- "sha256:16ca9f87485667b16b978b68a1aae4f9cc082c0fa018aed28567f9f34a590569"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==0.5.3"
- },
- "pipenv-setup": {
- "hashes": [
- "sha256:8a439aff7b16e18d7e07702c9186fc5fe86156679eace90e10c2578a43bd7af1",
- "sha256:e1bfd55c1152024e762f1c17f6189fcb073166509e7c0228870f7ea160355648"
- ],
- "index": "pypi",
- "version": "==3.1.1"
- },
- "pipfile": {
- "hashes": [
- "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984"
- ],
- "version": "==0.0.2"
- },
- "plette": {
- "extras": [
- "validation"
- ],
- "hashes": [
- "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26",
- "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16"
- ],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==0.2.3"
+ "version": "==5.6.0"
},
"pluggy": {
"hashes": [
@@ -523,118 +695,91 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
- "pytest": {
- "hashes": [
- "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
- "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
+ "pyrsistent": {
+ "hashes": [
+ "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2",
+ "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7",
+ "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea",
+ "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426",
+ "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710",
+ "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1",
+ "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396",
+ "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2",
+ "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680",
+ "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35",
+ "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427",
+ "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b",
+ "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b",
+ "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f",
+ "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef",
+ "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c",
+ "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4",
+ "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d",
+ "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78",
+ "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b",
+ "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"
],
- "index": "pypi",
- "version": "==6.2.1"
+ "markers": "python_version >= '3.6'",
+ "version": "==0.18.0"
},
- "python-dateutil": {
+ "pytest": {
"hashes": [
- "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
- "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
+ "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
+ "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.8.1"
+ "index": "pypi",
+ "version": "==6.2.4"
},
"pyyaml": {
"hashes": [
- "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
- "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
- "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
- "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
- "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
- "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
- "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
- "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
- "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
- "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
- "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
- "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
- "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
- ],
- "index": "pypi",
- "version": "==5.3.1"
- },
- "regex": {
- "hashes": [
- "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
- "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
- "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
- "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
- "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
- "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
- "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
- "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
- "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
- "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
- "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
- "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
- "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
- "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
- "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
- "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
- "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
- "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
- "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
- "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
- "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
- "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
- "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
- "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
- "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
- "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
- "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
- "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
- "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
- "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
- "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
- "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
- "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
- "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
- "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
- "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
- "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
- "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
- "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
- "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
- "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
- ],
- "version": "==2020.11.13"
- },
- "requests": {
- "hashes": [
- "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
- "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==2.25.1"
- },
- "requirementslib": {
- "hashes": [
- "sha256:50d20f27e4515a2393695b0d886219598302163438ae054253147b2bad9b4a44",
- "sha256:9c1e8666ca4512724cdd1739adcc7df19ec7ad2ed21f0e748f9631ad6b54f321"
+ "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
+ "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
+ "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
+ "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
+ "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
+ "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
+ "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
+ "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
+ "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
+ "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
+ "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
+ "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
+ "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
+ "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
+ "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
+ "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
+ "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
+ "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
+ "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
+ "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
+ "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
+ "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
+ "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
+ "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
+ "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
+ "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
+ "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
+ "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
+ "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==1.5.16"
+ "index": "pypi",
+ "version": "==5.4.1"
},
"six": {
"hashes": [
- "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
- "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"index": "pypi",
- "version": "==1.15.0"
+ "version": "==1.16.0"
},
"smmap": {
"hashes": [
- "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
- "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
+ "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
+ "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==3.0.4"
+ "markers": "python_version >= '3.5'",
+ "version": "==4.0.0"
},
"stevedore": {
"hashes": [
@@ -652,66 +797,6 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
- "tomlkit": {
- "hashes": [
- "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831",
- "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==0.7.0"
- },
- "typed-ast": {
- "hashes": [
- "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
- "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
- "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
- "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
- "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
- "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
- "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
- "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
- "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
- "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
- "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
- "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
- "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
- "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
- "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
- "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
- "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
- "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
- "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
- "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
- "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
- "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
- "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
- "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
- "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
- "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
- "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
- "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
- "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
- "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
- ],
- "version": "==1.4.2"
- },
- "typing-extensions": {
- "hashes": [
- "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
- "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
- "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
- ],
- "markers": "python_version < '3.8'",
- "version": "==3.7.4.3"
- },
- "urllib3": {
- "hashes": [
- "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
- "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
- ],
- "markers": "python_version != '3.4'",
- "version": "==1.26.2"
- },
"urllib3-mock": {
"hashes": [
"sha256:702c90042920d771c9902b7b5b542551cc57f259078f4eada47ab4e8cdd11f1a",
@@ -719,30 +804,6 @@
],
"index": "pypi",
"version": "==0.3.3"
- },
- "vistir": {
- "hashes": [
- "sha256:a37079cdbd85d31a41cdd18457fe521e15ec08b255811e81aa061fd5f48a20fb",
- "sha256:eff1d19ef50c703a329ed294e5ec0b0fbb35b96c1b3ee6dcdb266dddbe1e935a"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==0.5.2"
- },
- "wheel": {
- "hashes": [
- "sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e",
- "sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==0.36.2"
- },
- "zipp": {
- "hashes": [
- "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108",
- "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"
- ],
- "markers": "python_version < '3.8'",
- "version": "==3.4.0"
}
}
}
diff --git a/README.md b/README.md
index 1b72d5eb8f..6dc61dadc8 100644
--- a/README.md
+++ b/README.md
@@ -3,20 +3,19 @@
[](https://bridgecrew.io/?utm_source=github&utm_medium=organic_oss&utm_campaign=checkov)
[](https://github.com/bridgecrewio/checkov/actions?query=workflow%3Abuild)
[](https://github.com/bridgecrewio/checkov/actions?query=event%3Apush+branch%3Amaster+workflow%3Asecurity)
-[](https://github.com/bridgecrewio/checkov/actions?query=workflow%3Acoverage)
-[](https://www.checkov.io/documentation?utm_source=github&utm_medium=organic_oss&utm_campaign=checkov)
+[](https://github.com/bridgecrewio/checkov/actions?query=workflow%3Acoverage)
+[](https://www.checkov.io/1.Welcome/What%20is%20Checkov.html?utm_source=github&utm_medium=organic_oss&utm_campaign=checkov)
[](https://pypi.org/project/checkov/)
[](#)
[](#)
[](https://pepy.tech/project/checkov)
[](https://slack.bridgecrew.io/?utm_source=github&utm_medium=organic_oss&utm_campaign=checkov)
-
+
**Checkov** is a static code analysis tool for infrastructure-as-code.
-It scans cloud infrastructure provisioned using [Terraform](https://terraform.io/), [Cloudformation](https://aws.amazon.com/cloudformation/), [Kubernetes](https://kubernetes.io/), [Serverless](https://www.serverless.com/) or [ARM Templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview) and detects security and compliance misconfigurations.
+It scans cloud infrastructure provisioned using [Terraform](https://terraform.io/), Terraform plan, [Cloudformation](https://aws.amazon.com/cloudformation/), [Kubernetes](https://kubernetes.io/), [Dockerfile](https://www.docker.com/), [Serverless](https://www.serverless.com/) or [ARM Templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview) and detects security and compliance misconfigurations using graph-based scanning.
-
Checkov also powers [**Bridgecrew**](https://bridgecrew.io/?utm_source=github&utm_medium=organic_oss&utm_campaign=checkov), the developer-first platform that codifies and streamlines cloud security throughout the development lifecycle. Bridgecrew identifies, fixes, and prevents misconfigurations in cloud resources and infrastructure-as-code files.
@@ -38,13 +37,16 @@ Checkov also powers [**Bridgecrew**](https://bridgecrew.io/?utm_source=github&ut
## Features
- * [Over 500 built-in policies](docs/3.Scans/resource-scans.md) cover security and compliance best practices for AWS, Azure and Google Cloud.
- * Scans Terraform, Terraform Plan, CloudFormation, Kubernetes, Serverless framework and ARM template files.
- * Detects [AWS credentials](docs/3.Scans/Credentials%20Scans.md) in EC2 Userdata, Lambda environment variables and Terraform providers.
+ * [Over 1000 built-in policies](docs/5.Policy%20Index/all.md) cover security and compliance best practices for AWS, Azure and Google Cloud.
+ * Scans Terraform, Terraform Plan, CloudFormation, Kubernetes, Dockerfile, Serverless framework and ARM template files.
+ * Supports Context-awareness policies based on in-memory graph-based scanning.
+ * Supports Python format for attribute policies and YAML format for both attribute and composite policies.
+ * Detects [AWS credentials](docs/2.Basics/Scanning%20Credentials%20and%20Secrets.md) in EC2 Userdata, Lambda environment variables and Terraform providers.
+ * [Identifies secrets](https://bridgecrew.io/blog/checkov-secrets-scanning-find-exposed-credentials-in-iac/) using regular expressions, keywords, and entropy based detection.
* Evaluates [Terraform Provider](https://registry.terraform.io/browse/providers) settings to regulate the creation, management, and updates of IaaS, PaaS or SaaS managed through Terraform.
- * Policies support evaluation of [variables](docs/2.Concepts/Evaluations.md) to their optional default value.
- * Supports in-line [suppression](docs/2.Concepts/Suppressions.md) of accepted risks or false-positives to reduce recurring scan failures. Also supports global skip from using CLI.
-* [Output](docs/1.Introduction/Results.md) currently available as CLI, JSON, JUnit XML and github markdown and link to remediation [guides](https://docs.bridgecrew.io/docs/aws-policy-index).
+ * Policies support evaluation of [variables](docs/2.Basics/Handling%20Variables.md) to their optional default value.
+ * Supports in-line [suppression](docs/2.Basics/Suppressing%20and%20Skipping%20Policies.md) of accepted risks or false-positives to reduce recurring scan failures. Also supports global skip from using CLI.
+* [Output](docs/2.Basics/Reviewing%20Scan%20Results.md) currently available as CLI, JSON, JUnit XML and github markdown and link to remediation [guides](https://docs.bridgecrew.io/docs/aws-policy-index).
## Screenshots
@@ -58,7 +60,7 @@ Scheduled scan result in Jenkins
## Getting started
-### Requrirements
+### Requirements
* Python >= 3.7 (Data classes are available for Python 3.7+)
* Terraform >= 0.12
@@ -75,6 +77,19 @@ pip3 install --upgrade pip && pip3 install --upgrade setuptools
pip3 install checkov
```
+Installation on Ubuntu 18.04 LTS:
+
+Ubuntu 18.04 ships with Python 3.6. Install python 3.7 (from ppa repository)
+
+```sh
+sudo apt update
+sudo apt install software-properties-common
+sudo add-apt-repository ppa:deadsnakes/ppa
+sudo apt install python3.7
+sudo apt install python3-pip
+sudo python3.7 -m pip install -U checkov #to install or upgrade checkov)
+```
+
or using homebrew (MacOS only)
```sh
@@ -131,7 +146,6 @@ If you have installed `jq` you can convert json file into multiple lines with th
terraform show -json tf.plan | jq '.' > tf.json
```
Scan result would be much user friendly.
-
```sh
checkov -f tf.json
Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
@@ -146,6 +160,10 @@ Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enab
```
+Alternatively, specify the repo root of the hcl files used to generate the plan file, using the `--repo-root-for-plan-enrichment` flag, to enrich the output with the appropriate file path, line numbers, and codeblock of the resource(s). An added benefit is that check suppressions will be handled accordingly.
+```sh
+checkov -f tf.json --repo-root-for-plan-enrichment /user/path/to/iac/code
+```
### Scan result sample (CLI)
@@ -160,7 +178,7 @@ Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
Failed for resource: aws_s3_bucket.sls_deployment_bucket_name
```
-Start using Checkov by reading the [Getting Started](docs/1.Introduction/Getting%20Started.md) page.
+Start using Checkov by reading the [Getting Started](docs/1.Welcome/Quick%20Start.md) page.
### Using Docker
@@ -190,7 +208,7 @@ checkov --directory . --check CKV_AWS_20,CKV_AWS_57
Run all checks except 1 specified:
```sh
-checkov -d . --skip-check CKV_AWS_52
+checkov -d . --skip-check CKV_AWS_20
```
Run all checks except checks with specified patterns:
@@ -216,7 +234,7 @@ To skip a check on a given Terraform definition block or CloudFormation resource
`checkov:skip=:`
-* `` is one of the [available check scanners](docs/3.Scans/resource-scans.md)
+* `` is one of the [available check scanners](docs/5.Policy Index/all.md)
* `` is an optional suppression reason to be included in the output
#### Example
@@ -246,7 +264,13 @@ Check: "S3 Bucket has an ACL defined which allows public access."
...
```
+To skip multiple checks, add each as a new line.
+```
+ #checkov:skip=CKV2_AWS_6
+ #checkov:skip=CKV_AWS_20:The bucket is a public static content host
+```
+
To suppress checks in Kubernetes manifests, annotations are used with the following format:
`checkov.io/skip#: =`
@@ -273,16 +297,82 @@ For detailed logging to stdout setup the environment variable `LOG_LEVEL` to `DE
Default is `LOG_LEVEL=WARNING`.
#### Skipping directories
-To skip a whole directory, use the environment variable `CKV_IGNORED_DIRECTORIES`.
-Default is `CKV_IGNORED_DIRECTORIES=node_modules,.terraform,.serverless`
+To skip files or directories, use the argument `--skip-path`, which can be specified multiple times. This argument accepts regular expressions for paths relative to the current working directory. You can use it to skip entire directories and / or specific files.
+
+By default, all directories named `node_modules`, `.terraform`, and `.serverless` will be skipped, in addition to any files or directories beginning with `.`.
-## Alternatives
+You can override the default set of directories to skip by setting the environment variable `CKV_IGNORED_DIRECTORIES`. Note that if you want to preserve this list and add to it, you must include these values. For example, `CKV_IGNORED_DIRECTORIES=mynewdir` will skip only that directory, but not the others mentioned above. This variable is legacy functionality; we recommend using the `--skip-file` flag.
-For Terraform compliance scanners check out [tfsec](https://github.com/liamg/tfsec) and [Terraform AWS Secure Baseline](https://github.com/nozaq/terraform-aws-secure-baseline) for secured basline.
+#### VSCODE Extension
-For CloudFormation scanning check out [cfripper](https://github.com/Skyscanner/cfripper/) and [cfn_nag](https://github.com/stelligent/cfn_nag).
+If you want to use checkov's within vscode, give a try to the vscode extension availble at [vscode](https://marketplace.visualstudio.com/items?itemName=Bridgecrew.checkov)
-For Kubernetes scanning check out [kube-scan](https://github.com/octarinesec/kube-scan) and [Polaris](https://github.com/FairwindsOps/polaris).
+### Configuration using a config file
+
+Checkov can be configured using a YAML configuration file. By default, checkov looks for a `.checkov.yaml` or `.checkov.yml` file in the following places in order of precedence:
+* Directory against which checkov is run. (`--directory`)
+* Current working directory where checkov is called.
+* User's home directory.
+
+**Attention**: it is a best practice for checkov configuration file to be loaded from a trusted source composed by a verified identity, so that scanned files, check ids and loaded custom checks are as desired.
+
+Users can also pass in the path to a config file via the command line. In this case, the other config files will be ignored. For example:
+```sh
+checkov --config-file path/to/config.yaml
+```
+Users can also create a config file using the `--create-config` command, which takes the current command line args and writes them out to a given path. For example:
+```sh
+checkov --compact --directory test-dir --docker-image sample-image --dockerfile-path Dockerfile --download-external-modules True --external-checks-dir sample-dir --no-guide --quiet --repo-id bridgecrew/sample-repo --skip-check CKV_DOCKER_3,CKV_DOCKER_2 --skip-fixes --skip-framework dockerfile --skip-suppressions --soft-fail --branch develop --check CKV_DOCKER_1 --create-config /Users/sample/config.yml
+```
+Will create a `config.yaml` file which looks like this:
+```yaml
+branch: develop
+check:
+ - CKV_DOCKER_1
+compact: true
+directory:
+ - test-dir
+docker-image: sample-image
+dockerfile-path: Dockerfile
+download-external-modules: true
+evaluate-variables: true
+external-checks-dir:
+ - sample-dir
+external-modules-download-path: .external_modules
+framework: all
+no-guide: true
+output: cli
+quiet: true
+repo-id: bridgecrew/sample-repo
+skip-check:
+ - CKV_DOCKER_3
+ - CKV_DOCKER_2
+skip-fixes: true
+skip-framework: dockerfile
+skip-suppressions: true
+soft-fail: true
+```
+
+Users can also use the `--show-config` flag to view all the args and settings and where they came from i.e. commandline, config file, environment variable or default. For example:
+```sh
+checkov --show-config
+```
+Will display:
+```sh
+Command Line Args: --show-config
+Environment Variables:
+ BC_API_KEY: your-api-key
+Config File (/Users/sample/.checkov.yml):
+ soft-fail: False
+ branch: master
+ skip-check: ['CKV_DOCKER_3', 'CKV_DOCKER_2']
+Defaults:
+ --output: cli
+ --framework: all
+ --download-external-modules:False
+ --external-modules-download-path:.external_modules
+ --evaluate-variables:True
+```
## Contributing
@@ -290,7 +380,7 @@ Contribution is welcomed!
Start by reviewing the [contribution guidelines](CONTRIBUTING.md). After that, take a look at a [good first issue](https://github.com/bridgecrewio/checkov/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
-Looking to contribute new checks? Learn how to write a new check (AKA policy) [here](docs/5.Contribution/New-Check.md).
+Looking to contribute new checks? Learn how to write a new check (AKA policy) [here](docs/6.Contribution/Contribution%20Overview.md).
## Disclaimer
`checkov` does not save, publish or share with anyone any identifiable customer information.
diff --git a/bin/checkov b/bin/checkov
index 5b1fabd9c0..f03793b531 100755
--- a/bin/checkov
+++ b/bin/checkov
@@ -1,5 +1,8 @@
#!/usr/bin/env python
from checkov.main import run
+import warnings
if __name__ == '__main__':
- run()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", category=SyntaxWarning)
+ exit(run())
diff --git a/checkov/arm/base_parameter_check.py b/checkov/arm/base_parameter_check.py
new file mode 100644
index 0000000000..ad86f4db83
--- /dev/null
+++ b/checkov/arm/base_parameter_check.py
@@ -0,0 +1,30 @@
+from abc import abstractmethod
+
+from checkov.arm.registry import arm_parameter_registry
+from checkov.common.checks.base_check import BaseCheck
+from checkov.common.multi_signature import multi_signature
+
+
+class BaseParameterCheck(BaseCheck):
+ def __init__(self, name, id, categories, supported_resources):
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_resources,
+ block_type="parameter")
+ self.supported_resources = supported_resources
+ arm_parameter_registry.register(self)
+
+ def scan_entity_conf(self, conf, entity_type):
+ return self.scan_resource_conf(conf, entity_type)
+
+ @multi_signature()
+ @abstractmethod
+ def scan_resource_conf(self, conf, entity_type):
+ raise NotImplementedError()
+
+ @classmethod
+ @scan_resource_conf.add_signature(args=["self", "conf"])
+ def _scan_resource_conf_self_conf(cls, wrapped):
+ def wrapper(self, conf, entity_type=None):
+ # keep default argument for entity_type so old code, that doesn't set it, will work.
+ return wrapped(self, conf)
+
+ return wrapper
diff --git a/checkov/arm/base_registry.py b/checkov/arm/base_registry.py
index fd74091460..ea088bc213 100644
--- a/checkov/arm/base_registry.py
+++ b/checkov/arm/base_registry.py
@@ -3,9 +3,6 @@
class Registry(BaseCheckRegistry):
- def __init__(self):
- super().__init__()
-
def extract_entity_details(self, entity):
resource_name, resource = next(iter(entity.items()))
resource_type = str(resource['type']) # entity['type'] ??
diff --git a/checkov/arm/base_resource_check.py b/checkov/arm/base_resource_check.py
index 9457a30f2f..11cf4a9cfb 100644
--- a/checkov/arm/base_resource_check.py
+++ b/checkov/arm/base_resource_check.py
@@ -1,6 +1,6 @@
from abc import abstractmethod
-from checkov.arm.registry import arm_registry
+from checkov.arm.registry import arm_resource_registry
from checkov.common.checks.base_check import BaseCheck
from checkov.common.multi_signature import multi_signature
@@ -10,7 +10,7 @@ def __init__(self, name, id, categories, supported_resources):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_resources,
block_type="resource")
self.supported_resources = supported_resources
- arm_registry.register(self)
+ arm_resource_registry.register(self)
def scan_entity_conf(self, conf, entity_type):
return self.scan_resource_conf(conf, entity_type)
diff --git a/checkov/arm/checks/__init__.py b/checkov/arm/checks/__init__.py
index 13632cb9e7..835ade9d2b 100644
--- a/checkov/arm/checks/__init__.py
+++ b/checkov/arm/checks/__init__.py
@@ -1,4 +1,2 @@
-from os.path import dirname, basename, isfile, join
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+from checkov.arm.checks.resource import *
+from checkov.arm.checks.parameter import *
diff --git a/checkov/arm/checks/parameter/SecureStringParameterNoHardcodedValue.py b/checkov/arm/checks/parameter/SecureStringParameterNoHardcodedValue.py
new file mode 100644
index 0000000000..3aabc5cd25
--- /dev/null
+++ b/checkov/arm/checks/parameter/SecureStringParameterNoHardcodedValue.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.arm.base_parameter_check import BaseParameterCheck
+
+
+class SecureStringParameterNoHardcodedValue(BaseParameterCheck):
+ def __init__(self):
+ name = "SecureString parameter should not have hardcoded default values"
+ id = "CKV_AZURE_131"
+ supported_resources = ['secureString']
+ categories = [CheckCategories.SECRETS]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ # https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/test-cases#secure-parameters-cant-have-hardcoded-default
+ if conf.get('defaultValue'): # should be missing, or an empty string
+ return CheckResult.FAILED
+ else:
+ return CheckResult.PASSED
+
+
+check = SecureStringParameterNoHardcodedValue()
diff --git a/checkov/arm/checks/parameter/__init__.py b/checkov/arm/checks/parameter/__init__.py
new file mode 100644
index 0000000000..13632cb9e7
--- /dev/null
+++ b/checkov/arm/checks/parameter/__init__.py
@@ -0,0 +1,4 @@
+from os.path import dirname, basename, isfile, join
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
diff --git a/checkov/arm/checks/AKSApiServerAuthorizedIpRanges.py b/checkov/arm/checks/resource/AKSApiServerAuthorizedIpRanges.py
similarity index 100%
rename from checkov/arm/checks/AKSApiServerAuthorizedIpRanges.py
rename to checkov/arm/checks/resource/AKSApiServerAuthorizedIpRanges.py
diff --git a/checkov/arm/checks/AKSDashboardDisabled.py b/checkov/arm/checks/resource/AKSDashboardDisabled.py
similarity index 100%
rename from checkov/arm/checks/AKSDashboardDisabled.py
rename to checkov/arm/checks/resource/AKSDashboardDisabled.py
diff --git a/checkov/arm/checks/AKSLoggingEnabled.py b/checkov/arm/checks/resource/AKSLoggingEnabled.py
similarity index 100%
rename from checkov/arm/checks/AKSLoggingEnabled.py
rename to checkov/arm/checks/resource/AKSLoggingEnabled.py
diff --git a/checkov/arm/checks/AKSNetworkPolicy.py b/checkov/arm/checks/resource/AKSNetworkPolicy.py
similarity index 100%
rename from checkov/arm/checks/AKSNetworkPolicy.py
rename to checkov/arm/checks/resource/AKSNetworkPolicy.py
diff --git a/checkov/arm/checks/AKSRbacEnabled.py b/checkov/arm/checks/resource/AKSRbacEnabled.py
similarity index 100%
rename from checkov/arm/checks/AKSRbacEnabled.py
rename to checkov/arm/checks/resource/AKSRbacEnabled.py
diff --git a/checkov/arm/checks/AppServiceAuthentication.py b/checkov/arm/checks/resource/AppServiceAuthentication.py
similarity index 100%
rename from checkov/arm/checks/AppServiceAuthentication.py
rename to checkov/arm/checks/resource/AppServiceAuthentication.py
diff --git a/checkov/arm/checks/AppServiceClientCertificate.py b/checkov/arm/checks/resource/AppServiceClientCertificate.py
similarity index 100%
rename from checkov/arm/checks/AppServiceClientCertificate.py
rename to checkov/arm/checks/resource/AppServiceClientCertificate.py
diff --git a/checkov/arm/checks/AppServiceHTTPSOnly.py b/checkov/arm/checks/resource/AppServiceHTTPSOnly.py
similarity index 100%
rename from checkov/arm/checks/AppServiceHTTPSOnly.py
rename to checkov/arm/checks/resource/AppServiceHTTPSOnly.py
diff --git a/checkov/arm/checks/AppServiceHttps20Enabled.py b/checkov/arm/checks/resource/AppServiceHttps20Enabled.py
similarity index 100%
rename from checkov/arm/checks/AppServiceHttps20Enabled.py
rename to checkov/arm/checks/resource/AppServiceHttps20Enabled.py
diff --git a/checkov/arm/checks/AppServiceIdentity.py b/checkov/arm/checks/resource/AppServiceIdentity.py
similarity index 100%
rename from checkov/arm/checks/AppServiceIdentity.py
rename to checkov/arm/checks/resource/AppServiceIdentity.py
diff --git a/checkov/arm/checks/AppServiceMinTLSVersion.py b/checkov/arm/checks/resource/AppServiceMinTLSVersion.py
similarity index 100%
rename from checkov/arm/checks/AppServiceMinTLSVersion.py
rename to checkov/arm/checks/resource/AppServiceMinTLSVersion.py
diff --git a/checkov/arm/checks/AzureInstancePassword.py b/checkov/arm/checks/resource/AzureInstancePassword.py
similarity index 100%
rename from checkov/arm/checks/AzureInstancePassword.py
rename to checkov/arm/checks/resource/AzureInstancePassword.py
diff --git a/checkov/arm/checks/AzureManagedDiscEncryption.py b/checkov/arm/checks/resource/AzureManagedDiscEncryption.py
similarity index 100%
rename from checkov/arm/checks/AzureManagedDiscEncryption.py
rename to checkov/arm/checks/resource/AzureManagedDiscEncryption.py
diff --git a/checkov/arm/checks/CustomRoleDefinitionSubscriptionOwner.py b/checkov/arm/checks/resource/CustomRoleDefinitionSubscriptionOwner.py
similarity index 100%
rename from checkov/arm/checks/CustomRoleDefinitionSubscriptionOwner.py
rename to checkov/arm/checks/resource/CustomRoleDefinitionSubscriptionOwner.py
diff --git a/checkov/arm/checks/KeyvaultRecoveryEnabled.py b/checkov/arm/checks/resource/KeyvaultRecoveryEnabled.py
similarity index 100%
rename from checkov/arm/checks/KeyvaultRecoveryEnabled.py
rename to checkov/arm/checks/resource/KeyvaultRecoveryEnabled.py
diff --git a/checkov/arm/checks/MonitorLogProfileCategories.py b/checkov/arm/checks/resource/MonitorLogProfileCategories.py
similarity index 100%
rename from checkov/arm/checks/MonitorLogProfileCategories.py
rename to checkov/arm/checks/resource/MonitorLogProfileCategories.py
diff --git a/checkov/arm/checks/MonitorLogProfileRetentionDays.py b/checkov/arm/checks/resource/MonitorLogProfileRetentionDays.py
similarity index 100%
rename from checkov/arm/checks/MonitorLogProfileRetentionDays.py
rename to checkov/arm/checks/resource/MonitorLogProfileRetentionDays.py
diff --git a/checkov/arm/checks/MySQLServerSSLEnforcementEnabled.py b/checkov/arm/checks/resource/MySQLServerSSLEnforcementEnabled.py
similarity index 100%
rename from checkov/arm/checks/MySQLServerSSLEnforcementEnabled.py
rename to checkov/arm/checks/resource/MySQLServerSSLEnforcementEnabled.py
diff --git a/checkov/arm/checks/NSGRulePortAccessRestricted.py b/checkov/arm/checks/resource/NSGRulePortAccessRestricted.py
similarity index 97%
rename from checkov/arm/checks/NSGRulePortAccessRestricted.py
rename to checkov/arm/checks/resource/NSGRulePortAccessRestricted.py
index 84652b1bd0..335228b302 100644
--- a/checkov/arm/checks/NSGRulePortAccessRestricted.py
+++ b/checkov/arm/checks/resource/NSGRulePortAccessRestricted.py
@@ -14,7 +14,7 @@
# https://docs.microsoft.com/en-us/azure/templates/microsoft.network/networksecuritygroups/securityrules
INTERNET_ADDRESSES = ["*", "0.0.0.0", "/0", "/0", "internet", "any"] # nosec
-PORT_RANGE = re.compile('\d+-\d+')
+PORT_RANGE = re.compile(r"\d+-\d+")
class NSGRulePortAccessRestricted(BaseResourceCheck):
def __init__(self, name, check_id, port):
@@ -25,7 +25,7 @@ def __init__(self, name, check_id, port):
self.port = port
def is_port_in_range(self, portRange):
- if re.match(PORT_RANGE, portRange):
+ if re.match(PORT_RANGE, str(portRange)):
start, end = int(portRange.split('-')[0]), int(portRange.split('-')[1])
if start <= self.port <= end:
return True
diff --git a/checkov/arm/checks/NSGRuleRDPAccessRestricted.py b/checkov/arm/checks/resource/NSGRuleRDPAccessRestricted.py
similarity index 72%
rename from checkov/arm/checks/NSGRuleRDPAccessRestricted.py
rename to checkov/arm/checks/resource/NSGRuleRDPAccessRestricted.py
index a092e04431..7fab124c6d 100644
--- a/checkov/arm/checks/NSGRuleRDPAccessRestricted.py
+++ b/checkov/arm/checks/resource/NSGRuleRDPAccessRestricted.py
@@ -1,4 +1,4 @@
-from checkov.arm.checks.NSGRulePortAccessRestricted import NSGRulePortAccessRestricted
+from checkov.arm.checks.resource.NSGRulePortAccessRestricted import NSGRulePortAccessRestricted
class NSGRuleRDPAccessRestricted(NSGRulePortAccessRestricted):
diff --git a/checkov/arm/checks/NSGRuleSSHAccessRestricted.py b/checkov/arm/checks/resource/NSGRuleSSHAccessRestricted.py
similarity index 72%
rename from checkov/arm/checks/NSGRuleSSHAccessRestricted.py
rename to checkov/arm/checks/resource/NSGRuleSSHAccessRestricted.py
index 403398ea29..16d52cc51a 100644
--- a/checkov/arm/checks/NSGRuleSSHAccessRestricted.py
+++ b/checkov/arm/checks/resource/NSGRuleSSHAccessRestricted.py
@@ -1,4 +1,4 @@
-from checkov.arm.checks.NSGRulePortAccessRestricted import NSGRulePortAccessRestricted
+from checkov.arm.checks.resource.NSGRulePortAccessRestricted import NSGRulePortAccessRestricted
class NSGRuleSSHAccessRestricted(NSGRulePortAccessRestricted):
diff --git a/checkov/arm/checks/NetworkWatcherFlowLogPeriod.py b/checkov/arm/checks/resource/NetworkWatcherFlowLogPeriod.py
similarity index 100%
rename from checkov/arm/checks/NetworkWatcherFlowLogPeriod.py
rename to checkov/arm/checks/resource/NetworkWatcherFlowLogPeriod.py
diff --git a/checkov/arm/checks/PostgreSQLServerConnectionThrottlingEnabled.py b/checkov/arm/checks/resource/PostgreSQLServerConnectionThrottlingEnabled.py
similarity index 100%
rename from checkov/arm/checks/PostgreSQLServerConnectionThrottlingEnabled.py
rename to checkov/arm/checks/resource/PostgreSQLServerConnectionThrottlingEnabled.py
diff --git a/checkov/arm/checks/PostgreSQLServerLogCheckpointsEnabled.py b/checkov/arm/checks/resource/PostgreSQLServerLogCheckpointsEnabled.py
similarity index 100%
rename from checkov/arm/checks/PostgreSQLServerLogCheckpointsEnabled.py
rename to checkov/arm/checks/resource/PostgreSQLServerLogCheckpointsEnabled.py
diff --git a/checkov/arm/checks/PostgreSQLServerLogConnectionsEnabled.py b/checkov/arm/checks/resource/PostgreSQLServerLogConnectionsEnabled.py
similarity index 100%
rename from checkov/arm/checks/PostgreSQLServerLogConnectionsEnabled.py
rename to checkov/arm/checks/resource/PostgreSQLServerLogConnectionsEnabled.py
diff --git a/checkov/arm/checks/PostgreSQLServerSSLEnforcementEnabled.py b/checkov/arm/checks/resource/PostgreSQLServerSSLEnforcementEnabled.py
similarity index 100%
rename from checkov/arm/checks/PostgreSQLServerSSLEnforcementEnabled.py
rename to checkov/arm/checks/resource/PostgreSQLServerSSLEnforcementEnabled.py
diff --git a/checkov/arm/checks/SQLServerAuditingEnabled.py b/checkov/arm/checks/resource/SQLServerAuditingEnabled.py
similarity index 100%
rename from checkov/arm/checks/SQLServerAuditingEnabled.py
rename to checkov/arm/checks/resource/SQLServerAuditingEnabled.py
diff --git a/checkov/arm/checks/SQLServerAuditingRetention90Days.py b/checkov/arm/checks/resource/SQLServerAuditingRetention90Days.py
similarity index 100%
rename from checkov/arm/checks/SQLServerAuditingRetention90Days.py
rename to checkov/arm/checks/resource/SQLServerAuditingRetention90Days.py
diff --git a/checkov/arm/checks/SQLServerEmailAlertsEnabled.py b/checkov/arm/checks/resource/SQLServerEmailAlertsEnabled.py
similarity index 100%
rename from checkov/arm/checks/SQLServerEmailAlertsEnabled.py
rename to checkov/arm/checks/resource/SQLServerEmailAlertsEnabled.py
diff --git a/checkov/arm/checks/SQLServerEmailAlertsToAdminsEnabled.py b/checkov/arm/checks/resource/SQLServerEmailAlertsToAdminsEnabled.py
similarity index 100%
rename from checkov/arm/checks/SQLServerEmailAlertsToAdminsEnabled.py
rename to checkov/arm/checks/resource/SQLServerEmailAlertsToAdminsEnabled.py
diff --git a/checkov/arm/checks/SQLServerNoPublicAccess.py b/checkov/arm/checks/resource/SQLServerNoPublicAccess.py
similarity index 81%
rename from checkov/arm/checks/SQLServerNoPublicAccess.py
rename to checkov/arm/checks/resource/SQLServerNoPublicAccess.py
index 6f5b64e04b..05e4d1e955 100644
--- a/checkov/arm/checks/SQLServerNoPublicAccess.py
+++ b/checkov/arm/checks/resource/SQLServerNoPublicAccess.py
@@ -23,8 +23,10 @@ def scan_resource_conf(self, conf):
resource["type"] == "firewallRules" or \
resource["type"] == "firewallrules":
if "properties" in resource:
- if "startIpAddress" in resource["properties"] and \
- resource["properties"]["startIpAddress"] in ['0.0.0.0', '0.0.0.0/0']: # nosec
+ if ("startIpAddress" in resource["properties"] and # nosec
+ resource["properties"]["startIpAddress"] in ['0.0.0.0', '0.0.0.0/0'] and
+ "endIpAddress" in resource["properties"] and
+ resource["properties"]["endIpAddress"] == '255.255.255.255'):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/arm/checks/SQLServerThreatDetectionTypes.py b/checkov/arm/checks/resource/SQLServerThreatDetectionTypes.py
similarity index 100%
rename from checkov/arm/checks/SQLServerThreatDetectionTypes.py
rename to checkov/arm/checks/resource/SQLServerThreatDetectionTypes.py
diff --git a/checkov/arm/checks/SecretExpirationDate.py b/checkov/arm/checks/resource/SecretExpirationDate.py
similarity index 100%
rename from checkov/arm/checks/SecretExpirationDate.py
rename to checkov/arm/checks/resource/SecretExpirationDate.py
diff --git a/checkov/arm/checks/SecurityCenterContactEmailAlert.py b/checkov/arm/checks/resource/SecurityCenterContactEmailAlert.py
similarity index 100%
rename from checkov/arm/checks/SecurityCenterContactEmailAlert.py
rename to checkov/arm/checks/resource/SecurityCenterContactEmailAlert.py
diff --git a/checkov/arm/checks/SecurityCenterContactEmailAlertAdmins.py b/checkov/arm/checks/resource/SecurityCenterContactEmailAlertAdmins.py
similarity index 100%
rename from checkov/arm/checks/SecurityCenterContactEmailAlertAdmins.py
rename to checkov/arm/checks/resource/SecurityCenterContactEmailAlertAdmins.py
diff --git a/checkov/arm/checks/SecurityCenterContactPhone.py b/checkov/arm/checks/resource/SecurityCenterContactPhone.py
similarity index 100%
rename from checkov/arm/checks/SecurityCenterContactPhone.py
rename to checkov/arm/checks/resource/SecurityCenterContactPhone.py
diff --git a/checkov/arm/checks/SecurityCenterStandardPricing.py b/checkov/arm/checks/resource/SecurityCenterStandardPricing.py
similarity index 100%
rename from checkov/arm/checks/SecurityCenterStandardPricing.py
rename to checkov/arm/checks/resource/SecurityCenterStandardPricing.py
diff --git a/checkov/arm/checks/StorageAccountAzureServicesAccessEnabled.py b/checkov/arm/checks/resource/StorageAccountAzureServicesAccessEnabled.py
similarity index 84%
rename from checkov/arm/checks/StorageAccountAzureServicesAccessEnabled.py
rename to checkov/arm/checks/resource/StorageAccountAzureServicesAccessEnabled.py
index fcef24e0c3..603729851e 100644
--- a/checkov/arm/checks/StorageAccountAzureServicesAccessEnabled.py
+++ b/checkov/arm/checks/resource/StorageAccountAzureServicesAccessEnabled.py
@@ -1,7 +1,8 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.arm.base_resource_check import BaseResourceCheck
-
+from checkov.common.models.enums import CheckResult, CheckCategories
# https://docs.microsoft.com/en-us/azure/templates/microsoft.storage/storageaccounts
+from checkov.common.util.type_forcers import force_int
+
class StorageAccountAzureServicesAccessEnabled(BaseResourceCheck):
def __init__(self):
@@ -16,8 +17,10 @@ def __init__(self):
def scan_resource_conf(self, conf):
if "apiVersion" in conf:
# Fail if apiVersion < 2017 as you could not set networkAcls
- year = int(conf["apiVersion"][0:4])
+ year = force_int(conf["apiVersion"][0:4])
+ if year is None:
+ return CheckResult.UNKNOWN # Should be handled by variable rendering
if year < 2017:
return CheckResult.FAILED
@@ -31,4 +34,5 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
return CheckResult.FAILED
-check = StorageAccountAzureServicesAccessEnabled()
\ No newline at end of file
+
+check = StorageAccountAzureServicesAccessEnabled()
diff --git a/checkov/arm/checks/StorageAccountDefaultNetworkAccessDeny.py b/checkov/arm/checks/resource/StorageAccountDefaultNetworkAccessDeny.py
similarity index 83%
rename from checkov/arm/checks/StorageAccountDefaultNetworkAccessDeny.py
rename to checkov/arm/checks/resource/StorageAccountDefaultNetworkAccessDeny.py
index 4d354843d7..3914e08203 100644
--- a/checkov/arm/checks/StorageAccountDefaultNetworkAccessDeny.py
+++ b/checkov/arm/checks/resource/StorageAccountDefaultNetworkAccessDeny.py
@@ -1,5 +1,7 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.arm.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_int
+
# https://docs.microsoft.com/en-us/azure/templates/microsoft.storage/storageaccounts
@@ -16,9 +18,11 @@ def __init__(self):
def scan_resource_conf(self, conf):
if "apiVersion" in conf:
# Fail if apiVersion < 2017 as you could not set networkAcls
- year = int(conf["apiVersion"][0:4])
+ year = force_int(conf["apiVersion"][0:4])
- if year < 2017:
+ if year is None:
+ return CheckResult.UNKNOWN
+ elif year < 2017:
return CheckResult.FAILED
if "properties" in conf:
@@ -28,4 +32,5 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
return CheckResult.FAILED
-check = StorageAccountDefaultNetworkAccessDeny()
\ No newline at end of file
+
+check = StorageAccountDefaultNetworkAccessDeny()
diff --git a/checkov/arm/checks/StorageAccountLoggingQueueServiceEnabled.py b/checkov/arm/checks/resource/StorageAccountLoggingQueueServiceEnabled.py
similarity index 100%
rename from checkov/arm/checks/StorageAccountLoggingQueueServiceEnabled.py
rename to checkov/arm/checks/resource/StorageAccountLoggingQueueServiceEnabled.py
diff --git a/checkov/arm/checks/StorageAccountsTransportEncryption.py b/checkov/arm/checks/resource/StorageAccountsTransportEncryption.py
similarity index 84%
rename from checkov/arm/checks/StorageAccountsTransportEncryption.py
rename to checkov/arm/checks/resource/StorageAccountsTransportEncryption.py
index 90a371817a..203021ef68 100644
--- a/checkov/arm/checks/StorageAccountsTransportEncryption.py
+++ b/checkov/arm/checks/resource/StorageAccountsTransportEncryption.py
@@ -1,5 +1,6 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.arm.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_int
class StorageAccountsTransportEncryption(BaseResourceCheck):
@@ -23,12 +24,15 @@ def scan_resource_conf(self, conf):
# Use default if supportsHttpsTrafficOnly is not set
if "apiVersion" in conf:
# Default for apiVersion 2019 and newer is supportsHttpsTrafficOnly = True
- year = int(conf["apiVersion"][0:4])
+ year = force_int(conf["apiVersion"][0:4])
- if year < 2019:
+ if year is None:
+ return CheckResult.UNKNOWN
+ elif year < 2019:
return CheckResult.FAILED
else:
return CheckResult.PASSED
return CheckResult.FAILED
-check = StorageAccountsTransportEncryption()
\ No newline at end of file
+
+check = StorageAccountsTransportEncryption()
diff --git a/checkov/arm/checks/StorageBlobServiceContainerPrivateAccess.py b/checkov/arm/checks/resource/StorageBlobServiceContainerPrivateAccess.py
similarity index 100%
rename from checkov/arm/checks/StorageBlobServiceContainerPrivateAccess.py
rename to checkov/arm/checks/resource/StorageBlobServiceContainerPrivateAccess.py
diff --git a/checkov/arm/checks/resource/__init__.py b/checkov/arm/checks/resource/__init__.py
new file mode 100644
index 0000000000..13632cb9e7
--- /dev/null
+++ b/checkov/arm/checks/resource/__init__.py
@@ -0,0 +1,4 @@
+from os.path import dirname, basename, isfile, join
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
diff --git a/checkov/arm/context_parser.py b/checkov/arm/context_parser.py
index 21b2a06c96..a65add1250 100644
--- a/checkov/arm/context_parser.py
+++ b/checkov/arm/context_parser.py
@@ -4,6 +4,9 @@
from functools import reduce
# COMMENT_REGEX = re.compile(r'(checkov:skip=) *([A-Z_\d]+)(:[^\n]+)?')
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.util.type_forcers import force_list
+
COMMENT_REGEX = re.compile(r'([A-Z_\d]+)(:[^\n]+)?')
@@ -117,29 +120,25 @@ def find_lines(node, kv):
@staticmethod
def collect_skip_comments(resource):
skipped_checks = []
+ bc_id_mapping = bc_integration.get_id_mapping()
+ ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
if "metadata" in resource:
if "checkov" in resource["metadata"]:
- if isinstance(resource["metadata"]["checkov"], list):
- for index, item in enumerate(resource["metadata"]["checkov"]):
- skip_search = re.search(COMMENT_REGEX, str(item))
- if skip_search:
- skipped_checks.append(
- {
- 'id': skip_search.group(1),
- 'suppress_comment': skip_search.group(2)[1:] if skip_search.group(
- 2) else "No comment provided"
- }
- )
- else:
- skip_search = re.search(COMMENT_REGEX, str(resource["metadata"]["checkov"]))
+ for index, item in enumerate(force_list(resource["metadata"]["checkov"])):
+ skip_search = re.search(COMMENT_REGEX, str(item))
if skip_search:
- skipped_checks.append(
- {
- 'id': skip_search.group(1),
- 'suppress_comment': skip_search.group(2)[1:] if skip_search.group(
- 2) else "No comment provided"
- }
- )
+ skipped_check = {
+ 'id': skip_search.group(1),
+ 'suppress_comment': skip_search.group(2)[1:] if skip_search.group(
+ 2) else "No comment provided"
+ }
+ if bc_id_mapping and skipped_check["id"] in bc_id_mapping:
+ skipped_check["bc_id"] = skipped_check["id"]
+ skipped_check["id"] = bc_id_mapping[skipped_check["id"]]
+ elif ckv_to_bc_id_mapping:
+ skipped_check["bc_id"] = ckv_to_bc_id_mapping.get(skipped_check["id"])
+
+ skipped_checks.append(skipped_check)
return skipped_checks
diff --git a/checkov/arm/registry.py b/checkov/arm/registry.py
index a3bdc9ab5b..c9efb60945 100644
--- a/checkov/arm/registry.py
+++ b/checkov/arm/registry.py
@@ -1,3 +1,4 @@
from checkov.arm.base_registry import Registry
-arm_registry = Registry()
+arm_resource_registry = Registry()
+arm_parameter_registry = Registry()
diff --git a/checkov/arm/runner.py b/checkov/arm/runner.py
index ee2ca7e72b..9634aed481 100644
--- a/checkov/arm/runner.py
+++ b/checkov/arm/runner.py
@@ -1,11 +1,11 @@
import logging
import os
-from checkov.arm.registry import arm_registry
+from checkov.arm.registry import arm_resource_registry, arm_parameter_registry
from checkov.arm.parser import parse
from checkov.common.output.record import Record
from checkov.common.output.report import Report
-from checkov.common.runners.base_runner import BaseRunner, filter_ignored_directories
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
from checkov.runner_filter import RunnerFilter
from checkov.arm.parser.node import dict_node
from checkov.arm.context_parser import ContextParser
@@ -24,7 +24,7 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
files_list = []
if external_checks_dir:
for directory in external_checks_dir:
- arm_registry.load_external_checks(directory, runner_filter)
+ arm_resource_registry.load_external_checks(directory)
if files:
for file in files:
@@ -32,7 +32,8 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if root_folder:
for root, d_names, f_names in os.walk(root_folder):
- filter_ignored_directories(d_names)
+ filter_ignored_paths(root, d_names, runner_filter.excluded_paths)
+ filter_ignored_paths(root, f_names, runner_filter.excluded_paths)
for file in f_names:
file_ending = os.path.splitext(file)[1]
if file_ending in ARM_POSSIBLE_ENDINGS:
@@ -59,46 +60,70 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
file_abs_path = os.path.abspath(path_to_convert)
- if isinstance(definitions[arm_file], dict_node) and 'resources' in definitions[arm_file].keys():
+ if isinstance(definitions[arm_file], dict_node):
arm_context_parser = ContextParser(arm_file, definitions[arm_file], definitions_raw[arm_file])
logging.debug("Template Dump for {}: {}".format(arm_file, definitions[arm_file], indent=2))
- arm_context_parser.evaluate_default_parameters()
-
- # Split out nested resources from base resource
- for resource in definitions[arm_file]['resources']:
- if "parent_name" in resource.keys():
- continue
- nested_resources = []
- nested_resources = arm_context_parser.search_deep_keys("resources", resource, [])
- if nested_resources:
- for nr in nested_resources:
- nr_element = nr.pop()
- if nr_element:
- for element in nr_element:
- new_resource = {}
- new_resource = element
- if isinstance(new_resource, dict):
- new_resource["parent_name"] = resource["name"]
- new_resource["parent_type"] = resource["type"]
- definitions[arm_file]['resources'].append(new_resource)
-
- for resource in definitions[arm_file]['resources']:
- resource_id = arm_context_parser.extract_arm_resource_id(resource)
- resource_name = arm_context_parser.extract_arm_resource_name(resource)
- entity_lines_range, entity_code_lines = arm_context_parser.extract_arm_resource_code_lines(resource)
- if entity_lines_range and entity_code_lines:
+
+ if 'resources' in definitions[arm_file].keys():
+ arm_context_parser.evaluate_default_parameters()
+
+ # Split out nested resources from base resource
+ for resource in definitions[arm_file]['resources']:
+ if isinstance(resource, dict) and "parent_name" in resource.keys():
+ continue
+ nested_resources = []
+ nested_resources = arm_context_parser.search_deep_keys("resources", resource, [])
+ if nested_resources:
+ for nr in nested_resources:
+ nr_element = nr.pop()
+ if nr_element:
+ for element in nr_element:
+ new_resource = {}
+ new_resource = element
+ if isinstance(new_resource, dict):
+ new_resource["parent_name"] = resource["name"]
+ new_resource["parent_type"] = resource["type"]
+ definitions[arm_file]['resources'].append(new_resource)
+
+ for resource in definitions[arm_file]['resources']:
+ resource_id = arm_context_parser.extract_arm_resource_id(resource)
+ resource_name = arm_context_parser.extract_arm_resource_name(resource)
+ entity_lines_range, entity_code_lines = arm_context_parser.extract_arm_resource_code_lines(resource)
+ if entity_lines_range and entity_code_lines:
+ # TODO - Variable Eval Message!
+ variable_evaluations = {}
+
+ skipped_checks = ContextParser.collect_skip_comments(resource)
+
+ results = arm_resource_registry.scan(arm_file, {resource_name: resource}, skipped_checks,
+ runner_filter)
+ for check, check_result in results.items():
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
+ code_block=entity_code_lines, file_path=arm_file,
+ file_line_range=entity_lines_range,
+ resource=resource_id, evaluations=variable_evaluations,
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ report.add_record(record=record)
+
+ if 'parameters' in definitions[arm_file].keys():
+ parameters = definitions[arm_file]['parameters']
+ for parameter_name, parameter_details in parameters.items():
# TODO - Variable Eval Message!
variable_evaluations = {}
- skipped_checks = ContextParser.collect_skip_comments(resource)
-
- results = arm_registry.scan(arm_file, {resource_name: resource}, skipped_checks,
- runner_filter)
- for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
- code_block=entity_code_lines, file_path=arm_file,
- file_line_range=entity_lines_range,
- resource=resource_id, evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
- report.add_record(record=record)
+ resource_id = f'parameter.{parameter_name}'
+ resource_name = parameter_name
+ entity_lines_range, entity_code_lines = arm_context_parser.extract_arm_resource_code_lines(parameter_details)
+
+ if entity_lines_range and entity_code_lines:
+ skipped_checks = ContextParser.collect_skip_comments(parameter_details)
+ results = arm_parameter_registry.scan(arm_file, {resource_name: parameter_details}, skipped_checks, runner_filter)
+ for check, check_result in results.items():
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
+ code_block=entity_code_lines, file_path=arm_file,
+ file_line_range=entity_lines_range,
+ resource=resource_id, evaluations=variable_evaluations,
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ report.add_record(record=record)
+
return report
diff --git a/checkov/cloudformation/cfn_utils.py b/checkov/cloudformation/cfn_utils.py
new file mode 100644
index 0000000000..0461dcb5dc
--- /dev/null
+++ b/checkov/cloudformation/cfn_utils.py
@@ -0,0 +1,210 @@
+import json
+import logging
+import os
+from typing import Optional, List, Tuple, Dict, Any, Union
+
+import dpath.util
+
+from checkov.cloudformation.checks.resource.base_registry import Registry
+from checkov.cloudformation.checks.resource.registry import cfn_registry
+from checkov.cloudformation.context_parser import ContextParser, ENDLINE, STARTLINE
+from checkov.cloudformation.parser import parse, TemplateSections
+from checkov.cloudformation.parser.node import dict_node, list_node, str_node
+from checkov.common.runners.base_runner import filter_ignored_paths
+from checkov.runner_filter import RunnerFilter
+from checkov.common.models.consts import YAML_COMMENT_MARK
+
+CF_POSSIBLE_ENDINGS = frozenset([".yml", ".yaml", ".json", ".template"])
+
+
+def get_resource_tags(entity: Dict[str_node, dict_node], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:
+ entity_details = registry.extract_entity_details(entity)
+
+ if not entity_details:
+ return None
+
+ entity_config = entity_details[-1]
+
+ if not isinstance(entity_config, dict):
+ return None
+
+ try:
+ properties = entity_config.get("Properties")
+ if properties:
+ tags = properties.get("Tags")
+ if tags:
+ return parse_entity_tags(tags)
+ except:
+ logging.warning(f"Failed to parse tags for entity {entity}")
+
+ return None
+
+
+def parse_entity_tags(tags: Union[list_node, Dict[str, Any]]) -> Optional[Dict[str, str]]:
+ if isinstance(tags, list_node):
+ tag_dict = {tag["Key"]: get_entity_value_as_string(tag["Value"]) for tag in tags}
+ return tag_dict
+ elif isinstance(tags, dict):
+ tag_dict = {
+ str(key): get_entity_value_as_string(value)
+ for key, value in tags.items()
+ if key not in (STARTLINE, ENDLINE)
+ }
+ return tag_dict
+ return None
+
+
+def get_entity_value_as_string(value: Any) -> str:
+ """
+ Handles different type of entities with possible CFN function substitutions. Returns the simplest possible string value
+ (without performing any function calls).
+
+ Examples:
+ Key: Value # returns simple string
+
+ Key: !Ref ${AWS::AccountId}-data # returns ${AWS::AccountId}-data
+
+ Key:
+ - ${account}-data
+ - account: !Ref ${AWS::AccountId}
+
+ # returns ${account}-data
+
+ :param value:
+ :return:
+ """
+ if isinstance(value, dict):
+ (function, value) = next(iter(value.items()))
+ # If the value is a long-form function, then the first element is the template string (technically str_node)
+ # Otherwise the dict value is the template string
+ if isinstance(value, list):
+ if "Join" in function:
+ # Join looks like !Join [, [V1, V2, V3]]
+ join_str = str(value[0])
+ return join_str.join([str(v) for v in value[1]])
+ else:
+ return str(value[0])
+ else:
+ return str(value)
+ else:
+ return str(value)
+
+
+def get_folder_definitions(
+ root_folder: str, excluded_paths: Optional[List[str]]
+) -> Tuple[Dict[str, dict_node], Dict[str, List[Tuple[int, str]]]]:
+ files_list = []
+ for root, d_names, f_names in os.walk(root_folder):
+ filter_ignored_paths(root, d_names, excluded_paths)
+ filter_ignored_paths(root, f_names, excluded_paths)
+ for file in f_names:
+ file_ending = os.path.splitext(file)[1]
+ if file_ending in CF_POSSIBLE_ENDINGS:
+ files_list.append(os.path.join(root, file))
+
+ definitions: Dict[str, dict_node] = {}
+ definitions_raw: Dict[str, List[Tuple[int, str]]] = {}
+ for file in files_list:
+ relative_file_path = f"/{os.path.relpath(file, os.path.commonprefix((root_folder, file)))}"
+ try:
+ template, template_lines = parse(file)
+ if isinstance(template, dict_node) and isinstance(template.get("Resources"), dict_node):
+ definitions[relative_file_path] = template
+ definitions_raw[relative_file_path] = template_lines
+ else:
+ logging.debug(f"Parsed file {file} incorrectly {template}")
+ except (TypeError, ValueError) as e:
+ logging.warning(f"CloudFormation skipping {file} as it is not a valid CF template\n{e}")
+ continue
+
+ definitions = {create_file_abs_path(root_folder, file_path): v for (file_path, v) in definitions.items()}
+ definitions_raw = {create_file_abs_path(root_folder, file_path): v for (file_path, v) in definitions_raw.items()}
+
+ return definitions, definitions_raw
+
+
+def build_definitions_context(
+ definitions: Dict[str, dict_node], definitions_raw: Dict[str, List[Tuple[int, str]]], root_folder: str
+) -> Dict[str, Dict[str, Any]]:
+ definitions_context: Dict[str, Dict[str, Any]] = {}
+ # iterate on the files
+ for file_path, file_path_definitions in definitions.items():
+ # iterate on the definitions (Parameters, Resources, Outputs...)
+ for file_path_definition, definition in file_path_definitions.items():
+ if (
+ isinstance(file_path_definition, str_node)
+ and file_path_definition.upper() in TemplateSections.__members__
+ and isinstance(definition, dict_node)
+ ):
+ # iterate on the actual objects of each definition
+ for attribute, attr_value in definition.items():
+ if isinstance(attr_value, dict_node):
+ start_line = attr_value.start_mark.line
+ end_line = attr_value.end_mark.line
+ # fix lines number for yaml and json files
+ first_line_index = 0
+ while not str.strip(definitions_raw[file_path][first_line_index][1]):
+ first_line_index += 1
+ # check if the file is a json file
+ if str.strip(definitions_raw[file_path][first_line_index][1])[0] == "{":
+ start_line += 1
+ end_line += 1
+ else:
+ current_line = str.strip(definitions_raw[file_path][start_line - 1][1])
+ while not current_line or current_line[0] == YAML_COMMENT_MARK:
+ start_line -= 1
+ current_line = str.strip(definitions_raw[file_path][start_line - 1][1])
+ current_line = str.strip(definitions_raw[file_path][end_line - 1][1])
+ while not current_line or current_line[0] == YAML_COMMENT_MARK:
+ end_line -= 1
+ current_line = str.strip(definitions_raw[file_path][end_line - 1][1])
+
+ code_lines = definitions_raw[file_path][start_line - 1 : end_line]
+ dpath.new(
+ definitions_context,
+ [file_path, str(file_path_definition), str(attribute)],
+ {"start_line": start_line, "end_line": end_line, "code_lines": code_lines},
+ )
+ if file_path_definition.upper() == TemplateSections.RESOURCES.value.upper():
+ skipped_checks = ContextParser.collect_skip_comments(code_lines)
+ dpath.new(
+ definitions_context,
+ [file_path, str(file_path_definition), str(attribute), "skipped_checks"],
+ skipped_checks,
+ )
+ return definitions_context
+
+
+def create_file_abs_path(root_folder: str, cf_file: str) -> str:
+ # There are a few cases here. If -f was used, there could be a leading / because it's an absolute path,
+ # or there will be no leading slash; root_folder will always be none.
+ # If -d is used, root_folder will be the value given, and -f will start with a / (hardcoded above).
+ # The goal here is simply to get a valid path to the file (which cf_file does not always give).
+ if cf_file.startswith("/"):
+ path_to_convert = (root_folder + cf_file) if root_folder else cf_file
+ else:
+ path_to_convert = (os.path.join(root_folder, cf_file)) if root_folder else cf_file
+
+ return os.path.abspath(path_to_convert)
+
+
+def create_definitions(
+ root_folder: str, files: Optional[List[str]] = None, runner_filter: RunnerFilter = RunnerFilter()
+) -> Tuple[Dict[str, dict_node], Dict[str, List[Tuple[int, str]]]]:
+ definitions = {}
+ definitions_raw = {}
+ if files:
+ for file in files:
+ (definitions[file], definitions_raw[file]) = parse(file)
+
+ if root_folder:
+ definitions, definitions_raw = get_folder_definitions(root_folder, runner_filter.excluded_paths)
+
+ # Filter out empty files that have not been parsed successfully, and filter out non-CF template files
+ definitions = {
+ k: v
+ for k, v in definitions.items()
+ if v and isinstance(v, dict_node) and v.__contains__("Resources") and isinstance(v["Resources"], dict_node)
+ }
+ definitions_raw = {k: v for k, v in definitions_raw.items() if k in definitions.keys()}
+ return definitions, definitions_raw
diff --git a/checkov/terraform/checks/graph/__init__.py b/checkov/cloudformation/checks/graph_checks/__init__.py
similarity index 100%
rename from checkov/terraform/checks/graph/__init__.py
rename to checkov/cloudformation/checks/graph_checks/__init__.py
diff --git a/checkov/cloudformation/checks/graph_checks/aws/LambdaFunction.yaml b/checkov/cloudformation/checks/graph_checks/aws/LambdaFunction.yaml
new file mode 100644
index 0000000000..7406e41f6b
--- /dev/null
+++ b/checkov/cloudformation/checks/graph_checks/aws/LambdaFunction.yaml
@@ -0,0 +1,13 @@
+metadata:
+ id: "CKV2_AWS_26"
+ name: "X-ray tracing is enabled for Lambda"
+ category: "LOGGING"
+definition:
+ cond_type: attribute
+ attribute: Tracing_config.Mode
+ value:
+ - "PassThrough"
+ - "Active"
+ operator: within
+ resource_types:
+ - AWS::Lambda::Function
\ No newline at end of file
diff --git a/checkov/cloudformation/checks/graph_checks/aws/MSKClusterLogging.yaml b/checkov/cloudformation/checks/graph_checks/aws/MSKClusterLogging.yaml
new file mode 100644
index 0000000000..b34d86af9e
--- /dev/null
+++ b/checkov/cloudformation/checks/graph_checks/aws/MSKClusterLogging.yaml
@@ -0,0 +1,24 @@
+metadata:
+ id: "CKV2_AWS_25"
+ name: "Ensure MSK Cluster logging is enabled"
+ category: "LOGGING"
+definition:
+ or:
+ - cond_type: attribute
+ attribute: LoggingInfo.BrokerLogs.S3.Enabled
+ operator: equals
+ value: true
+ resource_types:
+ - "AWS::MSK::Cluster"
+ - cond_type: attribute
+ attribute: LoggingInfo.BrokerLogs.Firehose.Enabled
+ operator: equals
+ value: true
+ resource_types:
+ - "AWS::MSK::Cluster"
+ - cond_type: attribute
+ attribute: LoggingInfo.BrokerLogs.CloudWatchLogs.Enabled
+ operator: equals
+ value: true
+ resource_types:
+ - "AWS::MSK::Cluster"
diff --git a/checkov/cloudformation/checks/graph_checks/aws/SagemakerNotebookEncryption.yaml b/checkov/cloudformation/checks/graph_checks/aws/SagemakerNotebookEncryption.yaml
new file mode 100644
index 0000000000..6c7a7db569
--- /dev/null
+++ b/checkov/cloudformation/checks/graph_checks/aws/SagemakerNotebookEncryption.yaml
@@ -0,0 +1,10 @@
+metadata:
+ id: "CKV2_AWS_24"
+ name: "Ensure SageMaker Notebook is encrypted at rest using KMS CMK"
+ category: "ENCRYPTION"
+definition:
+ cond_type: attribute
+ attribute: KmsKeyId
+ operator: exists
+ resource_types:
+ - AWS::SageMaker::NotebookInstance
\ No newline at end of file
diff --git a/checkov/cloudformation/checks/resource/BaseCloudsplainingIAMCheck.py b/checkov/cloudformation/checks/resource/BaseCloudsplainingIAMCheck.py
new file mode 100644
index 0000000000..ca8c436d12
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/BaseCloudsplainingIAMCheck.py
@@ -0,0 +1,54 @@
+import json
+import logging
+from abc import abstractmethod
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.multi_signature import multi_signature
+from checkov.cloudformation.checks.utils.iam_cloudformation_document_to_policy_converter import \
+ convert_cloudformation_conf_to_iam_policy
+
+
+class BaseCloudsplainingIAMCheck(BaseResourceCheck):
+ def __init__(self, name, id):
+ super().__init__(name=name, id=id, categories=[CheckCategories.IAM],
+ supported_resources=["AWS::IAM::Policy", "AWS::IAM::ManagedPolicy", "AWS::IAM::Group",
+ "AWS::IAM::Role", "AWS::IAM::User"])
+
+ def scan_resource_conf(self, conf):
+ if conf.get('Properties'):
+ props_conf = conf['Properties']
+ policies_key = 'Policies'
+
+ # Obtain a list of 1 or more policies regardless of resource schema
+ if policies_key in props_conf.keys():
+ policy_conf = props_conf[policies_key]
+ else:
+ policy_conf = [props_conf]
+
+ # Scan all policies
+ for policy in policy_conf:
+ policy_doc_key = 'PolicyDocument'
+ if isinstance(policy, dict) and policy_doc_key in policy.keys():
+ # When using unresolved Cfn functions, policy is an str
+ policy_doc = policy[policy_doc_key]
+ try:
+ converted_policy_doc = convert_cloudformation_conf_to_iam_policy(policy_doc)
+ statement_key = 'Statement'
+ if statement_key in converted_policy_doc:
+ policy_statement = PolicyDocument(converted_policy_doc)
+ violations = self.cloudsplaining_analysis(policy_statement)
+ if violations:
+ logging.debug("detailed cloudsplainging finding: {}",json.dumps(violations))
+ return CheckResult.FAILED
+ except Exception as e:
+ # this might occur with templated iam policies where ARN is not in place or similar
+ logging.debug("could not run cloudsplaining analysis on policy {}", conf)
+ return CheckResult.UNKNOWN
+ return CheckResult.PASSED
+
+ @multi_signature()
+ @abstractmethod
+ def cloudsplaining_analysis(self, policy):
+ raise NotImplementedError()
diff --git a/checkov/cloudformation/checks/resource/aws/ALBDropHttpHeaders.py b/checkov/cloudformation/checks/resource/aws/ALBDropHttpHeaders.py
new file mode 100644
index 0000000000..1fef771e1a
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ALBDropHttpHeaders.py
@@ -0,0 +1,38 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class ALBDropHttpHeaders(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure that ALB drops HTTP headers"
+ id = "CKV_AWS_131"
+ supported_resources = ["AWS::ElasticLoadBalancingV2::LoadBalancer"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ # ALB is default loadbalancer type if not explicitly set
+ alb = True
+
+ properties = conf.get("Properties")
+ lb_type = properties.get("Type")
+ if lb_type != None and lb_type != 'application':
+ alb = False
+
+ # If lb is alb then drop headers must be present and true
+ if alb:
+ lb_attributes = properties.get('LoadBalancerAttributes', {})
+ if isinstance(lb_attributes, list):
+ for item in lb_attributes:
+ key = item.get('Key')
+ value = item.get('Value')
+ if key == 'routing.http.drop_invalid_header_fields.enabled' and value == "true":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ # If lb is not alb then check is not valid
+ return CheckResult.UNKNOWN
+
+
+check = ALBDropHttpHeaders()
diff --git a/checkov/cloudformation/checks/resource/aws/ALBListenerHTTPS.py b/checkov/cloudformation/checks/resource/aws/ALBListenerHTTPS.py
index 1034c9cfe4..22178a4dc5 100644
--- a/checkov/cloudformation/checks/resource/aws/ALBListenerHTTPS.py
+++ b/checkov/cloudformation/checks/resource/aws/ALBListenerHTTPS.py
@@ -26,9 +26,9 @@ def scan_resource_conf(self, conf):
if (
'DefaultActions' in conf['Properties'].keys()
and
- conf['Properties']['DefaultActions'][0]['Type'] == 'redirect'
+ conf['Properties']['DefaultActions'][0].get('Type') == 'redirect'
and
- conf['Properties']['DefaultActions'][0]['RedirectConfig']['Protocol'] == "HTTPS"
+ conf['Properties']['DefaultActions'][0].get('RedirectConfig', {}).get('Protocol') == "HTTPS"
):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/cloudformation/checks/resource/aws/APIGatewayAuthorization.py b/checkov/cloudformation/checks/resource/aws/APIGatewayAuthorization.py
index 424e562632..50d9c58c0b 100644
--- a/checkov/cloudformation/checks/resource/aws/APIGatewayAuthorization.py
+++ b/checkov/cloudformation/checks/resource/aws/APIGatewayAuthorization.py
@@ -14,7 +14,8 @@ def scan_resource_conf(self, conf):
if 'Properties' in conf.keys():
if 'HttpMethod' in conf['Properties'].keys() and 'AuthorizationType' in conf['Properties'].keys():
if conf['Properties']['HttpMethod'] != "OPTIONS" and conf['Properties']['AuthorizationType'] == "NONE":
- return CheckResult.FAILED
+ if 'ApiKeyRequired' not in conf['Properties'].keys() or conf['Properties']['ApiKeyRequired'] == False:
+ return CheckResult.FAILED
return CheckResult.PASSED
check = APIGatewayAuthorization()
diff --git a/checkov/cloudformation/checks/resource/aws/APIGatewayCacheEnable.py b/checkov/cloudformation/checks/resource/aws/APIGatewayCacheEnable.py
new file mode 100644
index 0000000000..08befdc774
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/APIGatewayCacheEnable.py
@@ -0,0 +1,19 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class APIGatewayCacheEnable(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure API Gateway caching is enabled"
+ id = "CKV_AWS_120"
+ supported_resources = ['AWS::ApiGateway::Stage']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/CacheClusterEnabled'
+
+
+check = APIGatewayCacheEnable()
diff --git a/checkov/cloudformation/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py b/checkov/cloudformation/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
index f1ca29942f..de9f099b11 100644
--- a/checkov/cloudformation/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
+++ b/checkov/cloudformation/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
@@ -33,6 +33,6 @@ def scan_resource_conf(self, conf):
if int(rule['FromPort']) == int(self.port) and int(rule['ToPort']) == int(self.port):
if 'CidrIp' in rule.keys() and rule['CidrIp'] == '0.0.0.0/0': # nosec # nosec
return CheckResult.FAILED
- elif 'CidrIpv6' in rule.keys() and rule['CidrIpv6'] == '::/0':
+ elif 'CidrIpv6' in rule.keys() and rule['CidrIpv6'] in ['::/0', '0000:0000:0000:0000:0000:0000:0000:0000/0']:
return CheckResult.FAILED
return CheckResult.PASSED
\ No newline at end of file
diff --git a/checkov/cloudformation/checks/resource/aws/AmazonMQBrokerPublicAccess.py b/checkov/cloudformation/checks/resource/aws/AmazonMQBrokerPublicAccess.py
new file mode 100644
index 0000000000..38b083bc93
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/AmazonMQBrokerPublicAccess.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+class AmazonMQBrokerPublicAccess(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure Amazon MQ Broker should not have public access"
+ id = "CKV_AWS_69"
+ supported_resources = ['AWS::AmazonMQ::Broker']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, missing_block_result=CheckResult.FAILED)
+
+ def get_expected_value(self):
+ return False
+
+ def get_inspected_key(self):
+ """
+ validates Amazon MQ Broker should not have public access
+ https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html
+ :return:
+ """
+ return 'Properties/PubliclyAccessible'
+
+
+check = AmazonMQBrokerPublicAccess()
diff --git a/checkov/cloudformation/checks/resource/aws/BackupVaultEncrypted.py b/checkov/cloudformation/checks/resource/aws/BackupVaultEncrypted.py
new file mode 100644
index 0000000000..51ba281dd0
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/BackupVaultEncrypted.py
@@ -0,0 +1,21 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class BackupVaultEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Backup Vault is encrypted at rest using KMS CMK"
+ id = "CKV_AWS_166"
+ supported_resources = ['AWS::Backup::BackupVault']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/EncryptionKeyArn'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = BackupVaultEncrypted()
diff --git a/checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupKMSKey.py b/checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupKMSKey.py
new file mode 100644
index 0000000000..a9b8bb9a60
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupKMSKey.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class CloudWatchLogGroupKMSKey(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that CloudWatch Log Group is encrypted by KMS"
+ id = "CKV_AWS_158"
+ supported_resource = ['AWS::Logs::LogGroup']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resource)
+
+ def get_inspected_key(self):
+ return 'Properties/KmsKeyId'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = CloudWatchLogGroupKMSKey()
diff --git a/checkov/cloudformation/checks/resource/aws/cloudwatchLogGroupRetention.py b/checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupRetention.py
similarity index 77%
rename from checkov/cloudformation/checks/resource/aws/cloudwatchLogGroupRetention.py
rename to checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupRetention.py
index 70d4d9070c..21c3a9bcd8 100644
--- a/checkov/cloudformation/checks/resource/aws/cloudwatchLogGroupRetention.py
+++ b/checkov/cloudformation/checks/resource/aws/CloudWatchLogGroupRetention.py
@@ -3,9 +3,9 @@
from checkov.common.models.consts import ANY_VALUE
-class cloudwatchLogGroupRetention(BaseResourceValueCheck):
+class CloudWatchLogGroupRetention(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure cloudwatch log groups specify retention days"
+ name = "Ensure that CloudWatch Log Group specifies retention days"
id = "CKV_AWS_66"
supported_resource = ['AWS::Logs::LogGroup']
categories = [CheckCategories.LOGGING]
@@ -18,4 +18,4 @@ def get_expected_value(self):
return ANY_VALUE
-check = cloudwatchLogGroupRetention()
+check = CloudWatchLogGroupRetention()
diff --git a/checkov/cloudformation/checks/resource/aws/DMSReplicationInstancePubliclyAccessible.py b/checkov/cloudformation/checks/resource/aws/DMSReplicationInstancePubliclyAccessible.py
new file mode 100644
index 0000000000..efcb7ec0c2
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/DMSReplicationInstancePubliclyAccessible.py
@@ -0,0 +1,19 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class DMSReplicationInstancePubliclyAccessible(BaseResourceValueCheck):
+ def __init__(self):
+ name = "DMS replication instance should not be publicly accessible"
+ id = "CKV_AWS_89"
+ supported_resources = ['AWS::DMS::ReplicationInstance']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/PubliclyAccessible'
+
+ def get_expected_value(self):
+ return False
+
+check = DMSReplicationInstancePubliclyAccessible()
diff --git a/checkov/cloudformation/checks/resource/aws/DocDBAuditLogs.py b/checkov/cloudformation/checks/resource/aws/DocDBAuditLogs.py
new file mode 100644
index 0000000000..2ca3a100de
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/DocDBAuditLogs.py
@@ -0,0 +1,23 @@
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class DocDBAuditLogs(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure DocDB has audit logs enabled"
+ id = "CKV_AWS_104"
+ supported_resources = ["AWS::DocDB::DBClusterParameterGroup"]
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: dict_node) -> CheckResult:
+ params = conf.get("Properties", {}).get("Parameters", {})
+
+ if params.get("audit_logs") == "enabled":
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = DocDBAuditLogs()
diff --git a/checkov/cloudformation/checks/resource/aws/DynamodbGlobalTableRecovery.py b/checkov/cloudformation/checks/resource/aws/DynamodbGlobalTableRecovery.py
new file mode 100644
index 0000000000..b4a3a09b5d
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/DynamodbGlobalTableRecovery.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class DynamodbGlobalTableRecovery(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Dynamodb global table point in time recovery (backup) is enabled"
+ id = "CKV_AWS_165"
+ supported_resources = ['AWS::DynamoDB::GlobalTable']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/Replicas/[0]/PointInTimeRecoverySpecification/PointInTimeRecoveryEnabled'
+
+
+check = DynamodbGlobalTableRecovery()
diff --git a/checkov/cloudformation/checks/resource/aws/EC2Credentials.py b/checkov/cloudformation/checks/resource/aws/EC2Credentials.py
new file mode 100644
index 0000000000..34c5f2c6ae
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/EC2Credentials.py
@@ -0,0 +1,26 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.secrets import string_has_secrets, AWS
+
+class EC2Credentials(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure no hard-coded secrets exist in EC2 user data"
+ id = "CKV_AWS_46"
+ supported_resources = ['AWS::EC2::Instance']
+ categories = [CheckCategories.SECRETS]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ if 'UserData' in conf['Properties'].keys():
+ user_data = conf['Properties']['UserData']
+ # Cast to string as user data object can look slightly different depending
+ # on Yaml or JSON CF Templates and how the B64 conversion is done.
+ user_data_str = str(user_data)
+ if isinstance(user_data_str, str):
+ if string_has_secrets(user_data_str):
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = EC2Credentials()
diff --git a/checkov/cloudformation/checks/resource/aws/EC2PublicIP.py b/checkov/cloudformation/checks/resource/aws/EC2PublicIP.py
new file mode 100644
index 0000000000..829986c0b8
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/EC2PublicIP.py
@@ -0,0 +1,41 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+class EC2PublicIP(BaseResourceCheck):
+ def __init__(self):
+ name = "EC2 instance should not have public IP."
+ id = "CKV_AWS_88"
+ supported_resources = ['AWS::EC2::Instance', 'AWS::EC2::LaunchTemplate']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ # For AWS::EC2::Instance
+ if 'NetworkInterfaces' in conf['Properties'].keys():
+ network_interfaces = conf['Properties']['NetworkInterfaces']
+ if isinstance(network_interfaces, list):
+ for network_interface in network_interfaces:
+ if 'AssociatePublicIpAddress' in network_interface.keys():
+ if network_interface['AssociatePublicIpAddress'] is True:
+ return CheckResult.FAILED
+ else:
+ # If not made explicit then default is true if default subnet and false otherwise.
+ # This info can not be derived from template so result is unknown.
+ # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html#Properties%23AssociatePublicIpAddress
+ return CheckResult.UNKNOWN
+ # For 'AWS::EC2::LaunchTemplate'
+ if 'LaunchTemplateData' in conf['Properties'].keys():
+ if 'NetworkInterfaces' in conf['Properties']['LaunchTemplateData'].keys():
+ network_interfaces = conf['Properties']['LaunchTemplateData']['NetworkInterfaces']
+ if isinstance(network_interfaces, list):
+ for network_interface in network_interfaces:
+ if 'AssociatePublicIpAddress' in network_interface.keys():
+ if network_interface['AssociatePublicIpAddress'] is True:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.UNKNOWN
+ return CheckResult.PASSED
+
+
+check = EC2PublicIP()
diff --git a/checkov/cloudformation/checks/resource/aws/ECRImageScanning.py b/checkov/cloudformation/checks/resource/aws/ECRImageScanning.py
index 703884d003..100e58d7ce 100644
--- a/checkov/cloudformation/checks/resource/aws/ECRImageScanning.py
+++ b/checkov/cloudformation/checks/resource/aws/ECRImageScanning.py
@@ -1,4 +1,17 @@
-#############################################################################
-### CloudFormation Currently (Feb 2020) does not support ECR Image Scanning #
-### https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/245
-#############################################################################
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ECRImageScanning(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure ECR image scanning on push is enabled"
+ id = "CKV_AWS_163"
+ supported_resources = ["AWS::ECR::Repository"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "Properties/ImageScanningConfiguration/ScanOnPush"
+
+
+check = ECRImageScanning()
diff --git a/checkov/cloudformation/checks/resource/aws/ECRPolicy.py b/checkov/cloudformation/checks/resource/aws/ECRPolicy.py
index bc1363a4f4..1cb6efb7f8 100644
--- a/checkov/cloudformation/checks/resource/aws/ECRPolicy.py
+++ b/checkov/cloudformation/checks/resource/aws/ECRPolicy.py
@@ -1,3 +1,6 @@
+import json
+
+from checkov.cloudformation.parser.node import str_node
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
@@ -19,8 +22,11 @@ def scan_resource_conf(self, conf):
"""
if 'Properties' in conf.keys():
if 'RepositoryPolicyText' in conf['Properties'].keys():
- if 'Statement' in conf['Properties']['RepositoryPolicyText'].keys():
- for statement in conf['Properties']['RepositoryPolicyText']['Statement']:
+ policy_text = conf['Properties']['RepositoryPolicyText']
+ if type(policy_text) in (str, str_node):
+ policy_text = json.loads(str(policy_text))
+ if 'Statement' in policy_text.keys():
+ for statement in policy_text['Statement']:
if 'Principal' in statement.keys():
for principal in statement['Principal']:
if principal == "*":
diff --git a/checkov/cloudformation/checks/resource/aws/ECRRepositoryEncrypted.py b/checkov/cloudformation/checks/resource/aws/ECRRepositoryEncrypted.py
new file mode 100644
index 0000000000..bd4755d855
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ECRRepositoryEncrypted.py
@@ -0,0 +1,19 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ECRRepositoryEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that ECR repositories are encrypted using KMS"
+ id = "CKV_AWS_136"
+ supported_resources = ["AWS::ECR::Repository"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "Properties/EncryptionConfiguration/EncryptionType"
+
+ def get_expected_value(self):
+ return "KMS"
+
+check = ECRRepositoryEncrypted()
diff --git a/checkov/cloudformation/checks/resource/aws/ECSClusterContainerInsights.py b/checkov/cloudformation/checks/resource/aws/ECSClusterContainerInsights.py
index 683db2a89a..7e7484f4d8 100644
--- a/checkov/cloudformation/checks/resource/aws/ECSClusterContainerInsights.py
+++ b/checkov/cloudformation/checks/resource/aws/ECSClusterContainerInsights.py
@@ -17,8 +17,8 @@ def scan_resource_conf(self, conf):
:param conf: AWS::ECS::Cluster configuration
:return:
"""
- if 'Properties' in conf.keys():
- if 'ClusterSettings' in conf['Properties'].keys():
+ if conf.get('Properties'):
+ if conf['Properties'].get('ClusterSettings'):
for setting in conf['Properties']['ClusterSettings']:
if setting['Name'] == 'containerInsights' and setting['Value'] == 'enabled':
return CheckResult.PASSED
diff --git a/checkov/cloudformation/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py b/checkov/cloudformation/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py
new file mode 100644
index 0000000000..d11be57648
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class ECSTaskDefinitionEFSVolumeEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions"
+ id = "CKV_AWS_97"
+ supported_resources = ['AWS::ECS::TaskDefinition']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ if 'Volumes' in conf['Properties'].keys():
+ volumes = conf['Properties']['Volumes']
+ if isinstance(volumes, list):
+ for volume in volumes:
+ if 'EFSVolumeConfiguration' in volume.keys():
+ if 'TransitEncryption' in volume['EFSVolumeConfiguration'].keys():
+ if volume['EFSVolumeConfiguration']['TransitEncryption'] == 'ENABLED':
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ECSTaskDefinitionEFSVolumeEncryption()
diff --git a/checkov/cloudformation/checks/resource/aws/EKSNodeGroupRemoteAccess.py b/checkov/cloudformation/checks/resource/aws/EKSNodeGroupRemoteAccess.py
new file mode 100644
index 0000000000..47e40b9446
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/EKSNodeGroupRemoteAccess.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+class EKSNodeGroupRemoteAccess(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0"
+ id = "CKV_AWS_100"
+ supported_resources = ['AWS::EKS::Nodegroup']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ if 'RemoteAccess' in conf['Properties'].keys():
+ if 'Ec2SshKey' in conf['Properties']['RemoteAccess'].keys():
+ if 'SourceSecurityGroups' in conf['Properties']['RemoteAccess'].keys():
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = EKSNodeGroupRemoteAccess()
diff --git a/checkov/cloudformation/checks/resource/aws/EKSSecretsEncryption.py b/checkov/cloudformation/checks/resource/aws/EKSSecretsEncryption.py
index 0ae0ba4a01..f75238d645 100644
--- a/checkov/cloudformation/checks/resource/aws/EKSSecretsEncryption.py
+++ b/checkov/cloudformation/checks/resource/aws/EKSSecretsEncryption.py
@@ -17,8 +17,9 @@ def scan_resource_conf(self, conf):
:param conf: AWS::EKS::Cluster configuration
:return:
"""
- if 'EncryptionConfig' in conf['Properties'].keys() and 'Resources' in conf['Properties']['EncryptionConfig'][0] \
- and 'secrets' in conf['Properties']['EncryptionConfig'][0]['Resources']:
+ encryption_config = list(conf.get('Properties', {}).get('EncryptionConfig', []))
+ encryption_config_resources = [p["Resources"] for p in encryption_config if "Resources" in p]
+ if isinstance(encryption_config_resources, list) and any('secrets' in r for r in encryption_config_resources):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/cloudformation/checks/resource/aws/ELBAccessLogs.py b/checkov/cloudformation/checks/resource/aws/ELBAccessLogs.py
new file mode 100644
index 0000000000..0e0cbf9678
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ELBAccessLogs.py
@@ -0,0 +1,17 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class ELBAccessLogs(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure the ELB has access logging enabled"
+ id = "CKV_AWS_92"
+ supported_resources = ['AWS::ElasticLoadBalancing::LoadBalancer']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/AccessLoggingPolicy/Enabled'
+
+
+check = ELBAccessLogs()
diff --git a/checkov/cloudformation/checks/resource/aws/ELBv2AccessLogs.py b/checkov/cloudformation/checks/resource/aws/ELBv2AccessLogs.py
new file mode 100644
index 0000000000..d50b63e488
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ELBv2AccessLogs.py
@@ -0,0 +1,24 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class ELBv2AccessLogs(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure the ELBv2 (Application/Network) has access logging enabled"
+ id = "CKV_AWS_91"
+ supported_resources = ['AWS::ElasticLoadBalancingV2::LoadBalancer']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ if 'LoadBalancerAttributes' in conf['Properties'].keys():
+ if isinstance(conf['Properties']['LoadBalancerAttributes'], list):
+ for item in conf['Properties']['LoadBalancerAttributes']:
+ if 'Key' in item.keys() and 'Value' in item.keys():
+ if item['Key'] == "access_logs.s3.enabled" and item['Value'] == "true":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = ELBv2AccessLogs()
diff --git a/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainEnforceHTTPS.py b/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainEnforceHTTPS.py
new file mode 100644
index 0000000000..d73dd515f8
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainEnforceHTTPS.py
@@ -0,0 +1,16 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ElasticsearchDomainEnforceHTTPS(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Elasticsearch Domain enforces HTTPS"
+ id = "CKV_AWS_83"
+ supported_resources = ['AWS::Elasticsearch::Domain']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/DomainEndpointOptions/EnforceHTTPS'
+
+check = ElasticsearchDomainEnforceHTTPS()
diff --git a/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainLogging.py b/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainLogging.py
new file mode 100644
index 0000000000..8c12f2ea38
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/ElasticsearchDomainLogging.py
@@ -0,0 +1,16 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ElasticsearchDomainLogging(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Elasticsearch Domain Logging is enabled"
+ id = "CKV_AWS_84"
+ supported_resources = ['AWS::Elasticsearch::Domain']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/LogPublishingOptions/AUDIT_LOGS/Enabled'
+
+check = ElasticsearchDomainLogging()
diff --git a/checkov/cloudformation/checks/resource/aws/GlobalAcceleratorAcceleratorFlowLogs.py b/checkov/cloudformation/checks/resource/aws/GlobalAcceleratorAcceleratorFlowLogs.py
new file mode 100644
index 0000000000..2fedd627e2
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/GlobalAcceleratorAcceleratorFlowLogs.py
@@ -0,0 +1,4 @@
+####################################################################################################################
+## CloudFormation (Mar 2021) doesn't support GlobalAccelerator Attributes Configuration ##
+## https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html ##
+####################################################################################################################
diff --git a/checkov/cloudformation/checks/resource/aws/GlueDataCatalogEncryption.py b/checkov/cloudformation/checks/resource/aws/GlueDataCatalogEncryption.py
new file mode 100644
index 0000000000..588707948e
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/GlueDataCatalogEncryption.py
@@ -0,0 +1,37 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class GlueDataCatalogEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Glue Data Catalog Encryption is enabled"
+ id = "CKV_AWS_94"
+ supported_resources = ['AWS::Glue::DataCatalogEncryptionSettings']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ connection_encrypted = False
+ encrypted_at_rest = False
+ if 'Properties' in conf.keys():
+ if 'DataCatalogEncryptionSettings' in conf['Properties'].keys():
+ dc_enc_settings = conf['Properties']['DataCatalogEncryptionSettings']
+ if 'ConnectionPasswordEncryption' in dc_enc_settings.keys():
+ con_pass_enc = dc_enc_settings['ConnectionPasswordEncryption']
+ if 'ReturnConnectionPasswordEncrypted' in con_pass_enc.keys():
+ if con_pass_enc['ReturnConnectionPasswordEncrypted'] == True:
+ connection_encrypted = True
+
+ if 'EncryptionAtRest' in dc_enc_settings.keys():
+ enc_at_rest = dc_enc_settings['EncryptionAtRest']
+ if 'CatalogEncryptionMode' in enc_at_rest.keys():
+ if enc_at_rest['CatalogEncryptionMode'] == "SSE-KMS":
+ encrypted_at_rest = True
+
+ if connection_encrypted and encrypted_at_rest:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = GlueDataCatalogEncryption()
diff --git a/checkov/cloudformation/checks/resource/aws/GlueSecurityConfiguration.py b/checkov/cloudformation/checks/resource/aws/GlueSecurityConfiguration.py
new file mode 100644
index 0000000000..534d993d49
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/GlueSecurityConfiguration.py
@@ -0,0 +1,44 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class GlueSecurityConfiguration(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Glue Security Configuration Encryption is enabled"
+ id = "CKV_AWS_99"
+ supported_resources = ['AWS::Glue::SecurityConfiguration']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ s3_enc = False
+ cw_enc = False
+ book_enc = False
+ if 'Properties' in conf.keys():
+ if 'EncryptionConfiguration' in conf['Properties'].keys():
+ enc_conf = conf['Properties']['EncryptionConfiguration']
+
+ if 'CloudWatchEncryption' in enc_conf.keys():
+ if 'CloudWatchEncryptionMode' in enc_conf['CloudWatchEncryption'].keys():
+ if enc_conf['CloudWatchEncryption']['CloudWatchEncryptionMode'] != 'DISABLED':
+ cw_enc = True
+
+ if 'JobBookmarksEncryption' in enc_conf.keys():
+ if 'JobBookmarksEncryptionMode' in enc_conf['JobBookmarksEncryption'].keys():
+ if enc_conf['JobBookmarksEncryption']['JobBookmarksEncryptionMode'] != 'DISABLED':
+ book_enc = True
+
+
+ if 'S3Encryptions' in enc_conf.keys():
+ if 'S3EncryptionMode' in enc_conf['S3Encryptions'].keys():
+ if enc_conf['S3Encryptions']['S3EncryptionMode'] != 'DISABLED':
+ s3_enc = True
+
+
+ if s3_enc and cw_enc and book_enc:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = GlueSecurityConfiguration()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMCredentialsExposure.py b/checkov/cloudformation/checks/resource/aws/IAMCredentialsExposure.py
new file mode 100644
index 0000000000..89f4529878
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMCredentialsExposure.py
@@ -0,0 +1,15 @@
+from checkov.cloudformation.checks.resource.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class cloudsplainingCredentialsExposure(BaseCloudsplainingIAMCheck):
+
+ def __init__(self):
+ name = "Ensure IAM policies does not allow credentials exposure"
+ id = "CKV_AWS_107"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy):
+ return policy.credentials_exposure
+
+
+check = cloudsplainingCredentialsExposure()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMDataExfiltration.py b/checkov/cloudformation/checks/resource/aws/IAMDataExfiltration.py
new file mode 100644
index 0000000000..9b86e390ae
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMDataExfiltration.py
@@ -0,0 +1,15 @@
+from checkov.cloudformation.checks.resource.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class cloudsplainingDataExfiltration(BaseCloudsplainingIAMCheck):
+
+ def __init__(self):
+ name = "Ensure IAM policies does not allow data exfiltration"
+ id = "CKV_AWS_108"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy):
+ return policy.allows_data_exfiltration_actions
+
+
+check = cloudsplainingDataExfiltration()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMPermissionsManagement.py b/checkov/cloudformation/checks/resource/aws/IAMPermissionsManagement.py
new file mode 100644
index 0000000000..dfb9c7b386
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMPermissionsManagement.py
@@ -0,0 +1,15 @@
+from checkov.cloudformation.checks.resource.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class cloudsplainingPermissionsManagement(BaseCloudsplainingIAMCheck):
+
+ def __init__(self):
+ name = "Ensure IAM policies does not allow permissions management without constraints"
+ id = "CKV_AWS_109"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy):
+ return policy.permissions_management_without_constraints
+
+
+check = cloudsplainingPermissionsManagement()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMPrivilegeEscalation.py b/checkov/cloudformation/checks/resource/aws/IAMPrivilegeEscalation.py
new file mode 100644
index 0000000000..84160b68db
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMPrivilegeEscalation.py
@@ -0,0 +1,16 @@
+from checkov.cloudformation.checks.resource.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class cloudsplainingPrivilegeEscalation(BaseCloudsplainingIAMCheck):
+
+ def __init__(self):
+ name = "Ensure IAM policies does not allow privilege escalation"
+ id = "CKV_AWS_110"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy):
+ escalation = policy.allows_privilege_escalation
+ return escalation
+
+
+check = cloudsplainingPrivilegeEscalation()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py b/checkov/cloudformation/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
index cc5cb2e176..ac83bff526 100644
--- a/checkov/cloudformation/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
+++ b/checkov/cloudformation/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
@@ -4,6 +4,7 @@
from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
+ACCOUNT_ACCESS = re.compile(r'\d{12}|arn:aws:iam::\d{12}:root')
class IAMRoleAllowAssumeFromAccount(BaseResourceCheck):
def __init__(self):
@@ -15,37 +16,24 @@ def __init__(self):
def scan_resource_conf(self, conf):
if 'AssumeRolePolicyDocument' in conf['Properties']:
- if isinstance(conf['Properties']['AssumeRolePolicyDocument'], dict) and 'Fn::Sub' in conf['Properties']['AssumeRolePolicyDocument'].keys():
- assume_role_block = json.loads(conf['Properties']['AssumeRolePolicyDocument']['Fn::Sub'])
- if 'Statement' in assume_role_block.keys():
- if isinstance(assume_role_block['Statement'], list) and 'Principal' in \
- assume_role_block['Statement'][0]:
- if 'AWS' in assume_role_block['Statement'][0]['Principal']:
- account_access = re.compile(r'\d{12}|arn:aws:iam::\d{12}:root')
- if 'AWS' in assume_role_block['Statement'][0]['Principal']:
- if isinstance(assume_role_block['Statement'][0]['Principal']['AWS'],
- list) and isinstance(
- assume_role_block['Statement'][0]['Principal']['AWS'][0], str):
- if re.match(account_access,
- assume_role_block['Statement'][0]['Principal']['AWS'][0]):
- return CheckResult.FAILED
+ assume_role_policy_doc = conf['Properties']['AssumeRolePolicyDocument']
+ if isinstance(assume_role_policy_doc, dict) and 'Fn::Sub' in assume_role_policy_doc.keys():
+ assume_role_block = json.loads(assume_role_policy_doc['Fn::Sub'])
+ elif isinstance(assume_role_policy_doc, str):
+ assume_role_block = json.loads(assume_role_policy_doc)
else:
- if isinstance(conf['Properties']['AssumeRolePolicyDocument'], str):
- assume_role_block = json.loads(conf['Properties']['AssumeRolePolicyDocument'])
- else:
- assume_role_block = conf['Properties']['AssumeRolePolicyDocument']
- if 'Statement' in assume_role_block.keys():
- if isinstance(assume_role_block['Statement'], list) and 'Principal' in \
- assume_role_block['Statement'][0]:
- if 'AWS' in assume_role_block['Statement'][0]['Principal']:
- account_access = re.compile(r'\d{12}|arn:aws:iam::\d{12}:root')
- if 'AWS' in assume_role_block['Statement'][0]['Principal']:
- if isinstance(assume_role_block['Statement'][0]['Principal']['AWS'],
- list) and isinstance(
- assume_role_block['Statement'][0]['Principal']['AWS'][0], str):
- if re.match(account_access,
- assume_role_block['Statement'][0]['Principal']['AWS'][0]):
- return CheckResult.FAILED
+ assume_role_block = assume_role_policy_doc
+ else:
+ return CheckResult.UNKNOWN
+
+ if 'Statement' in assume_role_block.keys():
+ if isinstance(assume_role_block['Statement'], list) and 'Principal' in \
+ assume_role_block['Statement'][0]:
+ if 'AWS' in assume_role_block['Statement'][0]['Principal']:
+ if isinstance(assume_role_block['Statement'][0]['Principal']['AWS'],list) \
+ and isinstance(assume_role_block['Statement'][0]['Principal']['AWS'][0], str):
+ if re.match(ACCOUNT_ACCESS, assume_role_block['Statement'][0]['Principal']['AWS'][0]):
+ return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/cloudformation/checks/resource/aws/IAMRoleAllowsPublicAssume.py b/checkov/cloudformation/checks/resource/aws/IAMRoleAllowsPublicAssume.py
new file mode 100644
index 0000000000..784caf152b
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMRoleAllowsPublicAssume.py
@@ -0,0 +1,42 @@
+import json
+
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class IAMRoleAllowsPublicAssume(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure IAM role allows only specific services or principals to assume it"
+ id = "CKV_AWS_60"
+ supported_resources = ['AWS::IAM::Role']
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf:
+ properties = conf['Properties']
+ if 'AssumeRolePolicyDocument' in properties:
+ assume_role_policy_doc = properties['AssumeRolePolicyDocument']
+ if isinstance(assume_role_policy_doc, str):
+ assume_role_policy_doc = json.loads(assume_role_policy_doc)
+ if 'Statement' in assume_role_policy_doc:
+ statements = assume_role_policy_doc['Statement']
+ if isinstance(statements, list):
+ for statement in statements:
+ if 'Effect' in statement:
+ if statement['Effect'] == "Deny":
+ continue
+ if 'Principal' in statement:
+ principal = statement['Principal']
+ if 'AWS' in principal:
+ aws_principals = principal['AWS']
+ if aws_principals == "*":
+ return CheckResult.FAILED
+ if isinstance(aws_principals, list):
+ for principal in aws_principals:
+ if principal == "*":
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = IAMRoleAllowsPublicAssume()
diff --git a/checkov/cloudformation/checks/resource/aws/IAMWriteAccess.py b/checkov/cloudformation/checks/resource/aws/IAMWriteAccess.py
new file mode 100644
index 0000000000..5e32e74e14
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IAMWriteAccess.py
@@ -0,0 +1,15 @@
+from checkov.cloudformation.checks.resource.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class cloudsplainingWriteAccess(BaseCloudsplainingIAMCheck):
+
+ def __init__(self):
+ name = "Ensure IAM policies does not allow write access without constraints"
+ id = "CKV_AWS_111"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy):
+ return policy.write_actions_without_constraints
+
+
+check = cloudsplainingWriteAccess()
diff --git a/checkov/cloudformation/checks/resource/aws/IMDSv1Disabled.py b/checkov/cloudformation/checks/resource/aws/IMDSv1Disabled.py
new file mode 100644
index 0000000000..4cb2d9be02
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/IMDSv1Disabled.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class IMDSv1Disabled(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Instance Metadata Service Version 1 is not enabled"
+ id = "CKV_AWS_79"
+ supported_resources = ['AWS::EC2::LaunchTemplate']
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ # IMDS can be disabled or IMDSv2 can be enabled
+ if 'Properties' in conf.keys():
+ properties = conf['Properties']
+ if 'LaunchTemplateData' in properties.keys():
+ launch_template_data = properties['LaunchTemplateData']
+ if 'MetadataOptions' in launch_template_data.keys():
+ metadata_options = launch_template_data['MetadataOptions']
+ if 'HttpEndpoint' in metadata_options.keys():
+ if metadata_options['HttpEndpoint'] == "disabled":
+ return CheckResult.PASSED
+ if 'HttpTokens' in metadata_options.keys():
+ if metadata_options['HttpTokens'] == "required":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = IMDSv1Disabled()
diff --git a/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py b/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py
new file mode 100644
index 0000000000..42bb8a943d
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.secrets import string_has_secrets, AWS, GENERAL
+
+class LambdaEnvironmentCredentials(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure no hard-coded secrets exist in lambda environment"
+ id = "CKV_AWS_45"
+ supported_resources = ['AWS::Lambda::Function']
+ categories = [CheckCategories.SECRETS]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'Properties' in conf.keys():
+ properties = conf['Properties']
+ if 'Environment' in properties.keys():
+ environment = properties['Environment']
+ if 'Variables' in environment.keys():
+ variables = environment['Variables']
+ for value in variables.values():
+ if string_has_secrets(str(value),AWS,GENERAL):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = LambdaEnvironmentCredentials()
diff --git a/checkov/cloudformation/checks/resource/aws/NeptuneClusterLogging.py b/checkov/cloudformation/checks/resource/aws/NeptuneClusterLogging.py
new file mode 100644
index 0000000000..00dc6c8c70
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/NeptuneClusterLogging.py
@@ -0,0 +1,24 @@
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class NeptuneClusterLogging(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure Neptune logging is enabled"
+ id = "CKV_AWS_101"
+ supported_resources = ["AWS::Neptune::DBCluster"]
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: dict_node) -> CheckResult:
+ log_types = ["audit"]
+
+ logs_exports = conf.get("Properties", {}).get("EnableCloudwatchLogsExports", [])
+ if all(elem in logs_exports for elem in log_types):
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = NeptuneClusterLogging()
diff --git a/checkov/cloudformation/checks/resource/aws/QLDBLedgerDeletionProtection.py b/checkov/cloudformation/checks/resource/aws/QLDBLedgerDeletionProtection.py
new file mode 100644
index 0000000000..ae2ff148d8
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/QLDBLedgerDeletionProtection.py
@@ -0,0 +1,26 @@
+from typing import Dict
+
+from checkov.cloudformation.parser.node import str_node, dict_node
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class QLDBLedgerDeletionProtection(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure QLDB ledger has deletion protection enabled"
+ id = "CKV_AWS_172"
+ supported_resources = ["AWS::QLDB::Ledger"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str_node, dict_node]) -> CheckResult:
+ # deletion protection is enabled on default
+ if "DeletionProtection" not in conf.get("Properties", {}):
+ return CheckResult.PASSED
+ return super().scan_resource_conf(conf)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/DeletionProtection"
+
+
+check = QLDBLedgerDeletionProtection()
diff --git a/checkov/cloudformation/checks/resource/aws/QLDBLedgerPermissionsMode.py b/checkov/cloudformation/checks/resource/aws/QLDBLedgerPermissionsMode.py
new file mode 100644
index 0000000000..f2473028f4
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/QLDBLedgerPermissionsMode.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class QLDBLedgerPermissionsMode(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure QLDB ledger permissions mode is set to STANDARD"
+ id = "CKV_AWS_170"
+ supported_resources = ["AWS::QLDB::Ledger"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/PermissionsMode"
+
+ def get_expected_value(self) -> str:
+ return "STANDARD"
+
+
+check = QLDBLedgerPermissionsMode()
diff --git a/checkov/cloudformation/checks/resource/aws/RDSClusterIAMAuthentication.py b/checkov/cloudformation/checks/resource/aws/RDSClusterIAMAuthentication.py
new file mode 100644
index 0000000000..bbd0b354b7
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RDSClusterIAMAuthentication.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RDSClusterIAMAuthentication(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure RDS cluster has IAM authentication enabled"
+ id = "CKV_AWS_162"
+ supported_resources = ["AWS::RDS::DBCluster"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/EnableIAMDatabaseAuthentication"
+
+
+check = RDSClusterIAMAuthentication()
diff --git a/checkov/cloudformation/checks/resource/aws/RDSIAMAuthentication.py b/checkov/cloudformation/checks/resource/aws/RDSIAMAuthentication.py
new file mode 100644
index 0000000000..3b39a2aa95
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RDSIAMAuthentication.py
@@ -0,0 +1,26 @@
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RDSIAMAuthentication(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure RDS database has IAM authentication enabled"
+ id = "CKV_AWS_161"
+ supported_resources = ["AWS::RDS::DBInstance"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/EnableIAMDatabaseAuthentication"
+
+ def scan_resource_conf(self, conf: dict_node) -> CheckResult:
+ # IAM authentication is only supported for MySQL and PostgreSQL
+ engine = conf.get("Properties", {}).get("Engine", {})
+ if engine not in ("mysql", "postgres"):
+ return CheckResult.UNKNOWN
+
+ return super().scan_resource_conf(conf)
+
+
+check = RDSIAMAuthentication()
diff --git a/checkov/cloudformation/checks/resource/aws/RDSMultiAZEnabled.py b/checkov/cloudformation/checks/resource/aws/RDSMultiAZEnabled.py
new file mode 100644
index 0000000000..ceffaa4e22
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RDSMultiAZEnabled.py
@@ -0,0 +1,18 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class RDSMultiAZEnabled(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that RDS instances have Multi-AZ enabled"
+ id = "CKV_AWS_157"
+ supported_resources = ['AWS::RDS::DBInstance']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/MultiAZ'
+
+
+check = RDSMultiAZEnabled()
diff --git a/checkov/cloudformation/checks/resource/aws/RDSPubliclyAccessible.py b/checkov/cloudformation/checks/resource/aws/RDSPubliclyAccessible.py
index 1c9c02a95b..b067a21626 100644
--- a/checkov/cloudformation/checks/resource/aws/RDSPubliclyAccessible.py
+++ b/checkov/cloudformation/checks/resource/aws/RDSPubliclyAccessible.py
@@ -5,7 +5,7 @@
class RDSPubliclyAccessible(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure all data stored in the RDS bucket is not public accessible"
+ name = "Ensure all data stored in RDS is not publicly accessible"
id = "CKV_AWS_17"
supported_resources = ['AWS::RDS::DBInstance']
categories = [CheckCategories.NETWORKING]
diff --git a/checkov/cloudformation/checks/resource/aws/RedShiftSSL.py b/checkov/cloudformation/checks/resource/aws/RedShiftSSL.py
new file mode 100644
index 0000000000..8c154916d4
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RedShiftSSL.py
@@ -0,0 +1,24 @@
+from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class RedShiftSSL(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure Redshift uses SSL"
+ id = "CKV_AWS_105"
+ supported_resources = ["AWS::Redshift::ClusterParameterGroup"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: dict_node) -> CheckResult:
+ params = conf.get("Properties", {}).get("Parameters", {})
+
+ for param in params:
+ if param.get("ParameterName") == "require_ssl" and param.get("ParameterValue") == "true":
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = RedShiftSSL()
diff --git a/checkov/cloudformation/checks/resource/aws/RedshiftClusterEncryption.py b/checkov/cloudformation/checks/resource/aws/RedshiftClusterEncryption.py
index ec85aa36f2..e506cc044c 100644
--- a/checkov/cloudformation/checks/resource/aws/RedshiftClusterEncryption.py
+++ b/checkov/cloudformation/checks/resource/aws/RedshiftClusterEncryption.py
@@ -3,16 +3,15 @@
class RedshiftClusterEncryption(BaseResourceValueCheck):
-
- def __init__(self):
+ def __init__(self) -> None:
name = "Ensure all data stored in the Redshift cluster is securely encrypted at rest"
id = "CKV_AWS_64"
- supported_resources = ['AWS::Redshift::Cluster']
+ supported_resources = ["AWS::Redshift::Cluster"]
categories = [CheckCategories.ENCRYPTION]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
- def get_inspected_key(self):
- return 'Properties/Encrypted'
+ def get_inspected_key(self) -> str:
+ return "Properties/Encrypted"
check = RedshiftClusterEncryption()
diff --git a/checkov/cloudformation/checks/resource/aws/RedshiftClusterLogging.py b/checkov/cloudformation/checks/resource/aws/RedshiftClusterLogging.py
new file mode 100644
index 0000000000..27393b1013
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RedshiftClusterLogging.py
@@ -0,0 +1,21 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+
+
+class RedshiftClusterLogging(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure Redshift Cluster logging is enabled"
+ id = "CKV_AWS_71"
+ supported_resources = ["AWS::Redshift::Cluster"]
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/LoggingProperties/BucketName"
+
+ def get_expected_value(self) -> str:
+ return ANY_VALUE
+
+
+check = RedshiftClusterLogging()
diff --git a/checkov/cloudformation/checks/resource/aws/RedshiftClusterPubliclyAccessible.py b/checkov/cloudformation/checks/resource/aws/RedshiftClusterPubliclyAccessible.py
new file mode 100644
index 0000000000..9222779cc1
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RedshiftClusterPubliclyAccessible.py
@@ -0,0 +1,22 @@
+from typing import List
+
+from checkov.cloudformation.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class RedshiftClusterPubliclyAccessible(BaseResourceNegativeValueCheck):
+ def __init__(self) -> None:
+ name = "Redshift cluster should not be publicly accessible"
+ id = "CKV_AWS_87"
+ supported_resources = ["AWS::Redshift::Cluster"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/PubliclyAccessible"
+
+ def get_forbidden_values(self) -> List[bool]:
+ return [True]
+
+
+check = RedshiftClusterPubliclyAccessible()
diff --git a/checkov/cloudformation/checks/resource/aws/RedshiftInEc2ClassicMode.py b/checkov/cloudformation/checks/resource/aws/RedshiftInEc2ClassicMode.py
new file mode 100644
index 0000000000..586e5dea36
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/RedshiftInEc2ClassicMode.py
@@ -0,0 +1,21 @@
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+
+
+class RedshiftInEc2ClassicMode(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure Redshift is not deployed outside of a VPC"
+ id = "CKV_AWS_154"
+ supported_resources = ["AWS::Redshift::Cluster"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/ClusterSubnetGroupName"
+
+ def get_expected_value(self) -> str:
+ return ANY_VALUE
+
+
+check = RedshiftInEc2ClassicMode()
diff --git a/checkov/cloudformation/checks/resource/aws/TimestreamDatabaseKMSKey.py b/checkov/cloudformation/checks/resource/aws/TimestreamDatabaseKMSKey.py
new file mode 100644
index 0000000000..962bbc8112
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/TimestreamDatabaseKMSKey.py
@@ -0,0 +1,23 @@
+from typing import Any
+
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+
+
+class TimestreamDatabaseKMSKey(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure that Timestream database is encrypted with KMS CMK"
+ id = "CKV_AWS_160"
+ supported_resources = ["AWS::Timestream::Database"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "Properties/KmsKeyId"
+
+ def get_expected_value(self) -> Any:
+ return ANY_VALUE
+
+
+check = TimestreamDatabaseKMSKey()
diff --git a/checkov/cloudformation/checks/resource/aws/TransferServerIsPublic.py b/checkov/cloudformation/checks/resource/aws/TransferServerIsPublic.py
new file mode 100644
index 0000000000..ae6efb8bb1
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/TransferServerIsPublic.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class TransferServerIsPublic(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Transfer Server is not exposed publicly."
+ id = "CKV_AWS_164"
+ supported_resources = ['AWS::Transfer::Server']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/EndpointType'
+
+ def get_expected_values(self):
+ return ["VPC", "VPC_ENDPOINT"]
+
+
+check = TransferServerIsPublic()
diff --git a/checkov/cloudformation/checks/resource/aws/VPCEndpointAcceptanceConfigured.py b/checkov/cloudformation/checks/resource/aws/VPCEndpointAcceptanceConfigured.py
new file mode 100644
index 0000000000..80b9a0ff46
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/VPCEndpointAcceptanceConfigured.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class VPCEndpointAcceptanceConfigured(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that VPC Endpoint Service is configured for Manual Acceptance"
+ id = "CKV_AWS_123"
+ supported_resources = ['AWS::EC2::VPCEndpointService']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/AcceptanceRequired'
+
+
+check = VPCEndpointAcceptanceConfigured()
diff --git a/checkov/cloudformation/checks/resource/aws/WorkspaceRootVolumeEncrypted.py b/checkov/cloudformation/checks/resource/aws/WorkspaceRootVolumeEncrypted.py
new file mode 100644
index 0000000000..78e0acb0d8
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/WorkspaceRootVolumeEncrypted.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class WorkspaceRootVolumeEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Workspace root volumes are encrypted"
+ id = "CKV_AWS_156"
+ supported_resources = ['AWS::WorkSpaces::Workspace']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/RootVolumeEncryptionEnabled'
+
+
+check = WorkspaceRootVolumeEncrypted()
diff --git a/checkov/cloudformation/checks/resource/aws/WorkspaceUserVolumeEncrypted.py b/checkov/cloudformation/checks/resource/aws/WorkspaceUserVolumeEncrypted.py
new file mode 100644
index 0000000000..91e3edec6d
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/WorkspaceUserVolumeEncrypted.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class WorkspaceUserVolumeEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Workspace user volumes are encrypted"
+ id = "CKV_AWS_155"
+ supported_resources = ['AWS::WorkSpaces::Workspace']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'Properties/UserVolumeEncryptionEnabled'
+
+
+check = WorkspaceUserVolumeEncrypted()
diff --git a/checkov/cloudformation/checks/resource/base_registry.py b/checkov/cloudformation/checks/resource/base_registry.py
index 6b3cf9f92c..269c8b2893 100644
--- a/checkov/cloudformation/checks/resource/base_registry.py
+++ b/checkov/cloudformation/checks/resource/base_registry.py
@@ -1,12 +1,12 @@
+from typing import Tuple, Dict
+
+from checkov.cloudformation.parser import dict_node
+from checkov.cloudformation.parser.node import str_node
from checkov.common.checks.base_check_registry import BaseCheckRegistry
class Registry(BaseCheckRegistry):
-
- def __init__(self):
- super().__init__()
-
- def extract_entity_details(self, entity):
+ def extract_entity_details(self, entity: Dict[str_node, dict_node]) -> Tuple[str_node, str_node, dict_node]:
resource_name, resource = next(iter(entity.items()))
- resource_type = resource['Type']
+ resource_type = resource["Type"]
return resource_type, resource_name, resource
diff --git a/checkov/cloudformation/checks/resource/base_resource_negative_value_check.py b/checkov/cloudformation/checks/resource/base_resource_negative_value_check.py
index 0a5f24da73..aa23027b7b 100644
--- a/checkov/cloudformation/checks/resource/base_resource_negative_value_check.py
+++ b/checkov/cloudformation/checks/resource/base_resource_negative_value_check.py
@@ -1,20 +1,29 @@
from abc import abstractmethod
+from typing import List, Any, Optional
from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
from checkov.cloudformation.context_parser import ContextParser
+from checkov.cloudformation.parser.node import dict_node, str_node
from checkov.common.models.consts import ANY_VALUE
-from checkov.common.models.enums import CheckResult
+from checkov.common.models.enums import CheckResult, CheckCategories
class BaseResourceNegativeValueCheck(BaseResourceCheck):
- def __init__(self, name, id, categories, supported_resources, missing_block_result=CheckResult.FAILED):
+ def __init__(
+ self,
+ name: str,
+ id: str,
+ categories: List[CheckCategories],
+ supported_resources: List[str],
+ missing_block_result: CheckResult = CheckResult.FAILED,
+ ) -> None:
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
self.missing_block_result = missing_block_result
- def scan_resource_conf(self, conf, entity_type):
+ def scan_resource_conf(self, conf: dict_node, entity_type: str_node) -> CheckResult:
excluded_key = self.get_excluded_key()
if excluded_key is not None:
- path_elements = excluded_key.split('/')
+ path_elements = excluded_key.split("/")
matches = ContextParser.search_deep_keys(path_elements[-1], conf, [])
if len(matches) > 0:
for match in matches:
@@ -26,7 +35,7 @@ def scan_resource_conf(self, conf, entity_type):
inspected_key = self.get_inspected_key()
bad_values = self.get_forbidden_values()
- path_elements = inspected_key.split('/')
+ path_elements = inspected_key.split("/")
matches = ContextParser.search_deep_keys(path_elements[-1], conf, [])
if len(matches) > 0:
for match in matches:
@@ -37,26 +46,26 @@ def scan_resource_conf(self, conf, entity_type):
return CheckResult.PASSED
@abstractmethod
- def get_inspected_key(self):
+ def get_inspected_key(self) -> str:
"""
:return: JSONPath syntax path of the checked attribute
"""
raise NotImplementedError()
@abstractmethod
- def get_forbidden_values(self):
+ def get_forbidden_values(self) -> List[Any]:
"""
Returns a list of vulnerable values for the inspected key, governed by provider best practices
"""
raise NotImplementedError()
- def get_excluded_key(self):
+ def get_excluded_key(self) -> Optional[str]:
"""
:return: JSONPath syntax path of the an attribute that provides exclusion condition for the inspected key
"""
return None
- def check_excluded_condition(self, value):
+ def check_excluded_condition(self, value: Any) -> bool:
"""
:param: value: value for excluded_key
:return: True if the value should exclude the check from failing if the inspected key has a bad value
diff --git a/checkov/cloudformation/checks/resource/base_resource_value_check.py b/checkov/cloudformation/checks/resource/base_resource_value_check.py
index 1ffba5b934..d1b41166f7 100644
--- a/checkov/cloudformation/checks/resource/base_resource_value_check.py
+++ b/checkov/cloudformation/checks/resource/base_resource_value_check.py
@@ -1,36 +1,47 @@
import re
from abc import abstractmethod
+from typing import List, Any, Dict
from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
from checkov.cloudformation.context_parser import ContextParser
+from checkov.cloudformation.parser import dict_node
+from checkov.cloudformation.parser.node import str_node
from checkov.common.models.consts import ANY_VALUE
-from checkov.common.models.enums import CheckResult
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_list
-VARIABLE_DEPENDANT_REGEX = r'(?:Ref)\.[^\s]+'
+VARIABLE_DEPENDANT_REGEX = r"(?:Ref)\.[^\s]+"
class BaseResourceValueCheck(BaseResourceCheck):
- def __init__(self, name, id, categories, supported_resources, missing_block_result=CheckResult.FAILED):
+ def __init__(
+ self,
+ name: str,
+ id: str,
+ categories: List[CheckCategories],
+ supported_resources: List[str],
+ missing_block_result: CheckResult = CheckResult.FAILED,
+ ) -> None:
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
self.missing_block_result = missing_block_result
@staticmethod
- def _filter_key_path(path):
+ def _filter_key_path(path: str) -> List[str]:
"""
Filter an attribute path to contain only named attributes by dropping array indices from the path)
:param path: valid JSONPath of an attribute
:return: List of named attributes with respect to the input JSONPath order
"""
- return [x for x in path.split("/") if not re.search(r'^\[?\d+\]?$', x)]
+ return [x for x in path.split("/") if not re.search(r"^\[?\d+\]?$", x)]
@staticmethod
- def _is_variable_dependant(value):
+ def _is_variable_dependant(value: Any) -> bool:
if isinstance(value, str) and re.match(VARIABLE_DEPENDANT_REGEX, value):
return True
return False
@staticmethod
- def _is_nesting_key(inspected_attributes, key):
+ def _is_nesting_key(inspected_attributes: List[str], key: str) -> bool:
"""
Resolves whether a key is a subset of the inspected nesting attributes
:param inspected_attributes: list of nesting attributes
@@ -39,10 +50,10 @@ def _is_nesting_key(inspected_attributes, key):
"""
return any([x in key for x in inspected_attributes])
- def scan_resource_conf(self, conf):
+ def scan_resource_conf(self, conf: Dict[str_node, dict_node]) -> CheckResult:
inspected_key = self.get_inspected_key()
expected_values = self.get_expected_values()
- path_elements = inspected_key.split('/')
+ path_elements = inspected_key.split("/")
matches = ContextParser.search_deep_keys(path_elements[-1], conf, [])
if len(matches) > 0:
for match in matches:
@@ -51,7 +62,7 @@ def scan_resource_conf(self, conf):
# those, allowing inspected_keys in checks to use the same syntax.
for i in range(0, len(match)):
if type(match[i]) == int:
- match[i] = f'[{match[i]}]'
+ match[i] = f"[{match[i]}]"
if match[:-1] == path_elements:
# Inspected key exists
@@ -71,13 +82,13 @@ def scan_resource_conf(self, conf):
return self.missing_block_result
@abstractmethod
- def get_inspected_key(self):
+ def get_inspected_key(self) -> str:
"""
:return: JSONPath syntax path of the checked attribute
"""
raise NotImplementedError()
- def get_expected_values(self):
+ def get_expected_values(self) -> List[str]:
"""
Override the method with the list of acceptable values if the check has more than one possible expected value, given
the inspected key
@@ -85,8 +96,11 @@ def get_expected_values(self):
"""
return [self.get_expected_value()]
- def get_expected_value(self):
+ def get_expected_value(self) -> Any:
"""
Returns the default expected value, governed by provider best practices
"""
return True
+
+ def get_evaluated_keys(self) -> List[str]:
+ return force_list(self.get_inspected_key())
diff --git a/tests/terraform/runner/resources/__init__.py b/checkov/cloudformation/checks/utils/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/__init__.py
rename to checkov/cloudformation/checks/utils/__init__.py
diff --git a/checkov/cloudformation/checks/utils/iam_cloudformation_document_to_policy_converter.py b/checkov/cloudformation/checks/utils/iam_cloudformation_document_to_policy_converter.py
new file mode 100644
index 0000000000..b8a1ae8010
--- /dev/null
+++ b/checkov/cloudformation/checks/utils/iam_cloudformation_document_to_policy_converter.py
@@ -0,0 +1,35 @@
+import copy
+
+from checkov.cloudformation.parser.node import dict_node
+
+
+def convert_cloudformation_conf_to_iam_policy(conf: dict_node) -> dict_node:
+ """
+ converts terraform parsed configuration to iam policy document
+ """
+ result = copy.deepcopy(conf)
+ if "Statement" in result.keys():
+ result["Statement"] = result.pop("Statement")
+ for statement in result["Statement"]:
+ if "Action" in statement:
+ statement["Action"] = str(statement.pop("Action")[0])
+ if "Resource" in statement:
+ resources = statement.pop("Resource")
+ if isinstance(resources, list):
+ statement["Resource"] = str(resources[0])
+ else:
+ statement["Resource"] = str(resources)
+ if "NotAction" in statement:
+ statement["NotAction"] = str(statement.pop("NotAction")[0])
+ if "NotResource" in statement:
+ not_resources = statement.pop("NotResource")
+ if isinstance(not_resources, list):
+ statement["NotResource"] = str(not_resources[0])
+ else:
+ statement["NotResource"] = str(not_resources)
+ if "Effect" in statement:
+ statement["Effect"] = str(statement.pop("Effect"))
+ if "Effect" not in statement:
+ statement["Effect"] = "Allow"
+ statement = dict(statement)
+ return result
diff --git a/checkov/cloudformation/context_parser.py b/checkov/cloudformation/context_parser.py
index 67d187e081..9b871f7dd0 100644
--- a/checkov/cloudformation/context_parser.py
+++ b/checkov/cloudformation/context_parser.py
@@ -1,79 +1,84 @@
import logging
import operator
-from functools import reduce
import re
-from checkov.common.comment.enum import COMMENT_REGEX
+from functools import reduce
+from typing import List, Tuple, Optional, Union, Generator
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.cloudformation.parser.node import dict_node, str_node, list_node
+from checkov.common.comment.enum import COMMENT_REGEX
+from checkov.common.typing import _SkippedCheck
-ENDLINE = '__endline__'
+ENDLINE = "__endline__"
+STARTLINE = "__startline__"
-STARTLINE = '__startline__'
class ContextParser(object):
"""
CloudFormation template context parser
"""
- def __init__(self, cf_file, cf_template, cf_template_lines):
+
+ def __init__(self, cf_file: str, cf_template: dict_node, cf_template_lines: List[Tuple[int, str]]) -> None:
self.cf_file = cf_file
self.cf_template = cf_template
self.cf_template_lines = cf_template_lines
- def evaluate_default_refs(self):
+ def evaluate_default_refs(self) -> None:
# Get Parameter Defaults - Locate Refs in Template
- refs = []
- refs.extend(self.search_deep_keys('Ref', self.cf_template, []))
+ refs = self.search_deep_keys("Ref", self.cf_template, [])
for ref in refs:
refname = ref.pop()
ref.pop() # Get rid of the 'Ref' dict key
- if 'Parameters' in self.cf_template.keys() and refname in self.cf_template[
- 'Parameters'].keys():
- # TODO refactor into evaluations
- if 'Default' in self.cf_template['Parameters'][refname].keys():
- logging.debug(
- "Replacing Ref {} in file {} with default parameter value: {}".format(refname, self.cf_file,
- self.cf_template[
- 'Parameters'][
- refname][
- 'Default']))
- self._set_in_dict(self.cf_template, ref,
- self.cf_template['Parameters'][refname]['Default'])
-
- ## TODO - Add Variable Eval Message for Output
- # Output in Checkov looks like this:
- # Variable versioning (of /.) evaluated to value "True" in expression: enabled = ${var.versioning}
+ # TODO refactor into evaluations
+ default_value = self.cf_template.get("Parameters", {}).get(refname, {}).get("Properties", {}).get("Default")
+ if default_value is not None:
+ logging.debug(
+ "Replacing Ref {} in file {} with default parameter value: {}".format(
+ refname, self.cf_file, default_value
+ )
+ )
+ self._set_in_dict(self.cf_template, ref, default_value)
+
+ ## TODO - Add Variable Eval Message for Output
+ # Output in Checkov looks like this:
+ # Variable versioning (of /.) evaluated to value "True" in expression: enabled = ${var.versioning}
@staticmethod
- def extract_cf_resource_id(cf_resource, cf_resource_name):
+ def extract_cf_resource_id(cf_resource: dict_node, cf_resource_name: str_node) -> Optional[str]:
if cf_resource_name == STARTLINE or cf_resource_name == ENDLINE:
- return
- if 'Type' not in cf_resource:
+ return None
+ if "Type" not in cf_resource:
# This is not a CloudFormation resource, skip
- return
+ return None
return f"{cf_resource['Type']}.{cf_resource_name}"
- def extract_cf_resource_code_lines(self, cf_resource):
- find_lines_result_list = list(self.find_lines(cf_resource, STARTLINE))
- if len(find_lines_result_list) >= 1:
- start_line = min(find_lines_result_list)
- end_line = max(list(self.find_lines(cf_resource, ENDLINE)))
+ def extract_cf_resource_code_lines(
+ self, cf_resource: dict_node
+ ) -> Tuple[Optional[List[int]], Optional[List[Tuple[int, str]]]]:
+ find_lines_result_set = set(self.find_lines(cf_resource, STARTLINE))
+ if len(find_lines_result_set) >= 1:
+ start_line = min(find_lines_result_set)
+ end_line = max(self.find_lines(cf_resource, ENDLINE))
# start_line - 2: -1 to switch to 0-based indexing, and -1 to capture the resource name
- entity_code_lines = self.cf_template_lines[start_line - 2: end_line - 1]
+ entity_code_lines = self.cf_template_lines[start_line - 2 : end_line - 1]
# if the file did not end in a new line, and this was the last resource in the file, then we
# trimmed off the last line
- if (end_line - 1) < len(self.cf_template_lines) and not self.cf_template_lines[end_line - 1][1].endswith('\n'):
+ if (end_line - 1) < len(self.cf_template_lines) and not self.cf_template_lines[end_line - 1][1].endswith(
+ "\n"
+ ):
entity_code_lines.append(self.cf_template_lines[end_line - 1])
entity_code_lines = ContextParser.trim_lines(entity_code_lines)
- entity_lines_range = [entity_code_lines[0][0],entity_code_lines[-1][0]]
+ entity_lines_range = [entity_code_lines[0][0], entity_code_lines[-1][0]]
return entity_lines_range, entity_code_lines
return None, None
@staticmethod
- def trim_lines(code_lines):
+ def trim_lines(code_lines: List[Tuple[int, str]]) -> List[Tuple[int, str]]:
# Removes leading and trailing lines that are only whitespace, returning a new value
# The passed value should be a list of tuples of line numbers and line strings (entity_code_lines)
start = 0
@@ -87,7 +92,16 @@ def trim_lines(code_lines):
return code_lines[start:end]
@staticmethod
- def find_lines(node, kv):
+ def find_lines(node: Union[list_node, dict_node], kv: str) -> Generator[int, None, None]:
+ # Hack to allow running checkov on json templates
+ # CF scripts that are parsed using the yaml mechanism have a magic STARTLINE and ENDLINE property
+ # CF scripts that are parsed using the json mechnism use dicts that have a marker
+ if hasattr(node, "start_mark") and kv == STARTLINE:
+ yield node.start_mark.line + 1
+
+ if hasattr(node, "end_mark") and kv == ENDLINE:
+ yield node.end_mark.line + 1
+
if isinstance(node, list):
for i in node:
for x in ContextParser.find_lines(i, kv):
@@ -100,24 +114,33 @@ def find_lines(node, kv):
yield x
@staticmethod
- def collect_skip_comments(entity_code_lines):
+ def collect_skip_comments(entity_code_lines: List[Tuple[int, str]]) -> List[_SkippedCheck]:
skipped_checks = []
+ bc_id_mapping = bc_integration.get_id_mapping()
+ ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
for line in entity_code_lines:
skip_search = re.search(COMMENT_REGEX, str(line))
if skip_search:
- skipped_checks.append(
- {
- 'id': skip_search.group(2),
- 'suppress_comment': skip_search.group(3)[1:] if skip_search.group(
- 3) else "No comment provided"
- }
- )
+ skipped_check: _SkippedCheck = {
+ "id": skip_search.group(2),
+ "suppress_comment": skip_search.group(3)[1:] if skip_search.group(3) else "No comment provided",
+ }
+ # No matter which ID was used to skip, save the pair of IDs in the appropriate fields
+ if bc_id_mapping and skipped_check["id"] in bc_id_mapping:
+ skipped_check["bc_id"] = skipped_check["id"]
+ skipped_check["id"] = bc_id_mapping[skipped_check["id"]]
+ elif ckv_to_bc_id_mapping:
+ skipped_check["bc_id"] = ckv_to_bc_id_mapping.get(skipped_check["id"])
+
+ skipped_checks.append(skipped_check)
return skipped_checks
@staticmethod
- def search_deep_keys(search_text, cfn_dict, path):
+ def search_deep_keys(
+ search_text: str, cfn_dict: Union[str_node, list_node, dict_node], path: List[str]
+ ) -> List[List[Union[int, str]]]:
"""Search deep for keys and get their values"""
- keys = []
+ keys: List[List[Union[int, str]]] = []
if isinstance(cfn_dict, dict):
for key in cfn_dict:
pathprop = path[:]
@@ -143,9 +166,22 @@ def search_deep_keys(search_text, cfn_dict, path):
return keys
- def _set_in_dict(self, data_dict, map_list, value):
- self._get_from_dict(data_dict, map_list[:-1])[map_list[-1]] = value
+ def _set_in_dict(self, data_dict: dict_node, map_list: List[Union[int, str]], value: str_node) -> None:
+ v = self._get_from_dict(data_dict, map_list[:-1])
+ # save the original marks so that we do not copy in the line numbers of the parameter element
+ # but not all ref types will have these attributes
+ start = None
+ end = None
+ if hasattr(v, "start_mark") and hasattr(v, "end_mark"):
+ start = v.start_mark
+ end = v.end_mark
+
+ v[map_list[-1]] = value
+
+ if hasattr(v[map_list[-1]], "start_mark") and start and end:
+ v[map_list[-1]].start_mark = start
+ v[map_list[-1]].end_mark = end
@staticmethod
- def _get_from_dict(data_dict, map_list):
+ def _get_from_dict(data_dict: dict_node, map_list: List[Union[int, str]]) -> Union[list_node, dict_node]:
return reduce(operator.getitem, map_list, data_dict)
diff --git a/tests/terraform/runner/resources/example/__init__.py b/checkov/cloudformation/graph_builder/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/example/__init__.py
rename to checkov/cloudformation/graph_builder/__init__.py
diff --git a/tests/terraform/runner/resources/nested_dir/__init__.py b/checkov/cloudformation/graph_builder/graph_components/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/nested_dir/__init__.py
rename to checkov/cloudformation/graph_builder/graph_components/__init__.py
diff --git a/checkov/cloudformation/graph_builder/graph_components/block_types.py b/checkov/cloudformation/graph_builder/graph_components/block_types.py
new file mode 100644
index 0000000000..9333c810eb
--- /dev/null
+++ b/checkov/cloudformation/graph_builder/graph_components/block_types.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+from enum import Enum
+
+from checkov.common.graph.graph_builder.graph_components.block_types import BlockType as CommonBlockType
+
+
+@dataclass
+class BlockType(CommonBlockType):
+ METADATA = "metadata"
+ PARAMETER = "parameters"
+ RULE = "rules"
+ MAPPING = "mappings"
+ CONDITION = "conditions"
+ TRANSFORM = "transform"
+ OUTPUT = "outputs"
diff --git a/checkov/cloudformation/graph_builder/graph_components/blocks.py b/checkov/cloudformation/graph_builder/graph_components/blocks.py
new file mode 100644
index 0000000000..cbadca2726
--- /dev/null
+++ b/checkov/cloudformation/graph_builder/graph_components/blocks.py
@@ -0,0 +1,24 @@
+from typing import Dict, Any
+
+from checkov.common.graph.graph_builder.graph_components.blocks import Block
+
+
+class CloudformationBlock(Block):
+ def __init__(
+ self,
+ name: str,
+ config: Dict[str, Any],
+ path: str,
+ block_type: str,
+ attributes: Dict[str, Any],
+ id: str = "",
+ source: str = "",
+ ) -> None:
+ """
+ :param name: unique name given to the terraform block, for example: 'aws_vpc.example_name'
+ :param config: the section in tf_definitions that belong to this block
+ :param path: the file location of the block
+ :param block_type: str
+ :param attributes: dictionary of the block's original attributes in the terraform file
+ """
+ super().__init__(name, config, path, block_type, attributes, id, source)
diff --git a/checkov/cloudformation/graph_builder/graph_to_definitions.py b/checkov/cloudformation/graph_builder/graph_to_definitions.py
new file mode 100644
index 0000000000..a8ced5d5ca
--- /dev/null
+++ b/checkov/cloudformation/graph_builder/graph_to_definitions.py
@@ -0,0 +1,35 @@
+import os
+from typing import List, Dict, Any, Tuple
+
+from checkov.cloudformation.graph_builder.graph_components.block_types import BlockType
+from checkov.cloudformation.parser import TemplateSections
+from checkov.cloudformation.graph_builder.graph_components.blocks import CloudformationBlock
+
+
+def convert_graph_vertices_to_definitions(
+ vertices: List[CloudformationBlock], root_folder: str
+) -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
+ definitions: Dict[str, Dict[str, Any]] = {}
+ breadcrumbs: Dict[str, Dict[str, Any]] = {}
+ for vertex in vertices:
+ if vertex.block_type != BlockType.RESOURCE and vertex.block_type != BlockType.PARAMETER:
+ continue
+ block_path = vertex.path
+ block_type = TemplateSections.RESOURCES.value if vertex.block_type == 'resource' else TemplateSections.PARAMETERS.value
+ block_name = vertex.name.split('.')[-1] # vertex.name is "type.name" so type.name -> [type, name]
+
+ definition = {
+ 'Type': vertex.attributes['resource_type'] if vertex.block_type == BlockType.RESOURCE else vertex.block_type,
+ 'Properties': vertex.config
+ }
+ definitions.setdefault(block_path, {}).setdefault(block_type, {}).setdefault(block_name, definition)
+
+ relative_block_path = f"/{os.path.relpath(block_path, root_folder)}"
+ add_breadcrumbs(vertex, breadcrumbs, relative_block_path)
+ return definitions, breadcrumbs
+
+
+def add_breadcrumbs(vertex: CloudformationBlock, breadcrumbs: Dict[str, Dict[str, Any]], relative_block_path: str) -> None:
+ vertex_breadcrumbs = vertex.breadcrumbs
+ if vertex_breadcrumbs:
+ breadcrumbs.setdefault(relative_block_path, {})[vertex.name] = vertex_breadcrumbs
diff --git a/checkov/cloudformation/graph_builder/local_graph.py b/checkov/cloudformation/graph_builder/local_graph.py
new file mode 100644
index 0000000000..d2f43edbd3
--- /dev/null
+++ b/checkov/cloudformation/graph_builder/local_graph.py
@@ -0,0 +1,271 @@
+import logging
+import re
+from inspect import ismethod
+from typing import Dict, Any, Optional
+
+from checkov.cloudformation.graph_builder.graph_components.block_types import BlockType
+from checkov.cloudformation.graph_builder.graph_components.blocks import CloudformationBlock
+from checkov.cloudformation.parser.cfn_keywords import IntrinsicFunctions, ConditionFunctions, ResourceAttributes, \
+ TemplateSections
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.graph.graph_builder import Edge
+from checkov.common.graph.graph_builder.local_graph import LocalGraph
+from cfnlint.template import Template
+
+
+class CloudformationLocalGraph(LocalGraph):
+ SUPPORTED_RESOURCE_ATTR_CONNECTION_KEYS = (ResourceAttributes.DEPENDS_ON, IntrinsicFunctions.CONDITION)
+ SUPPORTED_FN_CONNECTION_KEYS = (IntrinsicFunctions.GET_ATT, ConditionFunctions.IF,
+ IntrinsicFunctions.REF, IntrinsicFunctions.FIND_IN_MAP)
+
+ def __init__(self, cfn_definitions: Dict[str, dict_node], source: str = "CloudFormation") -> None:
+ super().__init__()
+ self.definitions = cfn_definitions
+ self.source = source
+ self._vertices_indexes = {}
+ self._templates = {}
+ self._edges_set = set()
+ self._templates = {file_path: Template(file_path, definition)
+ for file_path, definition in self.definitions.items()}
+ self._connection_key_func = {
+ IntrinsicFunctions.GET_ATT: self._fetch_getatt_target_id,
+ ConditionFunctions.IF: self._fetch_if_target_id,
+ IntrinsicFunctions.REF: self._fetch_ref_target_id,
+ IntrinsicFunctions.FIND_IN_MAP: self._fetch_findinmap_target_id
+ }
+
+ def build_graph(self, render_variables: bool) -> None:
+ self._create_vertices()
+ logging.info(f"[CloudformationLocalGraph] created {len(self.vertices)} vertices")
+ self._create_edges()
+ logging.info(f"[CloudformationLocalGraph] created {len(self.edges)} edges")
+
+ def _create_vertices(self) -> None:
+
+ def extract_resource_attributes(resource: dict_node) -> dict_node:
+ resource_type = resource.get("Type")
+ attributes = resource.get("Properties", {})
+ attributes["resource_type"] = resource_type
+ attributes["__startline__"] = resource["__startline__"]
+ attributes["__endline__"] = resource["__endline__"]
+ attributes.start_mark = resource.start_mark
+ attributes.end_mark = attributes.end_mark
+ return attributes
+
+ for file_path, file_conf in self.definitions.items():
+ self._create_section_vertices(file_path, file_conf, TemplateSections.RESOURCES,
+ BlockType.RESOURCE, extract_resource_attributes)
+ self._create_section_vertices(file_path, file_conf, TemplateSections.OUTPUTS, BlockType.OUTPUT)
+ self._create_section_vertices(file_path, file_conf, TemplateSections.MAPPINGS, BlockType.MAPPING)
+ self._create_section_vertices(file_path, file_conf, TemplateSections.CONDITIONS,
+ BlockType.CONDITION)
+ self._create_section_vertices(file_path, file_conf, TemplateSections.PARAMETERS,
+ BlockType.PARAMETER)
+
+ for i, vertex in enumerate(self.vertices):
+ self.vertices_by_block_type[vertex.block_type].append(i)
+ self.vertices_block_name_map[vertex.block_type][vertex.name].append(i)
+
+ def _create_section_vertices(self, file_path: str, file_conf: dict, section: TemplateSections,
+ block_type: str, attributes_operator: callable = lambda a: a) -> None:
+ for name, obj in get_only_dict_items(file_conf.get(section.value, {})).items():
+ is_resources_section = section == TemplateSections.RESOURCES
+ attributes = attributes_operator(obj)
+ block_name = name if not is_resources_section else f"{obj.get('Type', 'UnTyped')}.{name}"
+ config = obj if not is_resources_section else obj.get("Properties")
+ id = f"{block_type}.{block_name}" if not is_resources_section else block_name
+ self.vertices.append(CloudformationBlock(
+ name=block_name,
+ config=config,
+ path=file_path,
+ block_type=block_type,
+ attributes=attributes,
+ id=id,
+ source=self.source
+ ))
+
+ if not self._vertices_indexes.get(file_path):
+ self._vertices_indexes[file_path] = {}
+ self._vertices_indexes[file_path][name] = len(self.vertices) - 1
+
+ def _add_resource_attr_connections(self, attribute):
+ if attribute not in self.SUPPORTED_RESOURCE_ATTR_CONNECTION_KEYS:
+ return
+ for origin_node_index, vertex in enumerate(self.vertices):
+ if vertex.block_type == BlockType.RESOURCE:
+ vertex_path = vertex.path
+ vertex_name = vertex.name.split('.')[-1]
+ target_ids = self.definitions.get(vertex_path, {})\
+ .get(TemplateSections.RESOURCES.value, {}).get(vertex_name, {}).get(attribute, None)
+ target_ids = [target_ids] if isinstance(target_ids, str) else target_ids
+ if isinstance(target_ids, list):
+ for target_id in target_ids:
+ if isinstance(target_id, str):
+ dest_vertex_index = self._vertices_indexes.get(vertex_path, {}).get(target_id, None)
+ if dest_vertex_index is not None:
+ self._create_edge(origin_node_index, dest_vertex_index, label=attribute)
+ else:
+ logging.info(f"[CloudformationLocalGraph] didnt create edge for target_id {target_id}"
+ f"and vertex_path {vertex_path} as target_id is not a string")
+ else:
+ logging.info(f"[CloudformationLocalGraph] didnt create edge for target_ids {target_ids}"
+ f"and vertex_path {vertex_path} as target_ids is not a list")
+
+ def _extract_source_value_attrs(self, matching_path):
+ """ matching_path for Resource = [template_section, source_id, 'Properties', ... , key, value]
+ matching_path otherwise = # matching_path for Resource = [template_section, source_id, ... , key, value]
+ key = a member of SUPPORTED_FN_CONNECTION_KEYS """
+ template_section = matching_path[0]
+ source_id = matching_path[1]
+ value = matching_path[-1]
+ attrs_starting_index = 3 if template_section == TemplateSections.RESOURCES else 2
+ attributes = matching_path[attrs_starting_index:-2]
+ return source_id, value, attributes
+
+ def _add_fn_connections(self, key) -> None:
+ if key not in self.SUPPORTED_FN_CONNECTION_KEYS:
+ return
+ extract_target_id_func = self._connection_key_func.get(key, None)
+ if not ismethod(extract_target_id_func):
+ return
+
+ for file_path, template in self._templates.items():
+ matching_paths = template.search_deep_keys(key)
+ for matching_path in matching_paths:
+ source_id, value, attributes = self._extract_source_value_attrs(matching_path)
+ target_id = extract_target_id_func(template, value)
+ if target_id:
+ origin_vertex_index, dest_vertex_index, label = self._extract_origin_dest_label(
+ file_path, source_id, target_id, attributes)
+ if origin_vertex_index is not None and dest_vertex_index is not None:
+ self._create_edge(origin_vertex_index, dest_vertex_index, label)
+
+ def _fetch_if_target_id(self, template, value) -> Optional[int]:
+ target_id = None
+ # value = [condition_name, value_if_true, value_if_false]
+ if isinstance(value, list) and len(value) == 3 and (self._is_condition(template, value[0])):
+ target_id = value[0]
+ return target_id
+
+ def _fetch_getatt_target_id(self, template, value) -> Optional[int]:
+ """ might be one of the 2 following notations:
+ 1st: { "Fn::GetAtt" : [ "logicalNameOfResource", "attributeName" ] }
+ 2nd: { "!GetAtt" : "logicalNameOfResource.attributeName" } """
+ target_id = None
+
+ # Fn::GetAtt notation
+ if isinstance(value, list) and len(value) == 2 and (self._is_resource(template, value[0])):
+ target_id = value[0]
+
+ # !GetAtt notation
+ if isinstance(value, str) and '.' in value:
+ resource_id = value.split('.')[0]
+ if self._is_resource(template, resource_id):
+ target_id = resource_id
+
+ return target_id
+
+ def _fetch_ref_target_id(self, template, value) -> Optional[int]:
+ target_id = None
+ # value might be a string or a list of strings
+ if isinstance(value, (str, int)) \
+ and ((self._is_resource(template, value)) or (self._is_parameter(template, value))):
+ target_id = value
+ return target_id
+
+ def _fetch_findinmap_target_id(self, template, value) -> Optional[int]:
+ target_id = None
+ # value = [ MapName, TopLevelKey, SecondLevelKey ]
+ if isinstance(value, list) and len(value) == 3 and (self._is_mapping(template, value[0])):
+ target_id = value[0]
+ return target_id
+
+ def _add_fn_sub_connections(self):
+ for file_path, template in self._templates.items():
+ # add edges for "Fn::Sub" tags. E.g. { "Fn::Sub": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}" }
+ sub_objs = template.search_deep_keys(IntrinsicFunctions.SUB)
+ for sub_obj in sub_objs:
+ sub_parameters = []
+ sub_parameter_values = {}
+ source_id, value, attributes = self._extract_source_value_attrs(sub_obj)
+
+ if isinstance(value, list):
+ if not value:
+ continue
+ if len(value) == 2:
+ sub_parameter_values = value[1]
+ sub_parameters = self._find_fn_sub_parameter(value[0])
+ elif isinstance(value, str):
+ sub_parameters = self._find_fn_sub_parameter(value)
+
+ for sub_parameter in sub_parameters:
+ if sub_parameter not in sub_parameter_values:
+ if '.' in sub_parameter:
+ sub_parameter = sub_parameter.split('.')[0]
+ origin_vertex_index, dest_vertex_index, label = self._extract_origin_dest_label(
+ file_path, source_id, sub_parameter, attributes)
+ if origin_vertex_index is not None and dest_vertex_index is not None:
+ self._create_edge(origin_vertex_index, dest_vertex_index, label)
+
+ def _extract_origin_dest_label(self, file_path, source_id, target_id, attributes):
+ origin_vertex_index = self._vertices_indexes.get(file_path, {}).get(source_id, None)
+ dest_vertex_index = self._vertices_indexes.get(file_path, {}).get(target_id, None)
+ attributes_joined = '.'.join(map(str, attributes)) # mapping all attributes to str because one of the attrs might be an int
+ return origin_vertex_index, dest_vertex_index, attributes_joined
+
+ @staticmethod
+ def _find_fn_sub_parameter(string):
+ """Search string for tokenized fields"""
+ regex = re.compile(r'\${([a-zA-Z0-9.]*)}')
+ return regex.findall(string)
+
+ def _create_edges(self) -> None:
+ self._add_resource_attr_connections(ResourceAttributes.DEPENDS_ON)
+ self._add_resource_attr_connections(IntrinsicFunctions.CONDITION)
+ self._add_fn_connections(IntrinsicFunctions.GET_ATT)
+ self._add_fn_connections(ConditionFunctions.IF)
+ self._add_fn_connections(IntrinsicFunctions.REF)
+ self._add_fn_connections(IntrinsicFunctions.FIND_IN_MAP)
+ self._add_fn_sub_connections()
+
+ def _create_edge(self, origin_vertex_index: int, dest_vertex_index: int, label: str) -> None:
+ if origin_vertex_index == dest_vertex_index:
+ return
+ edge = Edge(origin_vertex_index, dest_vertex_index, label)
+ if edge not in self._edges_set:
+ self._edges_set.add(edge)
+ self.edges.append(edge)
+ self.out_edges[origin_vertex_index].append(edge)
+ self.in_edges[dest_vertex_index].append(edge)
+
+ @staticmethod
+ def _is_parameter(template, identifier):
+ """Check if the identifier is that of a Parameter"""
+ if isinstance(identifier, str):
+ return template.template.get(TemplateSections.PARAMETERS, {}).get(identifier, {})
+ return False
+
+ @staticmethod
+ def _is_mapping(template, identifier):
+ """Check if the identifier is that of a Mapping"""
+ if isinstance(identifier, str):
+ return template.template.get(TemplateSections.MAPPINGS, {}).get(identifier, {})
+ return False
+
+ @staticmethod
+ def _is_condition(template, identifier):
+ """Check if the identifier is that of a Condition"""
+ if isinstance(identifier, str):
+ return template.template.get(TemplateSections.CONDITIONS, {}).get(identifier, {})
+ return False
+
+ @staticmethod
+ def _is_resource(template, identifier):
+ """Check if the identifier is that of a Resource"""
+ if isinstance(identifier, str):
+ return template.template.get(TemplateSections.RESOURCES, {}).get(identifier, {})
+ return False
+
+
+def get_only_dict_items(origin_dict: Dict[str, Any]) -> Dict[str, Dict[str, Any]]:
+ return {key: value for key, value in origin_dict.items() if isinstance(value, dict)}
diff --git a/checkov/cloudformation/graph_manager.py b/checkov/cloudformation/graph_manager.py
new file mode 100644
index 0000000000..4561f0a77c
--- /dev/null
+++ b/checkov/cloudformation/graph_manager.py
@@ -0,0 +1,50 @@
+import json
+import logging
+from typing import List, Dict, Type, Optional, Tuple
+
+from checkov.cloudformation.cfn_utils import get_folder_definitions
+from checkov.cloudformation.context_parser import ContextParser
+from checkov.cloudformation.graph_builder.graph_to_definitions import convert_graph_vertices_to_definitions
+from checkov.cloudformation.graph_builder.local_graph import CloudformationLocalGraph
+from checkov.cloudformation.parser.node import dict_node
+from checkov.common.graph.db_connectors.db_connector import DBConnector
+from checkov.common.graph.graph_manager import GraphManager
+
+
+class CloudformationGraphManager(GraphManager):
+ def __init__(self, db_connector: DBConnector, source: str = "CloudFormation") -> None:
+ super().__init__(db_connector=db_connector, parser=None, source=source)
+
+ def build_graph_from_source_directory(
+ self,
+ source_dir: str,
+ render_variables: bool = True,
+ local_graph_class: Type[CloudformationLocalGraph] = CloudformationLocalGraph,
+ parsing_errors: Optional[Dict[str, Exception]] = None,
+ download_external_modules: bool = False,
+ excluded_paths: Optional[List[str]] = None,
+ ) -> Tuple[CloudformationLocalGraph, Dict[str, dict_node]]:
+ logging.info("[CloudformationGraphManager] Parsing files in source dir {source_dir}")
+ definitions, definitions_raw = get_folder_definitions(source_dir, excluded_paths)
+ local_graph = self.build_graph_from_definitions(definitions, render_variables)
+ rendered_definitions, _ = convert_graph_vertices_to_definitions(local_graph.vertices, source_dir)
+
+ # TODO: replace with real graph rendering
+ for cf_file in rendered_definitions.keys():
+ file_definition = rendered_definitions.get(cf_file, None)
+ file_definition_raw = definitions_raw.get(cf_file, None)
+ if file_definition is not None and file_definition_raw is not None:
+ cf_context_parser = ContextParser(cf_file, file_definition, file_definition_raw)
+ logging.debug(
+ "Template Dump for {}: {}".format(cf_file, json.dumps(file_definition, indent=2, default=str))
+ )
+ cf_context_parser.evaluate_default_refs()
+ return local_graph, rendered_definitions
+
+ def build_graph_from_definitions(
+ self, definitions: Dict[str, dict_node], render_variables: bool = False
+ ) -> CloudformationLocalGraph:
+ local_graph = CloudformationLocalGraph(definitions, source=self.source)
+ local_graph.build_graph(render_variables=render_variables)
+
+ return local_graph
diff --git a/checkov/cloudformation/parser/__init__.py b/checkov/cloudformation/parser/__init__.py
index 94fb26e928..9417c3e18a 100644
--- a/checkov/cloudformation/parser/__init__.py
+++ b/checkov/cloudformation/parser/__init__.py
@@ -1,17 +1,17 @@
import logging
+from json.decoder import JSONDecodeError
+from typing import Tuple, Optional, List, Union
-try:
- from json.decoder import JSONDecodeError
-except ImportError:
- JSONDecodeError = ValueError
-from yaml.parser import ParserError, ScannerError
-from yaml import YAMLError
from checkov.cloudformation.parser import cfn_yaml, cfn_json
+from checkov.cloudformation.parser.node import dict_node
+from checkov.cloudformation.parser.cfn_keywords import TemplateSections
+from yaml.parser import ScannerError
+from yaml import YAMLError
LOGGER = logging.getLogger(__name__)
-def parse(filename):
+def parse(filename: str) -> Union[Tuple[dict_node, List[Tuple[int, str]]], Tuple[None, None]]:
"""
Decode filename into an object
"""
@@ -21,21 +21,17 @@ def parse(filename):
(template, template_lines) = cfn_yaml.load(filename)
except IOError as e:
if e.errno == 2:
- LOGGER.error('Template file not found: %s', filename)
+ LOGGER.error("Template file not found: %s", filename)
elif e.errno == 21:
- LOGGER.error('Template references a directory, not a file: %s',
- filename)
+ LOGGER.error("Template references a directory, not a file: %s", filename)
elif e.errno == 13:
- LOGGER.error('Permission denied when accessing template file: %s',
- filename)
+ LOGGER.error("Permission denied when accessing template file: %s", filename)
except UnicodeDecodeError as err:
- LOGGER.error('Cannot read file contents: %s', filename)
+ LOGGER.error("Cannot read file contents: %s", filename)
except cfn_yaml.CfnParseError as err:
pass
except ScannerError as err:
- if err.problem in [
- 'found character \'\\t\' that cannot start any token',
- 'found unknown escape character']:
+ if err.problem in ["found character '\\t' that cannot start any token", "found unknown escape character"]:
try:
(template, template_lines) = cfn_json.load(filename)
except cfn_json.JSONDecodeError:
@@ -43,11 +39,16 @@ def parse(filename):
except JSONDecodeError:
pass
except Exception as json_err: # pylint: disable=W0703
- LOGGER.error(
- 'Template %s is malformed: %s', filename, err.problem)
- LOGGER.error('Tried to parse %s as JSON but got error: %s',
- filename, str(json_err))
+ LOGGER.error("Template %s is malformed: %s", filename, err.problem)
+ LOGGER.error("Tried to parse %s as JSON but got error: %s", filename, str(json_err))
except YAMLError as err:
pass
+ if isinstance(template, dict):
+ resources = template.get(TemplateSections.RESOURCES.value, None)
+ if resources:
+ if '__startline__' in resources:
+ del resources['__startline__']
+ if '__endline__' in resources:
+ del resources['__endline__']
return template, template_lines
diff --git a/checkov/cloudformation/parser/cfn_keywords.py b/checkov/cloudformation/parser/cfn_keywords.py
new file mode 100644
index 0000000000..b891162901
--- /dev/null
+++ b/checkov/cloudformation/parser/cfn_keywords.py
@@ -0,0 +1,49 @@
+from dataclasses import dataclass
+from enum import Enum
+
+
+@dataclass
+class IntrinsicFunctions:
+ BASE64 = "Fn::Base64"
+ CIDR = "Fn::Cidr"
+ FIND_IN_MAP = "Fn::FindInMap"
+ GET_ATT = "Fn::GetAtt"
+ GET_AZS = "Fn::GetAZs"
+ IMPORT_VALUE = "Fn::ImportValue"
+ JOIN = "Fn::Join"
+ SELECT = "Fn::Select"
+ SPLIT = "Fn::Split"
+ SUB = "Fn::Sub"
+ TRANSFORM = "Fn::Transform"
+ REF = "Ref"
+ CONDITION = "Condition"
+
+
+@dataclass
+class ConditionFunctions:
+ AND = "Fn::And"
+ EQUALS = "Fn::Equals"
+ IF = "Fn::If"
+ NOT = "Fn::Not"
+ OR = "Fn::Or"
+
+
+@dataclass
+class ResourceAttributes:
+ CREATION_POLICY = "CreationPolicy"
+ DELETION_POLICY = "DeletionPolicy"
+ DEPENDS_ON = "DependsOn"
+ METADATA = "Metadata"
+ UPDATE_POLICY = "UpdatePolicy"
+ UPDATE_REPLACE_POLICY = "UpdateReplacePolicy"
+
+
+class TemplateSections(str, Enum):
+ RESOURCES = "Resources"
+ METADATA = "Metadata"
+ PARAMETERS = "Parameters"
+ RULES = "Rules"
+ MAPPINGS = "Mappings"
+ CONDITIONS = "Conditions"
+ TRANSFORM = "Transform"
+ OUTPUTS = "Outputs"
diff --git a/checkov/cloudformation/runner.py b/checkov/cloudformation/runner.py
index 7306b6e446..9d7440e060 100644
--- a/checkov/cloudformation/runner.py
+++ b/checkov/cloudformation/runner.py
@@ -1,88 +1,159 @@
+import json
import logging
import os
+from typing import Optional, List
+from checkov.cloudformation import cfn_utils
+from checkov.cloudformation.cfn_utils import create_definitions, build_definitions_context
from checkov.cloudformation.checks.resource.registry import cfn_registry
-from checkov.cloudformation.parser import parse
+from checkov.cloudformation.context_parser import ContextParser
+from checkov.cloudformation.parser.cfn_keywords import TemplateSections
+from checkov.cloudformation.graph_builder.graph_to_definitions import convert_graph_vertices_to_definitions
+from checkov.cloudformation.graph_builder.local_graph import CloudformationLocalGraph
+from checkov.cloudformation.graph_manager import CloudformationGraphManager
+from checkov.common.checks_infra.registry import get_graph_checks_registry
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.common.graph.graph_builder import CustomAttributes
+from checkov.common.output.graph_record import GraphRecord
from checkov.common.output.record import Record
-from checkov.common.output.report import Report
-from checkov.common.runners.base_runner import BaseRunner, filter_ignored_directories
+from checkov.common.output.report import Report, merge_reports
+from checkov.common.runners.base_runner import BaseRunner
from checkov.runner_filter import RunnerFilter
-from checkov.cloudformation.parser.node import dict_node
-from checkov.cloudformation.context_parser import ContextParser
-
-CF_POSSIBLE_ENDINGS = [".yml", ".yaml", ".json", ".template"]
class Runner(BaseRunner):
check_type = "cloudformation"
- def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True):
+ def __init__(
+ self,
+ db_connector=NetworkxConnector(),
+ source="CloudFormation",
+ graph_class=CloudformationLocalGraph,
+ graph_manager=None,
+ external_registries=None,
+ ):
+ self.external_registries = [] if external_registries is None else external_registries
+ self.graph_class = graph_class
+ self.graph_manager = (
+ graph_manager
+ if graph_manager is not None
+ else CloudformationGraphManager(source=source, db_connector=db_connector)
+ )
+ self.definitions_raw = {}
+ self.graph_registry = get_graph_checks_registry(self.check_type)
+
+ def run(
+ self,
+ root_folder: str,
+ external_checks_dir: Optional[List[str]] = None,
+ files: Optional[List[str]] = None,
+ runner_filter: RunnerFilter = RunnerFilter(),
+ collect_skip_comments: bool = True,
+ ) -> Report:
report = Report(self.check_type)
- definitions = {}
- definitions_raw = {}
- parsing_errors = {}
- files_list = []
- if external_checks_dir:
- for directory in external_checks_dir:
- cfn_registry.load_external_checks(directory, runner_filter)
-
- if files:
- for file in files:
- (definitions[file], definitions_raw[file]) = parse(file)
-
- if root_folder:
- for root, d_names, f_names in os.walk(root_folder):
- filter_ignored_directories(d_names)
- for file in f_names:
- file_ending = os.path.splitext(file)[1]
- if file_ending in CF_POSSIBLE_ENDINGS:
- files_list.append(os.path.join(root, file))
-
- for file in files_list:
- relative_file_path = f'/{os.path.relpath(file, os.path.commonprefix((root_folder, file)))}'
- try:
- (definitions[relative_file_path], definitions_raw[relative_file_path]) = parse(file)
- except TypeError:
- logging.info(f'CloudFormation skipping {file} as it is not a valid CF template')
-
- # Filter out empty files that have not been parsed successfully, and filter out non-CF template files
- definitions = {k: v for k, v in definitions.items() if v and isinstance(v, dict_node) and v.__contains__("Resources") and isinstance(v["Resources"], dict_node)}
- definitions_raw = {k: v for k, v in definitions_raw.items() if k in definitions.keys()}
-
- for cf_file in definitions.keys():
-
- # There are a few cases here. If -f was used, there could be a leading / because it's an absolute path,
- # or there will be no leading slash; root_folder will always be none.
- # If -d is used, root_folder will be the value given, and -f will start with a / (hardcoded above).
- # The goal here is simply to get a valid path to the file (which cf_file does not always give).
- if cf_file[0] == '/':
- path_to_convert = (root_folder + cf_file) if root_folder else cf_file
- else:
- path_to_convert = (os.path.join(root_folder, cf_file)) if root_folder else cf_file
-
- file_abs_path = os.path.abspath(path_to_convert)
- if isinstance(definitions[cf_file], dict_node) and 'Resources' in definitions[cf_file].keys():
- cf_context_parser = ContextParser(cf_file, definitions[cf_file], definitions_raw[cf_file])
- logging.debug("Template Dump for {}: {}".format(cf_file, definitions[cf_file], indent=2))
+
+ if self.context is None or self.definitions is None or self.breadcrumbs is None:
+ self.definitions, self.definitions_raw = create_definitions(root_folder, files, runner_filter)
+ if external_checks_dir:
+ for directory in external_checks_dir:
+ cfn_registry.load_external_checks(directory)
+ self.graph_registry.load_external_checks(directory)
+ self.context = build_definitions_context(self.definitions, self.definitions_raw, root_folder)
+
+ logging.info("creating cloudformation graph")
+ local_graph = self.graph_manager.build_graph_from_definitions(self.definitions)
+ self.graph_manager.save_graph(local_graph)
+ self.definitions, self.breadcrumbs = convert_graph_vertices_to_definitions(local_graph.vertices, root_folder)
+
+ # TODO: replace with real graph rendering
+ for cf_file in self.definitions.keys():
+ file_definition = self.definitions.get(cf_file, None)
+ file_definition_raw = self.definitions_raw.get(cf_file, None)
+ if file_definition is not None and file_definition_raw is not None:
+ cf_context_parser = ContextParser(cf_file, file_definition, file_definition_raw)
+ logging.debug(
+ "Template Dump for {}: {}".format(cf_file, json.dumps(file_definition, indent=2, default=str))
+ )
cf_context_parser.evaluate_default_refs()
- for resource_name, resource in definitions[cf_file]['Resources'].items():
- resource_id = cf_context_parser.extract_cf_resource_id(resource, resource_name)
+
+ # run checks
+ self.check_definitions(root_folder, runner_filter, report)
+
+ # run graph checks
+ graph_report = self.get_graph_checks_report(root_folder, runner_filter)
+ merge_reports(report, graph_report)
+
+ return report
+
+ def check_definitions(self, root_folder, runner_filter, report):
+ for file_abs_path, definition in self.definitions.items():
+
+ cf_file = f"/{os.path.relpath(file_abs_path, root_folder)}"
+
+ if isinstance(definition, dict) and TemplateSections.RESOURCES in definition.keys():
+ for resource_name, resource in definition[TemplateSections.RESOURCES].items():
+ resource_id = ContextParser.extract_cf_resource_id(resource, resource_name)
# check that the resource can be parsed as a CF resource
if resource_id:
- entity_lines_range, entity_code_lines = cf_context_parser.extract_cf_resource_code_lines(resource)
+ resource_context = self.context[file_abs_path][
+ TemplateSections.RESOURCES][resource_name]
+ entity_lines_range = [resource_context['start_line'], resource_context['end_line']]
+ entity_code_lines = resource_context['code_lines']
if entity_lines_range and entity_code_lines:
# TODO - Variable Eval Message!
variable_evaluations = {}
-
skipped_checks = ContextParser.collect_skip_comments(entity_code_lines)
-
- results = cfn_registry.scan(cf_file, {resource_name: resource}, skipped_checks,
- runner_filter)
+ entity = {resource_name: resource}
+ results = cfn_registry.scan(cf_file, entity, skipped_checks, runner_filter)
+ tags = cfn_utils.get_resource_tags(entity)
for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
- code_block=entity_code_lines, file_path=cf_file,
- file_line_range=entity_lines_range,
- resource=resource_id, evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ record = Record(
+ check_id=check.id,
+ bc_check_id=check.bc_id,
+ check_name=check.name,
+ check_result=check_result,
+ code_block=entity_code_lines,
+ file_path=cf_file,
+ file_line_range=entity_lines_range,
+ resource=resource_id,
+ evaluations=variable_evaluations,
+ check_class=check.__class__.__module__,
+ file_abs_path=file_abs_path,
+ entity_tags=tags,
+ )
report.add_record(record=record)
- return report
\ No newline at end of file
+
+ def get_graph_checks_report(self, root_folder: str, runner_filter: RunnerFilter) -> Report:
+ report = Report(self.check_type)
+ checks_results = self.run_graph_checks_results(runner_filter)
+
+ for check, check_results in checks_results.items():
+ for check_result in check_results:
+ entity = check_result["entity"]
+ entity_file_abs_path = entity.get(CustomAttributes.FILE_PATH)
+ entity_file_path = scanned_file = f"/{os.path.relpath(entity_file_abs_path, root_folder)}"
+ entity_name = entity.get(CustomAttributes.BLOCK_NAME).split(".")[1]
+ entity_context = self.context[entity_file_abs_path][TemplateSections.RESOURCES][
+ entity_name
+ ]
+
+ record = Record(
+ check_id=check.id,
+ check_name=check.name,
+ check_result=check_result,
+ code_block=entity_context.get("code_lines"),
+ file_path=entity_file_path,
+ file_line_range=[entity_context.get("start_line"), entity_context.get("end_line")],
+ resource=entity.get(CustomAttributes.ID),
+ evaluations={},
+ check_class=check.__class__.__module__,
+ file_abs_path=entity_file_abs_path,
+ entity_tags={} if not entity.get("Tags") else cfn_utils.parse_entity_tags(entity.get("Tags")),
+ )
+ if self.breadcrumbs:
+ breadcrumb = self.breadcrumbs.get(record.file_path, {}).get(record.resource)
+ if breadcrumb:
+ record = GraphRecord(record, breadcrumb)
+
+ report.add_record(record=record)
+ return report
diff --git a/checkov/common/bridgecrew/bc_source.py b/checkov/common/bridgecrew/bc_source.py
new file mode 100644
index 0000000000..2193aae90e
--- /dev/null
+++ b/checkov/common/bridgecrew/bc_source.py
@@ -0,0 +1,32 @@
+from dataclasses import dataclass
+
+
+class SourceType:
+ def __init__(self, name: str, upload_results: bool):
+ self.name = name
+ self.upload_results = upload_results
+
+
+@dataclass
+class BCSourceType:
+ VSCODE = 'vscode'
+ CLI = 'cli'
+ KUBERNETES_WORKLOADS = 'kubernetesWorkloads'
+ DISABLED = 'disabled' # use this as a placeholder for generic no-upload logic
+
+
+SourceTypes = {
+ BCSourceType.VSCODE: SourceType(BCSourceType.VSCODE, False),
+ BCSourceType.CLI: SourceType(BCSourceType.CLI, True),
+ BCSourceType.KUBERNETES_WORKLOADS: SourceType(BCSourceType.KUBERNETES_WORKLOADS, True),
+ BCSourceType.DISABLED: SourceType(BCSourceType.VSCODE, False)
+}
+
+
+def get_source_type(source: str):
+ # helper method to get the source type with a default - using dict.get is ugly; you have to do:
+ # SourceTypes.get(xyz, SourceTypes[BCSourceType.Disabled])
+ if source in SourceTypes:
+ return SourceTypes[source]
+ else:
+ return SourceTypes[BCSourceType.DISABLED]
diff --git a/checkov/common/bridgecrew/ci_variables.py b/checkov/common/bridgecrew/ci_variables.py
new file mode 100644
index 0000000000..aeed1902b4
--- /dev/null
+++ b/checkov/common/bridgecrew/ci_variables.py
@@ -0,0 +1,15 @@
+import os
+
+BC_FROM_BRANCH = os.getenv('BC_FROM_BRANCH', "")
+BC_TO_BRANCH = os.getenv('BC_TO_BRANCH', "")
+if not BC_TO_BRANCH: # support flow of direct commit from the branch into the same branch
+ BC_TO_BRANCH = BC_FROM_BRANCH
+BC_PR_ID = os.getenv('BC_PR_ID', "")
+BC_PR_URL = os.getenv('BC_PR_URL', "")
+BC_COMMIT_HASH = os.getenv('BC_COMMIT_HASH', "")
+BC_COMMIT_URL = os.getenv('BC_COMMIT_URL', "")
+BC_AUTHOR_NAME = os.getenv('BC_AUTHOR_NAME', "")
+BC_AUTHOR_URL = os.getenv('BC_AUTHOR_URL', "")
+BC_RUN_ID = os.getenv('BC_RUN_ID', "")
+BC_RUN_URL = os.getenv('BC_RUN_URL', "")
+BC_REPOSITORY_URL = os.getenv('BC_REPOSITORY_URL', "")
diff --git a/tests/terraform/runner/resources/valid_tf_only_failed_checks/__init__.py b/checkov/common/bridgecrew/image_scanning/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/valid_tf_only_failed_checks/__init__.py
rename to checkov/common/bridgecrew/image_scanning/__init__.py
diff --git a/checkov/common/bridgecrew/image_scanning/docker_image_scanning_integration.py b/checkov/common/bridgecrew/image_scanning/docker_image_scanning_integration.py
new file mode 100644
index 0000000000..c5d6844721
--- /dev/null
+++ b/checkov/common/bridgecrew/image_scanning/docker_image_scanning_integration.py
@@ -0,0 +1,65 @@
+import logging
+import os
+import platform
+import stat
+import requests
+from datetime import datetime, timedelta
+
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.bridgecrew.integration_features.base_integration_feature import BC_API_URL
+from checkov.common.util.data_structures_utils import merge_dicts
+from checkov.common.util.http_utils import get_auth_header, get_default_get_headers, get_default_post_headers
+
+
+class DockerImageScanningIntegration:
+ docker_image_scanning_base_url = f"{BC_API_URL}/vulnerabilities/docker-images"
+
+ def get_bc_api_key(self):
+ return bc_integration.bc_api_key
+
+ def get_proxy_address(self):
+ return f"{self.docker_image_scanning_base_url}/twistcli/proxy"
+
+ def download_twistcli(self, cli_file_name):
+ os_type = platform.system().lower()
+ headers = merge_dicts(
+ get_default_get_headers(bc_integration.bc_source, bc_integration.bc_source_version),
+ get_auth_header(bc_integration.bc_api_key)
+ )
+ response = requests.request('GET', f"{self.docker_image_scanning_base_url}/twistcli/download?os={os_type}", headers=headers)
+ open(cli_file_name, 'wb').write(response.content)
+ st = os.stat(cli_file_name)
+ os.chmod(cli_file_name, st.st_mode | stat.S_IEXEC)
+ logging.debug(f'TwistCLI downloaded and has execute permission')
+
+ def report_results(self, docker_image_name, dockerfile_path, dockerfile_content, twistcli_scan_result):
+ headers = merge_dicts(
+ get_default_post_headers(bc_integration.bc_source, bc_integration.bc_source_version),
+ get_auth_header(bc_integration.bc_api_key)
+ )
+ vulnerabilities = list(map(lambda x: {
+ 'cveId': x['id'],
+ 'status': x.get('status', 'open'),
+ 'severity': x['severity'],
+ 'packageName': x['packageName'],
+ 'packageVersion': x['packageVersion'],
+ 'link': x['link'],
+ 'cvss': x.get('cvss'),
+ 'vector': x.get('vector'),
+ 'description': x.get('description'),
+ 'riskFactors': x.get('riskFactors'),
+ 'publishedDate': x.get('publishedDate') or (datetime.now() - timedelta(days=x.get('publishedDays', 0))).isoformat()
+ }, twistcli_scan_result['results'][0].get('vulnerabilities', [])))
+ payload = {
+ 'sourceId': bc_integration.repo_id,
+ 'branch': bc_integration.repo_branch,
+ 'dockerImageName': docker_image_name,
+ 'dockerFilePath': dockerfile_path,
+ 'dockerFileContent': dockerfile_content,
+ 'sourceType': bc_integration.bc_source.name,
+ 'vulnerabilities': vulnerabilities
+ }
+ response = requests.request('POST', f"{self.docker_image_scanning_base_url}/report", headers=headers, json=payload)
+ response.raise_for_status()
+
+docker_image_scanning_integration = DockerImageScanningIntegration()
diff --git a/checkov/common/bridgecrew/image_scanning/image_scanner.py b/checkov/common/bridgecrew/image_scanning/image_scanner.py
new file mode 100644
index 0000000000..522ff133e8
--- /dev/null
+++ b/checkov/common/bridgecrew/image_scanning/image_scanner.py
@@ -0,0 +1,59 @@
+import logging
+import subprocess # nosec
+import docker
+import json
+import os
+
+from checkov.common.bridgecrew.image_scanning.docker_image_scanning_integration import docker_image_scanning_integration
+
+TWISTCLI_FILE_NAME = 'twistcli'
+DOCKER_IMAGE_SCAN_RESULT_FILE_NAME = 'docker-image-scan-results.json'
+
+
+def _get_docker_image_name(docker_image_id):
+ try:
+ docker_client = docker.from_env()
+ return docker_client.images.get(docker_image_id).attrs['RepoDigests'][0].split('@')[0]
+ except Exception as e:
+ logging.error(f"docker image needs to have repository")
+ raise e
+
+
+def _get_dockerfile_content(dockerfile_path):
+ try:
+ with open(dockerfile_path) as f:
+ return f.read()
+ except FileNotFoundError as e:
+ logging.error(f"Path to Dockerfile is invalid\n{e}")
+ raise e
+ except Exception as e:
+ logging.error(f"Failed to read Dockerfile content\n{e}")
+ raise e
+
+
+class ImageScanner:
+ def scan(self, docker_image_id, dockerfile_path):
+ try:
+ docker_image_name = _get_docker_image_name(docker_image_id)
+ dockerfile_content = _get_dockerfile_content(dockerfile_path)
+ docker_image_scanning_integration.download_twistcli(TWISTCLI_FILE_NAME)
+
+ command_args = f"./{TWISTCLI_FILE_NAME} images scan --address {docker_image_scanning_integration.get_proxy_address()} --token {docker_image_scanning_integration.get_bc_api_key()} --details --output-file {DOCKER_IMAGE_SCAN_RESULT_FILE_NAME} {docker_image_id}".split()
+ subprocess.run(command_args, check=True) # nosec
+ logging.info(f'TwistCLI ran successfully on image {docker_image_id}')
+
+ with open(DOCKER_IMAGE_SCAN_RESULT_FILE_NAME) as docker_image_scan_result_file:
+ scan_result = json.load(docker_image_scan_result_file)
+
+ docker_image_scanning_integration.report_results(docker_image_name, dockerfile_path, dockerfile_content, twistcli_scan_result=scan_result)
+ logging.info(f'Docker image scanning results reported to the platform')
+
+ os.remove(TWISTCLI_FILE_NAME)
+ logging.info(f'twistcli file removed')
+ except Exception as e:
+ logging.error(f"Failed to scan docker image\n{e}")
+ raise e
+
+
+
+image_scanner = ImageScanner()
diff --git a/checkov/common/bridgecrew/integration_features/__init__.py b/checkov/common/bridgecrew/integration_features/__init__.py
new file mode 100644
index 0000000000..33035b6334
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/__init__.py
@@ -0,0 +1 @@
+from checkov.common.bridgecrew.integration_features.features import *
\ No newline at end of file
diff --git a/checkov/common/bridgecrew/integration_features/base_integration_feature.py b/checkov/common/bridgecrew/integration_features/base_integration_feature.py
new file mode 100644
index 0000000000..df359dfa6e
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/base_integration_feature.py
@@ -0,0 +1,38 @@
+import os
+from abc import ABC, abstractmethod
+
+from checkov.common.bridgecrew.integration_features.integration_feature_registry import integration_feature_registry
+
+BC_API_URL = os.getenv('BC_API_URL', "https://www.bridgecrew.cloud/api/v1")
+
+class BaseIntegrationFeature(ABC):
+ integrations_api_url = f"{BC_API_URL}/integrations/types/checkov"
+ guidelines_api_url = f"{BC_API_URL}/guidelines"
+ onboarding_url = f"{BC_API_URL}/signup/checkov"
+ api_token_url = f"{BC_API_URL}/integrations/apiToken"
+ suppressions_url = f"{BC_API_URL}/suppressions"
+ policies_url = f"{BC_API_URL}/policies/table/data"
+ fixes_url = f"{BC_API_URL}/fixes/checkov"
+
+ def __init__(self, bc_integration, order):
+ self.bc_integration = bc_integration
+ bc_integration.setup_http_manager()
+ self.order = order
+ integration_feature_registry.register(self)
+
+ @abstractmethod
+ def is_valid(self):
+ raise NotImplementedError()
+
+ def pre_scan(self):
+ """Runs before any runners"""
+ pass
+
+ def pre_runner(self):
+ """Runs before each runner"""
+ pass
+
+ def post_runner(self, scan_reports):
+ """Runs after each runner completes"""
+ pass
+
diff --git a/checkov/common/bridgecrew/integration_features/features/__init__.py b/checkov/common/bridgecrew/integration_features/features/__init__.py
new file mode 100644
index 0000000000..13632cb9e7
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/features/__init__.py
@@ -0,0 +1,4 @@
+from os.path import dirname, basename, isfile, join
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
diff --git a/checkov/common/bridgecrew/integration_features/features/custom_policies_integration.py b/checkov/common/bridgecrew/integration_features/features/custom_policies_integration.py
new file mode 100644
index 0000000000..9c2f294aa9
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/features/custom_policies_integration.py
@@ -0,0 +1,61 @@
+import logging
+
+import requests
+
+from checkov.common.bridgecrew.integration_features.base_integration_feature import BaseIntegrationFeature
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.checks_infra.registry import Registry, get_graph_checks_registry
+from checkov.common.util.data_structures_utils import merge_dicts
+from checkov.common.util.http_utils import get_default_get_headers, get_auth_header, extract_error_message
+
+
+class CustomPoliciesIntegration(BaseIntegrationFeature):
+ def __init__(self, bc_integration):
+ super().__init__(bc_integration, order=0)
+ self.policies = {}
+ self.platform_policy_parser = NXGraphCheckParser()
+
+ def is_valid(self):
+ return self.bc_integration.is_integration_configured() and not self.bc_integration.skip_policy_download
+
+ def pre_scan(self):
+ self.policies = self._get_policies_from_platform()
+ for policy in self.policies:
+ converted_check = self._convert_raw_check(policy)
+ resource_types = Registry._get_resource_types(converted_check['metadata'])
+ check = self.platform_policy_parser.parse_raw_check(converted_check, resources_types=resource_types)
+ get_graph_checks_registry("terraform").checks.append(check)
+ logging.debug(f'Found {len(self.policies)} custom policies from the platform.')
+
+ @staticmethod
+ def _convert_raw_check(policy):
+ metadata = {
+ 'id': policy['id'],
+ 'name': policy['title'],
+ 'category': policy['category'],
+ 'scope': {
+ 'provider': policy['provider']
+ }
+ }
+ check = {
+ 'metadata': metadata,
+ 'definition': policy['conditionQuery']
+ }
+ return check
+
+ def _get_policies_from_platform(self):
+ headers = merge_dicts(get_default_get_headers(self.bc_integration.bc_source, self.bc_integration.bc_source_version),
+ get_auth_header(self.bc_integration.bc_api_key))
+ response = requests.request('GET', self.policies_url, headers=headers)
+
+ if response.status_code != 200:
+ error_message = extract_error_message(response)
+ raise Exception(f'Get custom policies request failed with response code {response.status_code}: {error_message}')
+
+ policies = response.json().get('data', [])
+ policies = [p for p in policies if p['isCustom']]
+ return policies
+
+
+integration = CustomPoliciesIntegration(bc_integration)
diff --git a/checkov/common/bridgecrew/integration_features/features/fixes_integration.py b/checkov/common/bridgecrew/integration_features/features/fixes_integration.py
new file mode 100644
index 0000000000..fdd8868c3f
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/features/fixes_integration.py
@@ -0,0 +1,96 @@
+import logging
+from itertools import groupby
+
+import json
+import os
+import requests
+
+from checkov.common.bridgecrew.integration_features.base_integration_feature import BaseIntegrationFeature
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.util.data_structures_utils import merge_dicts
+from checkov.common.util.http_utils import get_auth_header, extract_error_message, \
+ get_default_post_headers
+
+SUPPORTED_FIX_FRAMEWORKS = ['terraform', 'cloudformation']
+
+
+class FixesIntegration(BaseIntegrationFeature):
+
+ def __init__(self, bc_integration):
+ super().__init__(bc_integration, order=10)
+
+ def is_valid(self):
+ return self.bc_integration.is_integration_configured() and not self.bc_integration.skip_fixes
+
+ def post_runner(self, scan_report):
+ if scan_report.check_type not in SUPPORTED_FIX_FRAMEWORKS:
+ return
+ self._get_platform_fixes(scan_report)
+
+ def _get_platform_fixes(self, scan_report):
+
+ # We might want to convert this to one call for all results (all files), but then we would also have to deal
+ # with repo size issues. Because the primary use case for this at the moment is VSCode integration, which
+ # runs one file at a time, this can wait.
+
+ sorted_by_file = sorted(scan_report.failed_checks, key=lambda c: c.repo_file_path)
+ for file, failed_checks in groupby(sorted_by_file, key=lambda c: c.repo_file_path):
+ failed_checks = [fc for fc in failed_checks if fc.check_id in self.bc_integration.ckv_to_bc_id_mapping]
+ if not failed_checks:
+ continue
+ # file path always starts with /
+ file_abs_path = os.path.abspath(os.path.join(os.getcwd(), file[1:]))
+ with open(file_abs_path, 'r') as reader:
+ file_contents = reader.read()
+
+ fixes = self._get_fixes_for_file(scan_report.check_type, file, file_contents, failed_checks)
+ if not fixes:
+ continue
+ all_fixes = fixes['fixes']
+
+ # a mapping of (checkov_check_id, resource_id) to the failed check Record object for lookup later
+ # guaranteed to map to exactly one record
+ failed_check_by_check_resource = {k: list(v)[0] for k, v in groupby(failed_checks, key=lambda c: (c.check_id, c.resource))}
+
+ for fix in all_fixes:
+ ckv_id = self.bc_integration.bc_id_mapping[fix['policyId']]
+ failed_check = failed_check_by_check_resource[(ckv_id, fix['resourceId'])]
+ failed_check.fixed_definition = fix['fixedDefinition']
+
+ def _get_fixes_for_file(self, check_type, filename, file_contents, failed_checks):
+
+ errors = list(map(lambda c: {
+ 'resourceId': c.resource,
+ 'policyId': self.bc_integration.ckv_to_bc_id_mapping[c.check_id],
+ 'startLine': c.file_line_range[0],
+ 'endLine': c.file_line_range[1]
+ }, failed_checks))
+
+ payload = {
+ 'filePath': filename,
+ 'fileContent': file_contents,
+ 'framework': check_type,
+ 'errors': errors
+ }
+
+ headers = merge_dicts(
+ get_default_post_headers(self.bc_integration.bc_source, self.bc_integration.bc_source_version),
+ get_auth_header(self.bc_integration.bc_api_key)
+ )
+
+ response = requests.request('POST', self.fixes_url, headers=headers, json=payload)
+
+ if response.status_code != 200:
+ error_message = extract_error_message(response)
+ raise Exception(f'Get fixes request failed with response code {response.status_code}: {error_message}')
+
+ logging.debug(f'Response from fixes API: {response.content}')
+
+ fixes = json.loads(response.content) if response.content else None
+ if not fixes or type(fixes) != list:
+ logging.warning(f'Unexpected fixes API response for file {filename}; skipping fixes for this file')
+ return None
+ return fixes[0]
+
+
+integration = FixesIntegration(bc_integration)
diff --git a/checkov/common/bridgecrew/integration_features/features/suppressions_integration.py b/checkov/common/bridgecrew/integration_features/features/suppressions_integration.py
new file mode 100644
index 0000000000..62d6f337a2
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/features/suppressions_integration.py
@@ -0,0 +1,151 @@
+import json
+import logging
+import re
+from itertools import groupby
+
+import requests
+
+from checkov.common.bridgecrew.integration_features.base_integration_feature import BaseIntegrationFeature
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.models.enums import CheckResult
+from checkov.common.util.data_structures_utils import merge_dicts
+from checkov.common.util.http_utils import get_default_get_headers, get_auth_header, extract_error_message
+
+
+class SuppressionsIntegration(BaseIntegrationFeature):
+
+ def __init__(self, bc_integration):
+ super().__init__(bc_integration, order=0)
+ self.suppressions = {}
+
+ # bcorgname_provider_timestamp (ex: companyxyz_aws_1234567891011)
+ # the provider may be lower or upper depending on where the policy was created
+ self.custom_policy_id_regex = re.compile(r'^[a-zA-Z0-9]+_[a-zA-Z]+_\d{13}$')
+
+ def is_valid(self):
+ return self.bc_integration.is_integration_configured() and not self.bc_integration.skip_suppressions
+
+ def pre_scan(self):
+ suppressions = sorted(self._get_suppressions_from_platform(), key=lambda s: s['checkovPolicyId'])
+ # group and map by policy ID
+ self.suppressions = {policy_id: list(sup) for policy_id, sup in groupby(suppressions, key=lambda s: s['checkovPolicyId'])}
+ logging.debug(f'Found {len(self.suppressions)} valid suppressions from the platform.')
+
+ def post_runner(self, scan_report):
+ self._apply_suppressions_to_report(scan_report)
+
+ def _apply_suppressions_to_report(self, scan_report):
+
+ # holds the checks that are still not suppressed
+ still_failed_checks = []
+ for failed_check in scan_report.failed_checks:
+ relevant_suppressions = self.suppressions.get(failed_check.check_id)
+
+ applied_suppression = self._check_suppressions(failed_check,
+ relevant_suppressions) if relevant_suppressions else None
+ if applied_suppression:
+ failed_check.check_result = {
+ 'result': CheckResult.SKIPPED,
+ 'suppress_comment': applied_suppression['comment']
+ }
+ scan_report.skipped_checks.append(failed_check)
+ else:
+ still_failed_checks.append(failed_check)
+
+ scan_report.failed_checks = still_failed_checks
+
+ def _check_suppressions(self, record, suppressions):
+ """
+ Checks the specified suppressions against the specified record, returning the first applicable suppression,
+ or None of no suppression is applicable.
+ :param record:
+ :param suppressions:
+ :return:
+ """
+ for suppression in suppressions:
+ if self._check_suppression(record, suppression):
+ return suppression
+ return None
+
+ def _check_suppression(self, record, suppression):
+ """
+ Returns True if and only if the specified suppression applies to the specified record.
+ :param record:
+ :param suppression:
+ :return:
+ """
+ if record.check_id != suppression['checkovPolicyId']:
+ return False
+
+ type = suppression['suppressionType']
+
+ if type == 'Policy':
+ # We already validated the policy ID above
+ return True
+ elif type == 'Accounts':
+ # This should be true, because we validated when we downloaded the policies.
+ # But checking here adds some resiliency against bugs if that changes.
+ return any(self._repo_matches(account) for account in suppression['accountIds'])
+ elif type == 'Resources':
+ for resource in suppression['resources']:
+ if self._repo_matches(resource['accountId']) and resource['resourceId'] == f'{record.repo_file_path}:{record.resource}':
+ return True
+ return False
+ elif type == 'Tags':
+ entity_tags = record.entity_tags
+ if not entity_tags:
+ return False
+ suppression_tags = suppression['tags'] # a list of objects of the form {key: str, value: str}
+
+ for tag in suppression_tags:
+ key = tag['key']
+ value = tag['value']
+ if entity_tags.get(key) == value:
+ return True
+
+ return False
+
+ def _get_suppressions_from_platform(self):
+ headers = merge_dicts(get_default_get_headers(self.bc_integration.bc_source, self.bc_integration.bc_source_version),
+ get_auth_header(self.bc_integration.bc_api_key))
+ response = requests.request('GET', self.suppressions_url, headers=headers)
+
+ if response.status_code != 200:
+ error_message = extract_error_message(response)
+ raise Exception(f'Get suppressions request failed with response code {response.status_code}: {error_message}')
+
+ # filter out suppressions that we know just don't apply
+ suppressions = [s for s in json.loads(response.content) if self._suppression_valid_for_run(s)]
+
+ for suppression in suppressions:
+ if suppression['policyId'] in self.bc_integration.bc_id_mapping:
+ suppression['checkovPolicyId'] = self.bc_integration.bc_id_mapping[suppression['policyId']]
+ else:
+ suppression['checkovPolicyId'] = suppression['policyId'] # custom policy
+
+ return suppressions
+
+ def _suppression_valid_for_run(self, suppression):
+ """
+ Returns whether this suppression is valid. A suppression is NOT valid if:
+ - the policy does not have a checkov ID and does not have an ID matching a custom policy format
+ - the suppression type is 'Accounts' and this repo is not included in the account list
+ :param suppression:
+ :return:
+ """
+ policyId = suppression['policyId']
+ if policyId not in self.bc_integration.bc_id_mapping and not self.custom_policy_id_regex.match(policyId):
+ return False
+
+ if suppression['suppressionType'] == 'Accounts':
+ if not any(self._repo_matches(account) for account in suppression['accountIds']):
+ return False
+
+ return True
+
+ def _repo_matches(self, repo_name):
+ # matches xyz_org/repo or org/repo (where xyz is the BC org name and the CLI repo prefix from the platform)
+ return re.match(f'^(\\w+_)?{self.bc_integration.repo_id}$', repo_name) is not None
+
+
+integration = SuppressionsIntegration(bc_integration)
diff --git a/checkov/common/bridgecrew/integration_features/integration_feature_registry.py b/checkov/common/bridgecrew/integration_features/integration_feature_registry.py
new file mode 100644
index 0000000000..4df3bd6ac9
--- /dev/null
+++ b/checkov/common/bridgecrew/integration_features/integration_feature_registry.py
@@ -0,0 +1,28 @@
+
+
+class IntegrationFeatureRegistry:
+
+ def __init__(self):
+ self.features = []
+
+ def register(self, integration_feature):
+ self.features.append(integration_feature)
+ self.features.sort(key=lambda f: f.order)
+
+ def run_pre_scan(self):
+ for integration in self.features:
+ if integration.is_valid():
+ integration.pre_scan()
+
+ def run_pre_runner(self):
+ for integration in self.features:
+ if integration.is_valid():
+ integration.pre_runner()
+
+ def run_post_runner(self, scan_reports):
+ for integration in self.features:
+ if integration.is_valid():
+ integration.post_runner(scan_reports)
+
+
+integration_feature_registry = IntegrationFeatureRegistry()
diff --git a/checkov/common/bridgecrew/platform_integration.py b/checkov/common/bridgecrew/platform_integration.py
index 40b7a7bab4..f52a53e612 100644
--- a/checkov/common/bridgecrew/platform_integration.py
+++ b/checkov/common/bridgecrew/platform_integration.py
@@ -1,36 +1,42 @@
+import json
+import logging
+import os.path
+import re
+import time
+import webbrowser
+from concurrent import futures
+from json import JSONDecodeError
+from os import path
from time import sleep
+from typing import Optional
import boto3
import dpath.util
-import json
-import logging
-import os
-import re
import requests
import urllib3
-import webbrowser
from botocore.exceptions import ClientError
from colorama import Style
-# from git import Repo
-from json import JSONDecodeError
-from os import path
from termcolor import colored
from tqdm import trange
from urllib3.exceptions import HTTPError
+from checkov.common.bridgecrew.ci_variables import *
from checkov.common.bridgecrew.platform_errors import BridgecrewAuthError
from checkov.common.bridgecrew.platform_key import read_key, persist_key, bridgecrew_file
+from checkov.common.bridgecrew.wrapper import reduce_scan_reports, persist_checks_results, \
+ enrich_and_persist_checks_metadata
from checkov.common.models.consts import SUPPORTED_FILE_EXTENSIONS
-from .wrapper import reduce_scan_reports, persist_checks_results, enrich_and_persist_checks_metadata
+from checkov.common.runners.base_runner import filter_ignored_paths
+from checkov.version import version as checkov_version
-EMAIL_PATTERN = "[^@]+@[^@]+\.[^@]+"
+EMAIL_PATTERN = r"[^@]+@[^@]+\.[^@]+"
ACCOUNT_CREATION_TIME = 180 # in seconds
UNAUTHORIZED_MESSAGE = 'User is not authorized to access this resource with an explicit deny'
DEFAULT_REGION = "us-west-2"
-
+MAX_RETRIES = 10
ONBOARDING_SOURCE = "checkov"
SIGNUP_HEADER = {
@@ -39,11 +45,6 @@
'Content-Type': 'application/json;charset=UTF-8'
}
-try:
- http = urllib3.ProxyManager(os.environ['https_proxy'])
-except KeyError:
- http = urllib3.PoolManager()
-
class BcPlatformIntegration(object):
def __init__(self):
@@ -53,58 +54,107 @@ def __init__(self):
self.credentials = None
self.repo_path = None
self.repo_id = None
+ self.repo_branch = None
+ self.skip_fixes = False
+ self.skip_suppressions = False
+ self.skip_policy_download = False
self.timestamp = None
self.scan_reports = []
self.bc_api_url = os.getenv('BC_API_URL', "https://www.bridgecrew.cloud/api/v1")
- self.bc_source = os.getenv('BC_SOURCE', "cli")
+ self.bc_source = None
+ self.bc_source_version = None
self.integrations_api_url = f"{self.bc_api_url}/integrations/types/checkov"
self.guidelines_api_url = f"{self.bc_api_url}/guidelines"
self.onboarding_url = f"{self.bc_api_url}/signup/checkov"
self.api_token_url = f"{self.bc_api_url}/integrations/apiToken"
+ self.suppressions_url = f"{self.bc_api_url}/suppressions"
+ self.fixes_url = f"{self.bc_api_url}/fixes/checkov"
self.guidelines = None
self.bc_id_mapping = None
+ self.ckv_to_bc_id_mapping = None
+ self.use_s3_integration = False
+ self.platform_integration_configured = False
+ self.http = None
+ self.excluded_paths = []
+
+ def setup_http_manager(self, ca_certificate=os.getenv('BC_CA_BUNDLE', None)):
+ """
+ bridgecrew uses both the urllib3 and requests libraries, while checkov uses the requests library.
+ :param ca_certificate: an optional CA bundle to be used by both libraries.
+ """
+ if self.http:
+ return
+ if ca_certificate:
+ os.environ['REQUESTS_CA_BUNDLE'] = ca_certificate
+ try:
+ self.http = urllib3.ProxyManager(os.environ['https_proxy'], cert_reqs='REQUIRED', ca_certs=ca_certificate)
+ except KeyError:
+ self.http = urllib3.PoolManager(cert_reqs='REQUIRED', ca_certs=ca_certificate)
+ else:
+ try:
+ self.http = urllib3.ProxyManager(os.environ['https_proxy'])
+ except KeyError:
+ self.http = urllib3.PoolManager()
- def setup_bridgecrew_credentials(self, bc_api_key, repo_id):
+ def setup_bridgecrew_credentials(self, bc_api_key, repo_id, skip_fixes=False, skip_suppressions=False,
+ skip_policy_download=False, source=None, source_version=None, repo_branch=None):
"""
Setup credentials against Bridgecrew's platform.
+ :param source:
+ :param skip_fixes: whether to skip querying fixes from Bridgecrew
:param repo_id: Identity string of the scanned repository, of the form /
:param bc_api_key: Bridgecrew issued API key
"""
self.bc_api_key = bc_api_key
self.repo_id = repo_id
- try:
- repo_full_path, response = self.get_s3_role(bc_api_key, repo_id)
- self.bucket, self.repo_path = repo_full_path.split("/", 1)
- self.timestamp = self.repo_path.split("/")[-1]
- self.credentials = response["creds"]
- self.s3_client = boto3.client("s3",
- aws_access_key_id=self.credentials["AccessKeyId"],
- aws_secret_access_key=self.credentials["SecretAccessKey"],
- aws_session_token=self.credentials["SessionToken"],
- region_name=DEFAULT_REGION
- )
- sleep(10) # Wait for the policy to update
- except HTTPError as e:
- logging.error(f"Failed to get customer assumed role\n{e}")
- raise e
- except ClientError as e:
- logging.error(f"Failed to initiate client with credentials {self.credentials}\n{e}")
- raise e
- except JSONDecodeError as e:
- logging.error(f"Response of {self.integrations_api_url} is not a valid JSON\n{e}")
- raise e
+ self.repo_branch = repo_branch
+ self.skip_fixes = skip_fixes
+ self.skip_suppressions = skip_suppressions
+ self.skip_policy_download = skip_policy_download
+ self.bc_source = source
+ self.bc_source_version = source_version
+
+ if self.bc_source.upload_results:
+ try:
+ self.skip_fixes = True # no need to run fixes on CI integration
+ repo_full_path, response = self.get_s3_role(bc_api_key, repo_id)
+ self.bucket, self.repo_path = repo_full_path.split("/", 1)
+ self.timestamp = self.repo_path.split("/")[-1]
+ self.credentials = response["creds"]
+ self.s3_client = boto3.client("s3",
+ aws_access_key_id=self.credentials["AccessKeyId"],
+ aws_secret_access_key=self.credentials["SecretAccessKey"],
+ aws_session_token=self.credentials["SessionToken"],
+ region_name=DEFAULT_REGION
+ )
+ sleep(10) # Wait for the policy to update
+ self.platform_integration_configured = True
+ self.use_s3_integration = True
+ except HTTPError as e:
+ logging.error(f"Failed to get customer assumed role\n{e}")
+ raise e
+ except ClientError as e:
+ logging.error(f"Failed to initiate client with credentials {self.credentials}\n{e}")
+ raise e
+ except JSONDecodeError as e:
+ logging.error(f"Response of {self.integrations_api_url} is not a valid JSON\n{e}")
+ raise e
+
+ self.get_id_mapping()
+
+ self.platform_integration_configured = True
def get_s3_role(self, bc_api_key, repo_id):
- request = http.request("POST", self.integrations_api_url, body=json.dumps({"repoId": repo_id}),
- headers={"Authorization": bc_api_key, "Content-Type": "application/json"})
+ request = self.http.request("POST", self.integrations_api_url, body=json.dumps({"repoId": repo_id}),
+ headers={"Authorization": bc_api_key, "Content-Type": "application/json"})
response = json.loads(request.data.decode("utf8"))
while ('Message' in response or 'message' in response):
if 'Message' in response and response['Message'] == UNAUTHORIZED_MESSAGE:
raise BridgecrewAuthError()
if 'message' in response and "cannot be found" in response['message']:
self.loading_output("creating role")
- request = http.request("POST", self.integrations_api_url, body=json.dumps({"repoId": repo_id}),
- headers={"Authorization": bc_api_key, "Content-Type": "application/json"})
+ request = self.http.request("POST", self.integrations_api_url, body=json.dumps({"repoId": repo_id}),
+ headers={"Authorization": bc_api_key, "Content-Type": "application/json"})
response = json.loads(request.data.decode("utf8"))
repo_full_path = response["path"]
@@ -112,29 +162,58 @@ def get_s3_role(self, bc_api_key, repo_id):
def is_integration_configured(self):
"""
- Checks if Bridgecrew integration is fully configured.
+ Checks if Bridgecrew integration is fully configured based in input params.
:return: True if the integration is configured, False otherwise
"""
- return all([self.repo_path, self.credentials, self.s3_client])
+ return self.platform_integration_configured
- def persist_repository(self, root_dir):
+ def persist_repository(self, root_dir, files=None, excluded_paths=None):
"""
- Persist the repository found on root_dir path to Bridgecrew's platform
- :param root_dir: Absolute path of the directory containing the repository root level
+ Persist the repository found on root_dir path to Bridgecrew's platform. If --file flag is used, only files
+ that are specified will be persisted.
+ :param files: Absolute path of the files passed in the --file flag.
+ :param root_dir: Absolute path of the directory containing the repository root level.
+ :param excluded_paths: Paths to exclude from persist process
"""
- for root_path, d_names, f_names in os.walk(root_dir):
- for file_path in f_names:
- _, file_extension = os.path.splitext(file_path)
+ excluded_paths = excluded_paths if excluded_paths is not None else []
+
+ if not self.use_s3_integration:
+ return
+ files_to_persist = []
+ if files:
+ for f in files:
+ _, file_extension = os.path.splitext(f)
if file_extension in SUPPORTED_FILE_EXTENSIONS:
- full_file_path = os.path.join(root_path, file_path)
- relative_file_path = os.path.relpath(full_file_path, root_dir)
- self._persist_file(full_file_path, relative_file_path)
+ files_to_persist.append((f, os.path.relpath(f, root_dir)))
+ else:
+ for root_path, d_names, f_names in os.walk(root_dir):
+ # self.excluded_paths only contains the config fetched from the platform.
+ # but here we expect the list from runner_registry as well (which includes self.excluded_paths).
+ filter_ignored_paths(root_path, d_names, excluded_paths)
+ filter_ignored_paths(root_path, f_names, excluded_paths)
+ for file_path in f_names:
+ _, file_extension = os.path.splitext(file_path)
+ if file_extension in SUPPORTED_FILE_EXTENSIONS:
+ full_file_path = os.path.join(root_path, file_path)
+ relative_file_path = os.path.relpath(full_file_path, root_dir)
+ files_to_persist.append((full_file_path, relative_file_path))
+
+ logging.info(f"Persisting {len(files_to_persist)} files")
+ with futures.ThreadPoolExecutor() as executor:
+ futures.wait(
+ [executor.submit(self._persist_file, full_file_path, relative_file_path) for full_file_path, relative_file_path in files_to_persist],
+ return_when=futures.FIRST_EXCEPTION,
+ )
+ logging.info(f"Done persisting {len(files_to_persist)} files")
def persist_scan_results(self, scan_reports):
"""
Persist checkov's scan result into bridgecrew's platform.
:param scan_reports: List of checkov scan reports
"""
+ if not self.use_s3_integration:
+ return
+
self.scan_reports = scan_reports
reduced_scan_reports = reduce_scan_reports(scan_reports)
checks_metadata_paths = enrich_and_persist_checks_metadata(scan_reports, self.s3_client, self.bucket,
@@ -147,28 +226,53 @@ def commit_repository(self, branch):
:param branch: branch to be persisted
Finalize the repository's scanning in bridgecrew's platform.
"""
- request = None
- try:
- request = http.request("PUT", f"{self.integrations_api_url}?source={self.bc_source}",
- body=json.dumps({"path": self.repo_path, "branch": branch}),
- headers={"Authorization": self.bc_api_key, "Content-Type": "application/json"})
- response = json.loads(request.data.decode("utf8"))
- except HTTPError as e:
- logging.error(f"Failed to commit repository {self.repo_path}\n{e}")
- raise e
- except JSONDecodeError as e:
- logging.error(f"Response of {self.integrations_api_url} is not a valid JSON\n{e}")
- raise e
- finally:
- if request.status == 201 and response["result"] == "Success":
- logging.info(f"Finalize repository {self.repo_id} in bridgecrew's platform")
- else:
- raise Exception(f"Failed to finalize repository {self.repo_id} in bridgecrew's platform\n{response}")
+ try_num = 0
+ while try_num < MAX_RETRIES:
+ if not self.use_s3_integration:
+ return
+
+ request = None
+ response = None
+ try:
+
+ request = self.http.request("PUT", f"{self.integrations_api_url}?source={self.bc_source.name}",
+ body=json.dumps(
+ {"path": self.repo_path, "branch": branch, "to_branch": BC_TO_BRANCH,
+ "pr_id": BC_PR_ID, "pr_url": BC_PR_URL,
+ "commit_hash": BC_COMMIT_HASH, "commit_url": BC_COMMIT_URL,
+ "author": BC_AUTHOR_NAME, "author_url": BC_AUTHOR_URL,
+ "run_id": BC_RUN_ID, "run_url": BC_RUN_URL,
+ "repository_url": BC_REPOSITORY_URL}),
+ headers={"Authorization": self.bc_api_key,
+ "Content-Type": "application/json",
+ 'x-api-client': self.bc_source.name,
+ 'x-api-checkov-version': checkov_version
+ })
+ response = json.loads(request.data.decode("utf8"))
+ url = response.get("url", None)
+ return url
+ except HTTPError as e:
+ logging.error(f"Failed to commit repository {self.repo_path}\n{e}")
+ raise e
+ except JSONDecodeError as e:
+ logging.error(f"Response of {self.integrations_api_url} is not a valid JSON\n{e}")
+ raise e
+ finally:
+ if request.status == 201 and response and response.get("result") == "Success":
+ logging.info(f"Finalize repository {self.repo_id} in bridgecrew's platform")
+ elif try_num < MAX_RETRIES and re.match('The integration ID .* in progress',
+ response.get('message', '')):
+ logging.info(f"Failed to persist for repo {self.repo_id}, sleeping for 2 seconds before retrying")
+ try_num += 1
+ sleep(3)
+ else:
+ raise Exception(
+ f"Failed to finalize repository {self.repo_id} in bridgecrew's platform\n{response}")
def _persist_file(self, full_file_path, relative_file_path):
tries = 4
curr_try = 0
- file_object_key = os.path.join(self.repo_path, relative_file_path)
+ file_object_key = os.path.join(self.repo_path, relative_file_path).replace("\\", "/")
while curr_try < tries:
try:
self.s3_client.upload_file(full_file_path, self.bucket, file_object_key)
@@ -197,46 +301,88 @@ def get_id_mapping(self) -> dict:
self.get_checkov_mapping_metadata()
return self.bc_id_mapping
- def get_checkov_mapping_metadata(self) -> dict:
+ def get_ckv_to_bc_id_mapping(self) -> dict:
+ if not self.ckv_to_bc_id_mapping:
+ self.get_checkov_mapping_metadata()
+ return self.ckv_to_bc_id_mapping
+
+ def get_checkov_mapping_metadata(self) -> Optional[dict]:
+ BC_SKIP_MAPPING = os.getenv("BC_SKIP_MAPPING", "FALSE")
+ if BC_SKIP_MAPPING.upper() == "TRUE":
+ logging.debug(f"Skipped mapping API call")
+ self.ckv_to_bc_id_mapping = {}
+ return
try:
- request = http.request("GET", self.guidelines_api_url)
+ request = self.http.request("GET", self.guidelines_api_url)
response = json.loads(request.data.decode("utf8"))
self.guidelines = response["guidelines"]
self.bc_id_mapping = response.get("idMapping")
+ self.ckv_to_bc_id_mapping = {ckv_id: bc_id for (bc_id, ckv_id) in self.bc_id_mapping.items()}
logging.debug(f"Got checkov mappings from Bridgecrew BE")
except Exception as e:
logging.debug(f"Failed to get the guidelines from {self.guidelines_api_url}, error:\n{e}")
- return {}
+ self.ckv_to_bc_id_mapping = {}
+ return
def onboarding(self):
if not self.bc_api_key:
- print(Style.BRIGHT + colored("Visualize and collaborate on security issues with Bridgecrew! \n", 'blue',
- attrs=['bold']) + colored(
- "Bridgecrew's dashboard allows automation of future checks, Pull Request scanning and "
- "auto-comments, automatic remidiation PR's and more! \n Plus it's free for 100 cloud resources and a "
- "great way to visualize and collaborate on Checkov results. For more information on dashboard integration, see: http://bridge.dev/checkov-dashboard \n \n To instantly see future Checkov scans in the "
- "platform, Press y! \n",
- 'yellow') + Style.RESET_ALL)
- reply = self._input_visualize_results()
+ print(Style.BRIGHT + colored("\nWould you like to “level up” your Checkov powers for free? The upgrade includes: \n\n", 'green',
+ attrs=['bold']) + colored(
+ u"\u2022 " + "Command line docker Image scanning\n"
+ u"\u2022 " + "Free (forever) bridgecrew.cloud account with API access\n"
+ u"\u2022 " + "Auto-fix remediation suggestions\n"
+ u"\u2022 " + "Enabling of VS Code Plugin\n"
+ u"\u2022 " + "Dashboard visualisation of Checkov scans\n"
+ u"\u2022 " + "Integration with GitHub for:\n"
+ "\t" + u"\u25E6 " + "\tAutomated Pull Request scanning\n"
+ "\t" + u"\u25E6 " + "\tAuto remediation PR generation\n"
+ u"\u2022 " + "Integration with up to 100 cloud resources for:\n"
+ "\t" + u"\u25E6 " + "\tAutomated cloud resource checks\n"
+ "\t" + u"\u25E6 " + "\tResource drift detection\n"
+ "\n"
+ "\n" + "and much more...",'yellow') +
+ colored("\n\nIt's easy and only takes 2 minutes. We can do it right now!\n\n"
+ "To Level-up, press 'y'... \n",
+ 'cyan') + Style.RESET_ALL)
+ reply = self._input_levelup_results()
if reply[:1] == 'y':
- print(Style.BRIGHT + colored("\nEmail Address? \n", 'blue', attrs=['bold']))
+ print(Style.BRIGHT + colored("\nOk, let’s get you started on creating your free account! \n"
+ "\nEnter your email address to begin: ",'green', attrs=['bold']) + colored(" // This will be used as your login at https://bridgecrew.cloud.\n", 'green'))
if not self.bc_api_key:
email = self._input_email()
+ print(Style.BRIGHT + colored("\nLooks good!"
+ "\nNow choose an Organisation Name: ",'green', attrs=['bold']) + colored(" // This will enable collaboration with others who you can add to your team.\n", 'green'))
org = self._input_orgname()
+ print(Style.BRIGHT + colored("\nAmazing!"
+ "\nWe are now generating a personal API key to immediately enable some new features… ",'green', attrs=['bold']))
bc_api_token, response = self.get_api_token(email, org)
self.bc_api_key = bc_api_token
if response.status_code == 200:
- print('\n Saving API key to {}'.format(bridgecrew_file))
- print(Style.BRIGHT + colored("\n Checkov Dashboard configured, opening https://bridgecrew.cloud, check your inbox for login details! \n", 'blue', attrs=['bold']))
+ print(Style.BRIGHT + colored("\nComplete!",'green', attrs=['bold']))
+ print('\nSaving API key to {}'.format(bridgecrew_file))
+ print(Style.BRIGHT + colored("\nCheckov will automatically check this location for a key. If you forget it you’ll find it here\nhttps://bridgecrew.cloud/integrations/api-token\n\n",'green'))
persist_key(self.bc_api_key)
+ print(Style.BRIGHT + colored("Checkov Dashboard is configured, opening https://bridgecrew.cloud to explore your new powers.", 'green', attrs=['bold']))
+ print(Style.BRIGHT + colored("FYI - check your inbox for login details! \n", 'green'))
+
+ print(Style.BRIGHT + colored("Congratulations! You’ve just super-sized your Checkov! Why not test-drive image scanning now:",'cyan'))
+
+ print(Style.BRIGHT + colored("\ncheckov --docker-image ubuntu --dockerfile-path /Users/bob/workspaces/bridgecrew/Dockerfile --repo-id bob/test --branch master\n",'white'))
+
+ print(Style.BRIGHT + colored("Or download our VS Code plugin: https://github.com/bridgecrewio/checkov-vscode \n", 'cyan',attrs=['bold']))
+
+ print(Style.BRIGHT + colored( "Interested in contributing to Checkov as an open source developer. We thought you’d never ask. Check us out at: \nhttps://github.com/bridgecrewio/checkov/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 \n", 'white', attrs=['bold']))
+
else:
print(
Style.BRIGHT + colored("\nCould not create account, please try again on your next scan! \n",
'red', attrs=['bold']) + Style.RESET_ALL)
- webbrowser.open("https://bridgecrew.cloud/?utm_source=cli&utm_medium=organic_oss&utm_campaign=checkov")
+ webbrowser.open(
+ "https://bridgecrew.cloud/?utm_source=cli&utm_medium=organic_oss&utm_campaign=checkov")
else:
- print("\n To see the Dashboard prompt again, run `checkov` with no arguments \n For Checkov usage, try `checkov --help`")
+ print(
+ "\n To see the Dashboard prompt again, run `checkov` with no arguments \n For Checkov usage, try `checkov --help`")
else:
print("No argument given. Try ` --help` for further information")
@@ -249,20 +395,39 @@ def get_report_to_platform(self, args, scan_reports):
if self.is_integration_configured():
self._upload_run(args, scan_reports)
+ # Added this to generate a default repo_id for cli scans for upload to the platform
+ # whilst also persisting a cli repo_id into the object
+ def persist_bc_api_key(self, args):
+ if args.bc_api_key:
+ self.bc_api_key = args.bc_api_key
+ else:
+ # get the key from file
+ self.bc_api_key = read_key()
+ return self.bc_api_key
+
+ # Added this to generate a default repo_id for cli scans for upload to the platform
+ # whilst also persisting a cli repo_id into the object
+ def persist_repo_id(self, args):
+ if args.repo_id is None:
+ if BC_FROM_BRANCH:
+ self.repo_id = BC_FROM_BRANCH
+ if args.directory:
+ basename = path.basename(os.path.abspath(args.directory[0]))
+ self.repo_id = "cli_repo/" + basename
+ if args.file:
+ # Get the base path of the file based on it's absolute path
+ basename = os.path.basename(os.path.dirname(os.path.abspath(args.file[0])))
+ self.repo_id = "cli_repo/" + basename
+
+ else:
+ self.repo_id = args.repo_id
+ return self.repo_id
+
def get_repository(self, args):
- repo_id = "cli_repo/" + path.basename(args.directory[0])
- valid_repos = 0
- # Work out git repo name for BC --repo-id from root_folder
- # for dir in args.directory:
- # try:
- # repo = Repo(dir)
- # git_remote_uri = repo.remotes.origin.url
- # git_repo_dict = re.match(r'(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git',
- # git_remote_uri).group(4, 5)
- # repo_id = git_repo_dict[0] + "/" + git_repo_dict[1]
- # valid_repos += 1
- # except: # nosec
- # pass
+ if BC_FROM_BRANCH:
+ return BC_FROM_BRANCH
+ basename = 'unnamed_repo' if path.basename(args.directory[0]) == '.' else path.basename(args.directory[0])
+ repo_id = "cli_repo/" + basename
return repo_id
def get_api_token(self, email, org):
@@ -271,7 +436,7 @@ def get_api_token(self, email, org):
return bc_api_token, response
def _upload_run(self, args, scan_reports):
- print(Style.BRIGHT + colored("Sucessfully configured Bridgecrew.cloud...", 'green',
+ print(Style.BRIGHT + colored("Connecting to Bridgecrew.cloud...", 'green',
attrs=['bold']) + Style.RESET_ALL)
self.persist_repository(args.directory[0])
print(Style.BRIGHT + colored("Metadata upload complete", 'green',
@@ -281,8 +446,7 @@ def _upload_run(self, args, scan_reports):
attrs=['bold']) + Style.RESET_ALL)
self.commit_repository(args.branch)
print(Style.BRIGHT + colored(
- "COMPLETE! Your Bridgecrew dashboard is available here: https://bridgecrew.cloud \n"
- "Login information should be in your email inbox", 'green', attrs=['bold']) + Style.RESET_ALL)
+ "COMPLETE! \nYour results are in your Bridgecrew dashboard, available here: https://bridgecrew.cloud \n", 'green', attrs=['bold']) + Style.RESET_ALL)
def _create_bridgecrew_account(self, email, org):
"""
@@ -301,14 +465,15 @@ def _create_bridgecrew_account(self, email, org):
return response
else:
raise Exception("failed to create a bridgecrew account. An organization with this name might already "
- "exist with this email address. Please login bridgecrew.cloud to retrieve access key");
+ "exist with this email address. Please login bridgecrew.cloud to retrieve access key")
def _input_orgname(self):
valid = False
result = None
while not valid:
result = str(
- input('Organization name (this will create an account with matching identifier): ')).lower().strip() # nosec
+ input(
+ 'Organization name: ')).lower().strip() # nosec
# remove spaces and special characters
result = ''.join(e for e in result if e.isalnum())
if result:
@@ -324,10 +489,20 @@ def _input_visualize_results(self):
valid = True
return result
+ def _input_levelup_results(self):
+ valid = False
+ result = None
+ while not valid:
+ result = str(input('Level up? (y/n): ')).lower().strip() # nosec
+ if result[:1] in ["y", "n"]:
+ valid = True
+ return result
+
def _input_email(self):
valid_email = False
+ email = ''
while not valid_email:
- email = str(input('E-Mail:')).lower().strip() # nosec
+ email = str(input('E-Mail: ')).lower().strip() # nosec
if re.search(EMAIL_PATTERN, email):
valid_email = True
else:
@@ -342,5 +517,23 @@ def loading_output(msg):
t.set_postfix(refresh=False)
sleep(1)
+ def get_excluded_paths(self):
+ repo_settings_api_url = f'{self.bc_api_url}/vcs/settings/scheme'
+ try:
+ request = self.http.request("GET", repo_settings_api_url,
+ headers={"Authorization": self.bc_api_key, "Content-Type": "application/json"})
+ response = json.loads(request.data.decode("utf8"))
+ if 'scannedFiles' in response:
+ for section in response.get('scannedFiles').get('sections'):
+ if self.repo_id in section.get('repos') and section.get('rule').get('excludePaths'):
+ self.excluded_paths.extend(section.get('rule').get('excludePaths'))
+ return self.excluded_paths
+ except HTTPError as e:
+ logging.error(f"Failed to get vcs settings for repo {self.repo_path}\n{e}")
+ raise e
+ except JSONDecodeError as e:
+ logging.error(f"Response of {repo_settings_api_url} is not a valid JSON\n{e}")
+ raise e
+
bc_integration = BcPlatformIntegration()
diff --git a/checkov/common/bridgecrew/wrapper.py b/checkov/common/bridgecrew/wrapper.py
index eb9f886ee8..8ac5ea301b 100644
--- a/checkov/common/bridgecrew/wrapper.py
+++ b/checkov/common/bridgecrew/wrapper.py
@@ -4,6 +4,7 @@
import itertools
import dpath.util
from checkov.common.models.consts import SUPPORTED_FILE_EXTENSIONS
+from checkov.common.util.json_utils import CustomJSONEncoder
checkov_results_prefix = 'checkov_results'
check_reduced_keys = (
@@ -19,7 +20,7 @@ def _is_scanned_file(file):
def _put_json_object(s3_client, json_obj, bucket, object_path):
try:
- s3_client.put_object(Bucket=bucket, Key=object_path, Body=json.dumps(json_obj))
+ s3_client.put_object(Bucket=bucket, Key=object_path, Body=json.dumps(json_obj, cls=CustomJSONEncoder))
except Exception as e:
logging.error(f"failed to persist object {json_obj} into S3 bucket {bucket}\n{e}")
raise e
diff --git a/checkov/common/checks/base_check.py b/checkov/common/checks/base_check.py
index d4091c0978..c2bc7171ef 100644
--- a/checkov/common/checks/base_check.py
+++ b/checkov/common/checks/base_check.py
@@ -1,62 +1,67 @@
import logging
from abc import abstractmethod
+from typing import List, Dict, Any, Callable, Optional
+from checkov.common.typing import _SkippedCheck
from checkov.common.util.type_forcers import force_list
-from checkov.common.models.enums import CheckResult
+from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.common.multi_signature import MultiSignatureMeta, multi_signature
class BaseCheck(metaclass=MultiSignatureMeta):
id = ""
name = ""
- categories = []
- supported_entities = []
+ categories: List[CheckCategories] = []
+ supported_entities: List[str] = []
- def __init__(self, name, id, categories, supported_entities, block_type):
+ def __init__(
+ self, name: str, id: str, categories: List[CheckCategories], supported_entities: List[str], block_type: str, bc_id: Optional[str] = None
+ ) -> None:
self.name = name
self.id = id
+ self.bc_id = bc_id
self.categories = categories
self.block_type = block_type
self.supported_entities = supported_entities
self.logger = logging.getLogger("{}".format(self.__module__))
- self.evaluated_keys = []
+ self.evaluated_keys: List[str] = []
- def run(self, scanned_file, entity_configuration, entity_name, entity_type, skip_info):
- check_result = {}
+ def run(
+ self,
+ scanned_file: str,
+ entity_configuration: Dict[str, List[Any]],
+ entity_name: str,
+ entity_type: str,
+ skip_info: _SkippedCheck,
+ ) -> Dict[str, Any]:
+ check_result: Dict[str, Any] = {}
if skip_info:
- check_result['result'] = CheckResult.SKIPPED
- check_result['suppress_comment'] = skip_info['suppress_comment']
- message = "File {}, {} \"{}.{}\" check \"{}\" Result: {}, Suppression comment: {} ".format(
+ check_result["result"] = CheckResult.SKIPPED
+ check_result["suppress_comment"] = skip_info["suppress_comment"]
+ message = 'File {}, {} "{}.{}" check "{}" Result: {}, Suppression comment: {} '.format(
scanned_file,
self.block_type,
entity_type,
entity_name,
self.name,
check_result,
- check_result['suppress_comment']
+ check_result["suppress_comment"],
)
self.logger.debug(message)
else:
try:
self.evaluated_keys = []
- check_result['result'] = self.scan_entity_conf(entity_configuration, entity_type)
- check_result['evaluated_keys'] = self.get_evaluated_keys()
- message = "File {}, {} \"{}.{}\" check \"{}\" Result: {} ".format(
- scanned_file,
- self.block_type,
- entity_type,
- entity_name,
- self.name,
- check_result
+ check_result["result"] = self.scan_entity_conf(entity_configuration, entity_type)
+ check_result["evaluated_keys"] = self.get_evaluated_keys()
+ message = 'File {}, {} "{}.{}" check "{}" Result: {} '.format(
+ scanned_file, self.block_type, entity_type, entity_name, self.name, check_result
)
self.logger.debug(message)
except Exception as e:
self.logger.error(
"Failed to run check: {} for configuration: {} at file: {}".format(
- self.name,
- str(entity_configuration),
- scanned_file
+ self.name, str(entity_configuration), scanned_file
)
)
raise e
@@ -64,21 +69,24 @@ def run(self, scanned_file, entity_configuration, entity_name, entity_type, skip
@multi_signature()
@abstractmethod
- def scan_entity_conf(self, conf, entity_type):
+ def scan_entity_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
raise NotImplementedError()
@classmethod
@scan_entity_conf.add_signature(args=["self", "conf"])
- def _scan_entity_conf_self_conf(cls, wrapped):
- def wrapper(self, conf, entity_type=None):
+ def _scan_entity_conf_self_conf(cls, wrapped: Callable) -> Callable:
+ def wrapper(self: "BaseCheck", conf: Dict[str, List[Any]], entity_type: Optional[str] = None) -> CheckResult:
# keep default argument for entity_type so old code, that doesn't set it, will work.
return wrapped(self, conf)
return wrapper
- def get_evaluated_keys(self):
+ def get_evaluated_keys(self) -> List[str]:
"""
Retrieves the evaluated keys for the run's report. Child classes override the function and return the `expected_keys` instead.
:return: List of the evaluated keys, as JSONPath syntax paths of the checked attributes
"""
return force_list(self.evaluated_keys)
+
+ def get_output_id(self, use_bc_ids: bool) -> str:
+ return self.bc_id if self.bc_id and use_bc_ids else self.id
diff --git a/checkov/common/checks/base_check_registry.py b/checkov/common/checks/base_check_registry.py
index c750e2abe9..33f4c07bb6 100644
--- a/checkov/common/checks/base_check_registry.py
+++ b/checkov/common/checks/base_check_registry.py
@@ -5,12 +5,13 @@
import sys
from abc import abstractmethod
from itertools import chain
-from typing import Generator, Tuple
+from typing import Generator, Tuple, Dict, List, Optional, Any
from checkov.common.checks.base_check import BaseCheck
from collections import defaultdict
+from checkov.common.typing import _SkippedCheck
from checkov.runner_filter import RunnerFilter
@@ -18,18 +19,19 @@ class BaseCheckRegistry(object):
# NOTE: Needs to be static to because external check loading may be triggered by a registry to which
# checks aren't registered. (This happens with Serverless, for example.)
__loading_external_checks = False
+ __all_registered_checks: List[BaseCheck] = []
- def __init__(self):
+ def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
# IMPLEMENTATION NOTE: Checks is used to directly access checks based on an specific entity
- self.checks = defaultdict(list)
+ self.checks: Dict[str, List[BaseCheck]] = defaultdict(list)
# IMPLEMENTATION NOTE: When using a wildcard, every pattern needs to be checked. To reduce the
# number of checks checks with the same pattern are grouped, which is the
# reason to use a dict for this too.
- self.wildcard_checks = defaultdict(list)
- self.check_id_allowlist = None
+ self.wildcard_checks: Dict[str, List[BaseCheck]] = defaultdict(list)
+ self.check_id_allowlist: Optional[List[str]] = None
- def register(self, check):
+ def register(self, check: BaseCheck) -> None:
# IMPLEMENTATION NOTE: Checks are registered when the script is loaded
# (see BaseResourceCheck.__init__() for the various frameworks). The only
# difficultly with this process is that external checks need to be specially
@@ -42,20 +44,24 @@ def register(self, check):
for entity in check.supported_entities:
checks = self.wildcard_checks if self._is_wildcard(entity) else self.checks
- checks[entity].append(check)
+ if not any(c.id == check.id for c in checks[entity]):
+ checks[entity].append(check)
+
+ BaseCheckRegistry.__all_registered_checks.append(check)
+
+ @staticmethod
+ def get_all_registered_checks() -> List[BaseCheck]:
+ return BaseCheckRegistry.__all_registered_checks
@staticmethod
- def _is_wildcard(entity):
- return ('*' in entity
- or '?' in entity
- or ('[' in entity and ']' in entity))
+ def _is_wildcard(entity: str) -> bool:
+ return "*" in entity or "?" in entity or ("[" in entity and "]" in entity)
- def get_check_by_id(self, check_id):
+ def get_check_by_id(self, check_id: str) -> Optional[BaseCheck]:
return next(
- filter(
- lambda c: c.id == check_id,
- chain(*self.checks.values(), *self.wildcard_checks.values())
- ), None)
+ (check for check in chain(*self.checks.values(), *self.wildcard_checks.values()) if check.id == check_id),
+ None,
+ )
def all_checks(self) -> Generator[Tuple[str, BaseCheck], None, None]:
for entity, checks in self.checks.items():
@@ -69,7 +75,7 @@ def all_checks(self) -> Generator[Tuple[str, BaseCheck], None, None]:
def contains_wildcard(self) -> bool:
return bool(self.wildcard_checks)
- def get_checks(self, entity):
+ def get_checks(self, entity: str) -> List[BaseCheck]:
if not self.wildcard_checks:
# Optimisation: When no wildcards are used, we can use the list in self.checks
return self.checks.get(entity) or []
@@ -81,54 +87,75 @@ def get_checks(self, entity):
res += checks
return res
- def set_checks_allowlist(self, runner_filter):
+ def set_checks_allowlist(self, runner_filter: RunnerFilter) -> None:
if runner_filter.checks:
self.check_id_allowlist = runner_filter.checks
@abstractmethod
- def extract_entity_details(self, entity):
+ def extract_entity_details(self, entity: Dict[str, Any]) -> Tuple[str, str, Dict[str, Any]]:
raise NotImplementedError()
- def scan(self, scanned_file, entity, skipped_checks, runner_filter):
+ def scan(
+ self,
+ scanned_file: str,
+ entity: Dict[str, Any],
+ skipped_checks: List[_SkippedCheck],
+ runner_filter: RunnerFilter,
+ ) -> Dict[BaseCheck, Dict[str, Any]]:
+
(entity_type, entity_name, entity_configuration) = self.extract_entity_details(entity)
- results = {}
+
+ results: Dict[BaseCheck, Dict[str, Any]] = {}
+
+ if not isinstance(entity_configuration, dict):
+ return results
+
checks = self.get_checks(entity_type)
for check in checks:
- skip_info = {}
+ skip_info: _SkippedCheck = {}
if skipped_checks:
- if check.id in [x['id'] for x in skipped_checks]:
- skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
+ if check.id in [x["id"] for x in skipped_checks]:
+ skip_info = [x for x in skipped_checks if x["id"] == check.id][0]
- if runner_filter.should_run_check(check.id):
+ if runner_filter.should_run_check(check.id, check.bc_id):
result = self.run_check(check, entity_configuration, entity_name, entity_type, scanned_file, skip_info)
results[check] = result
return results
- def run_check(self, check, entity_configuration, entity_name, entity_type, scanned_file, skip_info):
+ def run_check(
+ self,
+ check: BaseCheck,
+ entity_configuration: Dict[str, List[Any]],
+ entity_name: str,
+ entity_type: str,
+ scanned_file: str,
+ skip_info: _SkippedCheck,
+ ) -> Dict[str, Any]:
self.logger.debug("Running check: {} on file {}".format(check.name, scanned_file))
- result = check.run(scanned_file=scanned_file, entity_configuration=entity_configuration,
- entity_name=entity_name, entity_type=entity_type, skip_info=skip_info)
+ result = check.run(
+ scanned_file=scanned_file,
+ entity_configuration=entity_configuration,
+ entity_name=entity_name,
+ entity_type=entity_type,
+ skip_info=skip_info,
+ )
return result
@staticmethod
- def _directory_has_init_py(directory):
+ def _directory_has_init_py(directory: str) -> bool:
""" Check if a given directory contains a file named __init__.py.
__init__.py is needed to ensure the directory is a Python module, thus
can be imported.
"""
- if os.path.exists("{}/__init__.py".format(directory)):
- return True
- return False
+ return os.path.exists(f"{directory}/__init__.py")
@staticmethod
- def _file_can_be_imported(entry):
+ def _file_can_be_imported(entry: os.DirEntry) -> bool:
""" Verify if a directory entry is a non-magic Python file."""
- if entry.is_file() and not entry.name.startswith('__') and entry.name.endswith('.py'):
- return True
- return False
+ return entry.is_file() and not entry.name.startswith("__") and entry.name.endswith(".py")
- def load_external_checks(self, directory, runner_filter):
+ def load_external_checks(self, directory: str) -> None:
""" Browse a directory looking for .py files to import.
Log an error when the directory does not contains an __init__.py or
@@ -144,7 +171,7 @@ def load_external_checks(self, directory, runner_filter):
else:
for entry in directory_content:
if self._file_can_be_imported(entry):
- check_name = entry.name.replace('.py', '')
+ check_name = entry.name.replace(".py", "")
# Filter is set while loading external checks so the filter can be informed
# of the checks, which need to be handled specially.
@@ -155,13 +182,12 @@ def load_external_checks(self, directory, runner_filter):
except SyntaxError as e:
self.logger.error(
"Cannot load external check '{check_name}' from {check_full_path} : {error_message} ("
- "{error_line}:{error_column}) "
- .format(
+ "{error_line}:{error_column}) ".format(
check_name=check_name,
check_full_path=e.args[1][0],
error_message=e.args[0],
error_line=e.args[1][1],
- error_column=e.args[1][2]
+ error_column=e.args[1][2],
)
)
finally:
diff --git a/tests/terraform/runner/resources/valid_tf_only_passed_checks/__init__.py b/checkov/common/checks_infra/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/valid_tf_only_passed_checks/__init__.py
rename to checkov/common/checks_infra/__init__.py
diff --git a/checkov/common/checks_infra/checks_parser.py b/checkov/common/checks_infra/checks_parser.py
new file mode 100644
index 0000000000..9d3999e6e5
--- /dev/null
+++ b/checkov/common/checks_infra/checks_parser.py
@@ -0,0 +1,138 @@
+from typing import Dict, Any, List, Optional
+
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.graph.checks_infra.base_check import BaseGraphCheck
+from checkov.common.graph.checks_infra.base_parser import BaseGraphCheckParser
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers import *
+
+operators_to_attributes_solver_classes = {
+ "equals": EqualsAttributeSolver,
+ "not_equals": NotEqualsAttributeSolver,
+ "exists": ExistsAttributeSolver,
+ "any": AnyResourceSolver,
+ "contains": ContainsAttributeSolver,
+ "not_exists": NotExistsAttributeSolver,
+ "within": WithinAttributeSolver,
+ "not_contains": NotContainsAttributeSolver,
+ "starting_with": StartingWithAttributeSolver,
+ "not_starting_with": NotStartingWithAttributeSolver,
+ "ending_with": EndingWithAttributeSolver,
+ "not_ending_with": NotEndingWithAttributeSolver,
+}
+
+operators_to_complex_solver_classes = {
+ "and": AndSolver,
+ "or": OrSolver,
+}
+
+operator_to_connection_solver_classes = {"exists": ConnectionExistsSolver, "not_exists": ConnectionNotExistsSolver}
+
+operator_to_complex_connection_solver_classes = {"and": AndConnectionSolver, "or": OrConnectionSolver}
+
+operator_to_filter_solver_classes = {
+ "within": WithinFilterSolver,
+}
+
+condition_type_to_solver_type = {
+ "": SolverType.ATTRIBUTE,
+ "attribute": SolverType.ATTRIBUTE,
+ "connection": SolverType.CONNECTION,
+ "filter": SolverType.FILTER,
+}
+
+
+class NXGraphCheckParser(BaseGraphCheckParser):
+ def parse_raw_check(self, raw_check: Dict[str, Dict[str, Any]], **kwargs: Any) -> BaseGraphCheck:
+ policy_definition = raw_check.get("definition", {})
+ check = self._parse_raw_check(policy_definition, kwargs.get("resources_types"))
+ check.id = raw_check.get("metadata", {}).get("id", "")
+ check.name = raw_check.get("metadata", {}).get("name", "")
+ if bc_integration.ckv_to_bc_id_mapping:
+ check.bc_id = bc_integration.ckv_to_bc_id_mapping.get(check.id)
+ solver = self.get_check_solver(check)
+ check.set_solver(solver)
+
+ return check
+
+ def _parse_raw_check(self, raw_check: Dict[str, Any], resources_types: Optional[List[str]]) -> BaseGraphCheck:
+ check = BaseGraphCheck()
+ complex_operator = get_complex_operator(raw_check)
+ if complex_operator:
+ check.type = SolverType.COMPLEX
+ check.operator = complex_operator
+ sub_solvers = raw_check.get(complex_operator, [])
+ for sub_solver in sub_solvers:
+ check.sub_checks.append(self._parse_raw_check(sub_solver, resources_types))
+ resources_types_of_sub_solvers = [
+ q.resource_types for q in check.sub_checks if q is not None and q.resource_types is not None
+ ]
+ check.resource_types = list(set(sum(resources_types_of_sub_solvers, [])))
+ if any(q.type in [SolverType.CONNECTION, SolverType.COMPLEX_CONNECTION] for q in check.sub_checks):
+ check.type = SolverType.COMPLEX_CONNECTION
+
+ else:
+ resource_type = raw_check.get("resource_types", [])
+ if (
+ not resource_type
+ or (isinstance(resource_type, str) and resource_type.lower() == "all")
+ or (isinstance(resource_type, list) and resource_type[0].lower() == "all")
+ ):
+ check.resource_types = resources_types
+ else:
+ check.resource_types = resource_type
+
+ connected_resources_type = raw_check.get("connected_resource_types", [])
+ if connected_resources_type == ["All"] or connected_resources_type == "all":
+ check.connected_resources_types = resources_types
+ else:
+ check.connected_resources_types = connected_resources_type
+
+ condition_type = raw_check.get("cond_type", "")
+ check.type = condition_type_to_solver_type.get(condition_type)
+ if condition_type == "":
+ check.operator = "any"
+ else:
+ check.operator = raw_check.get("operator")
+ check.attribute = raw_check.get("attribute")
+ check.attribute_value = raw_check.get("value")
+
+ return check
+
+ def get_check_solver(self, check: BaseGraphCheck) -> BaseSolver:
+ sub_solvers: List[BaseSolver] = []
+ if check.sub_checks:
+ sub_solvers = []
+ for sub_solver in check.sub_checks:
+ sub_solvers.append(self.get_check_solver(sub_solver))
+
+ type_to_solver = {
+ SolverType.COMPLEX_CONNECTION: operator_to_complex_connection_solver_classes.get(
+ check.operator, lambda *args: None
+ )(sub_solvers, check.operator),
+ SolverType.COMPLEX: operators_to_complex_solver_classes.get(check.operator, lambda *args: None)(
+ sub_solvers, check.resource_types
+ ),
+ SolverType.ATTRIBUTE: operators_to_attributes_solver_classes.get(check.operator, lambda *args: None)(
+ check.resource_types, check.attribute, check.attribute_value
+ ),
+ SolverType.CONNECTION: operator_to_connection_solver_classes.get(check.operator, lambda *args: None)(
+ check.resource_types, check.connected_resources_types
+ ),
+ SolverType.FILTER: operator_to_filter_solver_classes.get(check.operator, lambda *args: None)(
+ check.resource_types, check.attribute, check.attribute_value
+ ),
+ }
+
+ solver = type_to_solver.get(check.type)
+ if not solver:
+ raise NotImplementedError(f"solver type {check.type} with operator {check.operator} is not supported")
+ return solver
+
+
+def get_complex_operator(raw_check: Dict[str, Any]) -> Optional[str]:
+ for operator in operators_to_complex_solver_classes.keys():
+ if raw_check.get(operator):
+ return operator
+ return None
diff --git a/checkov/common/checks_infra/registry.py b/checkov/common/checks_infra/registry.py
new file mode 100644
index 0000000000..269af44144
--- /dev/null
+++ b/checkov/common/checks_infra/registry.py
@@ -0,0 +1,66 @@
+import json
+import logging
+import os
+from pathlib import Path
+from typing import Dict, Any, Optional, List
+
+import yaml
+
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.graph.checks_infra.base_parser import BaseGraphCheckParser
+from checkov.common.graph.checks_infra.registry import BaseRegistry
+from checkov.runner_filter import RunnerFilter
+from checkov.common.checks_infra.resources_types import resources_types
+
+CHECKS_POSSIBLE_ENDING = [".yaml", ".yml"]
+
+
+class Registry(BaseRegistry):
+ def __init__(self, checks_dir: str, parser: BaseGraphCheckParser = BaseGraphCheckParser()) -> None:
+ super().__init__(parser)
+ self.checks = []
+ self.parser = parser
+ self.checks_dir = checks_dir
+ self.logger = logging.getLogger(__name__)
+
+ def load_checks(self) -> None:
+ self._load_checks_from_dir(self.checks_dir)
+
+ def _load_checks_from_dir(self, directory: str) -> None:
+ dir = os.path.expanduser(directory)
+ self.logger.info("Loading external checks from {}".format(dir))
+ for root, d_names, f_names in os.walk(dir):
+ self.logger.info(f"Searching through {d_names} and {f_names}")
+ for file in f_names:
+ file_ending = os.path.splitext(file)[1]
+ if file_ending in CHECKS_POSSIBLE_ENDING:
+ with open(f"{root}/{file}", "r") as f:
+ if dir != self.checks_dir:
+ self.logger.info(f"loading {file}")
+ check_yaml = yaml.safe_load(f)
+ check_json = json.loads(json.dumps(check_yaml))
+ check = self.parser.parse_raw_check(
+ check_json, resources_types=self._get_resource_types(check_json)
+ )
+ if not any([c for c in self.checks if check.id == c.id]):
+ # Note the external check; used in the should_run_check logic
+ RunnerFilter.notify_external_check(check.id)
+ self.checks.append(check)
+
+ def load_external_checks(self, dir: str) -> None:
+ self._load_checks_from_dir(dir)
+
+ @staticmethod
+ def _get_resource_types(check_json: Dict[str, Dict[str, Any]]) -> Optional[List[str]]:
+ provider = check_json.get("scope", {}).get("provider", "").lower()
+ return resources_types.get(provider)
+
+
+_registry_instances = {}
+
+
+def get_graph_checks_registry(check_type):
+ if not _registry_instances.get(check_type):
+ _registry_instances[check_type] = Registry(parser=NXGraphCheckParser(),
+ checks_dir=f"{Path(__file__).parent.parent.parent}/{check_type}/checks/graph_checks")
+ return _registry_instances[check_type]
diff --git a/checkov/common/checks_infra/resources_types.py b/checkov/common/checks_infra/resources_types.py
new file mode 100644
index 0000000000..d0fc4d9403
--- /dev/null
+++ b/checkov/common/checks_infra/resources_types.py
@@ -0,0 +1,1291 @@
+resources_types = {
+ "aws": [
+ "aws",
+ "aws_root",
+ "aws_root_access_key",
+ "aws_acm_certificate",
+ "aws_acm_certificate_validation",
+ "aws_acmpca_certificate_authority",
+ "aws_api_gateway_account",
+ "aws_api_gateway_api_key",
+ "aws_api_gateway_authorizer",
+ "aws_api_gateway_base_path_mapping",
+ "aws_api_gateway_client_certificate",
+ "aws_api_gateway_deployment",
+ "aws_api_gateway_documentation_part",
+ "aws_api_gateway_documentation_version",
+ "aws_api_gateway_domain_name",
+ "aws_api_gateway_gateway_response",
+ "aws_api_gateway_integration",
+ "aws_api_gateway_integration_response",
+ "aws_api_gateway_method",
+ "aws_api_gateway_method_response",
+ "aws_api_gateway_method_settings",
+ "aws_api_gateway_model",
+ "aws_api_gateway_request_validator",
+ "aws_api_gateway_resource",
+ "aws_api_gateway_rest_api",
+ "aws_api_gateway_stage",
+ "aws_api_gateway_usage_plan",
+ "aws_api_gateway_usage_plan_key",
+ "aws_api_gateway_vpc_link",
+ "aws_apigatewayv2_api",
+ "aws_apigatewayv2_api_mapping",
+ "aws_apigatewayv2_authorizer",
+ "aws_apigatewayv2_deployment",
+ "aws_apigatewayv2_domain_name",
+ "aws_apigatewayv2_integration",
+ "aws_apigatewayv2_integration_response",
+ "aws_apigatewayv2_model",
+ "aws_apigatewayv2_route",
+ "aws_apigatewayv2_route_response",
+ "aws_apigatewayv2_stage",
+ "aws_apigatewayv2_vpc_link",
+ "aws_accessanalyzer_analyzer",
+ "aws_appmesh_mesh",
+ "aws_appmesh_route",
+ "aws_appmesh_virtual_node",
+ "aws_appmesh_virtual_router",
+ "aws_appmesh_virtual_service",
+ "aws_appsync_api_key",
+ "aws_appsync_datasource",
+ "aws_appsync_function",
+ "aws_appsync_graphql_api",
+ "aws_appsync_resolver",
+ "aws_appautoscaling_policy",
+ "aws_appautoscaling_scheduled_action",
+ "aws_appautoscaling_target",
+ "aws_athena_database",
+ "aws_athena_named_query",
+ "aws_athena_workgroup",
+ "aws_autoscaling_attachment",
+ "aws_autoscaling_group",
+ "aws_autoscaling_lifecycle_hook",
+ "aws_autoscaling_notification",
+ "aws_autoscaling_policy",
+ "aws_autoscaling_schedule",
+ "aws_backup_plan",
+ "aws_backup_selection",
+ "aws_backup_vault",
+ "aws_batch_compute_environment",
+ "aws_batch_job_definition",
+ "aws_batch_job_queue",
+ "aws_budgets_budget",
+ "aws_cloud9_environment_ec2",
+ "aws_cloudformation_stack",
+ "aws_cloudformation_stack_set",
+ "aws_cloudformation_stack_set_instance",
+ "aws_cloudfront_distribution",
+ "aws_cloudfront_origin_access_identity",
+ "aws_cloudfront_public_key",
+ "aws_cloudhsm_v2_cluster",
+ "aws_cloudhsm_v2_hsm",
+ "aws_cloudtrail",
+ "aws_cloudwatch_dashboard",
+ "aws_cloudwatch_event_permission",
+ "aws_cloudwatch_event_rule",
+ "aws_cloudwatch_event_target",
+ "aws_cloudwatch_log_destination",
+ "aws_cloudwatch_log_destination_policy",
+ "aws_cloudwatch_log_group",
+ "aws_cloudwatch_log_metric_filter",
+ "aws_cloudwatch_log_resource_policy",
+ "aws_cloudwatch_log_stream",
+ "aws_cloudwatch_log_subscription_filter",
+ "aws_cloudwatch_metric_alarm",
+ "aws_codebuild_project",
+ "aws_codebuild_source_credential",
+ "aws_codebuild_webhook",
+ "aws_codecommit_repository",
+ "aws_codecommit_trigger",
+ "aws_codedeploy_app",
+ "aws_codedeploy_deployment_config",
+ "aws_codedeploy_deployment_group",
+ "aws_codepipeline",
+ "aws_codepipeline_webhook",
+ "aws_codestarnotifications_notification_rule",
+ "aws_cognito_identity_pool",
+ "aws_cognito_identity_pool_roles_attachment",
+ "aws_cognito_identity_provider",
+ "aws_cognito_resource_server",
+ "aws_cognito_user_group",
+ "aws_cognito_user_pool",
+ "aws_cognito_user_pool_client",
+ "aws_cognito_user_pool_domain",
+ "aws_config_aggregate_authorization",
+ "aws_config_config_rule",
+ "aws_config_configuration_aggregator",
+ "aws_config_configuration_recorder",
+ "aws_config_configuration_recorder_status",
+ "aws_config_delivery_channel",
+ "aws_config_organization_custom_rule",
+ "aws_config_organization_managed_rule",
+ "aws_cur_report_definition",
+ "aws_dlm_lifecycle_policy",
+ "aws_datapipeline_pipeline",
+ "aws_datasync_agent",
+ "aws_datasync_location_efs",
+ "aws_datasync_location_nfs",
+ "aws_datasync_location_s3",
+ "aws_datasync_location_smb",
+ "aws_datasync_task",
+ "aws_dms_certificate",
+ "aws_dms_endpoint",
+ "aws_dms_event_subscription",
+ "aws_dms_replication_instance",
+ "aws_dms_replication_subnet_group",
+ "aws_dms_replication_task",
+ "aws_devicefarm_project",
+ "aws_dx_bgp_peer",
+ "aws_dx_connection",
+ "aws_dx_connection_association",
+ "aws_dx_gateway",
+ "aws_dx_gateway_association",
+ "aws_dx_gateway_association_proposal",
+ "aws_dx_hosted_private_virtual_interface",
+ "aws_dx_hosted_private_virtual_interface_accepter",
+ "aws_dx_hosted_public_virtual_interface",
+ "aws_dx_hosted_public_virtual_interface_accepter",
+ "aws_dx_hosted_transit_virtual_interface",
+ "aws_dx_hosted_transit_virtual_interface_accepter",
+ "aws_dx_lag",
+ "aws_dx_private_virtual_interface",
+ "aws_dx_public_virtual_interface",
+ "aws_dx_transit_virtual_interface",
+ "aws_directory_service_conditional_forwarder",
+ "aws_directory_service_directory",
+ "aws_directory_service_log_subscription",
+ "aws_docdb_cluster",
+ "aws_docdb_cluster_instance",
+ "aws_docdb_cluster_parameter_group",
+ "aws_docdb_cluster_snapshot",
+ "aws_docdb_subnet_group",
+ "aws_dynamodb_global_table",
+ "aws_dynamodb_table",
+ "aws_dynamodb_table_item",
+ "aws_dax_cluster",
+ "aws_dax_parameter_group",
+ "aws_dax_subnet_group",
+ "aws_ami",
+ "aws_ami_copy",
+ "aws_ami_from_instance",
+ "aws_ami_launch_permission",
+ "aws_ebs_default_kms_key",
+ "aws_ebs_encryption_by_default",
+ "aws_ebs_snapshot",
+ "aws_ebs_snapshot_copy",
+ "aws_ebs_volume",
+ "aws_ec2_availability_zone_group",
+ "aws_ec2_capacity_reservation",
+ "aws_ec2_client_vpn_authorization_rule",
+ "aws_ec2_client_vpn_endpoint",
+ "aws_ec2_client_vpn_network_association",
+ "aws_ec2_client_vpn_route",
+ "aws_ec2_fleet",
+ "aws_ec2_local_gateway_route",
+ "aws_ec2_local_gateway_route_table_vpc_association",
+ "aws_ec2_tag",
+ "aws_ec2_traffic_mirror_filter",
+ "aws_ec2_traffic_mirror_filter_rule",
+ "aws_ec2_traffic_mirror_session",
+ "aws_ec2_traffic_mirror_target",
+ "aws_ec2_transit_gateway",
+ "aws_ec2_transit_gateway_peering_attachment",
+ "aws_ec2_transit_gateway_peering_attachment_accepter",
+ "aws_ec2_transit_gateway_route",
+ "aws_ec2_transit_gateway_route_table",
+ "aws_ec2_transit_gateway_route_table_association",
+ "aws_ec2_transit_gateway_route_table_propagation",
+ "aws_ec2_transit_gateway_vpc_attachment",
+ "aws_ec2_transit_gateway_vpc_attachment_accepter",
+ "aws_eip",
+ "aws_eip_association",
+ "aws_instance",
+ "aws_key_pair",
+ "aws_launch_configuration",
+ "aws_launch_template",
+ "aws_placement_group",
+ "aws_snapshot_create_volume_permission",
+ "aws_spot_datafeed_subscription",
+ "aws_spot_fleet_request",
+ "aws_spot_instance_request",
+ "aws_volume_attachment",
+ "aws_ecr_lifecycle_policy",
+ "aws_ecr_repository",
+ "aws_ecr_repository_policy",
+ "aws_ecs_capacity_provider",
+ "aws_ecs_cluster",
+ "aws_ecs_service",
+ "aws_ecs_task_definition",
+ "aws_efs_access_point",
+ "aws_efs_file_system",
+ "aws_efs_file_system_policy",
+ "aws_efs_mount_target",
+ "aws_eks_cluster",
+ "aws_eks_fargate_profile",
+ "aws_eks_node_group",
+ "aws_elasticache_cluster",
+ "aws_elasticache_parameter_group",
+ "aws_elasticache_replication_group",
+ "aws_elasticache_security_group",
+ "aws_elasticache_subnet_group",
+ "aws_elastic_beanstalk_application",
+ "aws_elastic_beanstalk_application_version",
+ "aws_elastic_beanstalk_configuration_template",
+ "aws_elastic_beanstalk_environment",
+ "aws_app_cookie_stickiness_policy",
+ "aws_elb",
+ "aws_elb_attachment",
+ "aws_lb_cookie_stickiness_policy",
+ "aws_lb_ssl_negotiation_policy",
+ "aws_load_balancer_backend_server_policy",
+ "aws_load_balancer_listener_policy",
+ "aws_load_balancer_policy",
+ "aws_proxy_protocol_policy",
+ "aws_lb",
+ "aws_lb_listener",
+ "aws_lb_listener_certificate",
+ "aws_lb_listener_rule",
+ "aws_lb_target_group",
+ "aws_lb_target_group_attachment",
+ "aws_emr_cluster",
+ "aws_emr_instance_group",
+ "aws_emr_security_configuration",
+ "aws_elastictranscoder_pipeline",
+ "aws_elastictranscoder_preset",
+ "aws_elasticsearch_domain",
+ "aws_elasticsearch_domain_policy",
+ "aws_fsx_lustre_file_system",
+ "aws_fsx_windows_file_system",
+ "aws_fms_admin_account",
+ "aws_gamelift_alias",
+ "aws_gamelift_build",
+ "aws_gamelift_fleet",
+ "aws_gamelift_game_session_queue",
+ "aws_glacier_vault",
+ "aws_glacier_vault_lock",
+ "aws_globalaccelerator_accelerator",
+ "aws_globalaccelerator_endpoint_group",
+ "aws_globalaccelerator_listener",
+ "aws_glue_catalog_database",
+ "aws_glue_catalog_table",
+ "aws_glue_classifier",
+ "aws_glue_connection",
+ "aws_glue_crawler",
+ "aws_glue_job",
+ "aws_glue_security_configuration",
+ "aws_glue_trigger",
+ "aws_glue_workflow",
+ "aws_guardduty_detector",
+ "aws_guardduty_invite_accepter",
+ "aws_guardduty_ipset",
+ "aws_guardduty_member",
+ "aws_guardduty_organization_admin_account",
+ "aws_guardduty_organization_configuration",
+ "aws_guardduty_threatintelset",
+ "aws_iam_access_key",
+ "aws_iam_account_alias",
+ "aws_iam_account_password_policy",
+ "aws_iam_group",
+ "aws_iam_group_membership",
+ "aws_iam_group_policy",
+ "aws_iam_group_policy_attachment",
+ "aws_iam_instance_profile",
+ "aws_iam_openid_connect_provider",
+ "aws_iam_policy",
+ "aws_iam_policy_attachment",
+ "aws_iam_policy_document",
+ "aws_iam_role",
+ "aws_iam_role_policy",
+ "aws_iam_role_policy_attachment",
+ "aws_iam_saml_provider",
+ "aws_iam_server_certificate",
+ "aws_iam_service_linked_role",
+ "aws_iam_user",
+ "aws_iam_user_group_membership",
+ "aws_iam_user_login_profile",
+ "aws_iam_user_policy",
+ "aws_iam_user_policy_attachment",
+ "aws_iam_user_ssh_key",
+ "aws_inspector_assessment_target",
+ "aws_inspector_assessment_template",
+ "aws_inspector_resource_group",
+ "aws_iot_certificate",
+ "aws_iot_policy",
+ "aws_iot_policy_attachment",
+ "aws_iot_role_alias",
+ "aws_iot_thing",
+ "aws_iot_thing_principal_attachment",
+ "aws_iot_thing_type",
+ "aws_iot_topic_rule",
+ "aws_kms_alias",
+ "aws_kms_ciphertext",
+ "aws_kms_external_key",
+ "aws_kms_grant",
+ "aws_kms_key",
+ "aws_kinesis_analytics_application",
+ "aws_kinesis_stream",
+ "aws_kinesis_firehose_delivery_stream",
+ "aws_kinesis_video_stream",
+ "aws_lambda_alias",
+ "aws_lambda_event_source_mapping",
+ "aws_lambda_function",
+ "aws_lambda_function_event_invoke_config",
+ "aws_lambda_layer_version",
+ "aws_lambda_permission",
+ "aws_lambda_provisioned_concurrency_config",
+ "aws_licensemanager_association",
+ "aws_licensemanager_license_configuration",
+ "aws_lightsail_domain",
+ "aws_lightsail_instance",
+ "aws_lightsail_key_pair",
+ "aws_lightsail_static_ip",
+ "aws_lightsail_static_ip_attachment",
+ "aws_mq_broker",
+ "aws_mq_configuration",
+ "aws_macie_member_account_association",
+ "aws_macie_s3_bucket_association",
+ "aws_msk_cluster",
+ "aws_msk_configuration",
+ "aws_media_convert_queue",
+ "aws_media_package_channel",
+ "aws_media_store_container",
+ "aws_media_store_container_policy",
+ "aws_neptune_cluster",
+ "aws_neptune_cluster_instance",
+ "aws_neptune_cluster_parameter_group",
+ "aws_neptune_cluster_snapshot",
+ "aws_neptune_event_subscription",
+ "aws_neptune_parameter_group",
+ "aws_neptune_subnet_group",
+ "aws_opsworks_application",
+ "aws_opsworks_custom_layer",
+ "aws_opsworks_ganglia_layer",
+ "aws_opsworks_haproxy_layer",
+ "aws_opsworks_instance",
+ "aws_opsworks_java_app_layer",
+ "aws_opsworks_memcached_layer",
+ "aws_opsworks_mysql_layer",
+ "aws_opsworks_nodejs_app_layer",
+ "aws_opsworks_permission",
+ "aws_opsworks_php_app_layer",
+ "aws_opsworks_rails_app_layer",
+ "aws_opsworks_rds_db_instance",
+ "aws_opsworks_stack",
+ "aws_opsworks_static_web_layer",
+ "aws_opsworks_user_profile",
+ "aws_organizations_account",
+ "aws_organizations_organization",
+ "aws_organizations_organizational_unit",
+ "aws_organizations_policy",
+ "aws_organizations_policy_attachment",
+ "aws_pinpoint_adm_channel",
+ "aws_pinpoint_apns_channel",
+ "aws_pinpoint_apns_sandbox_channel",
+ "aws_pinpoint_apns_voip_channel",
+ "aws_pinpoint_apns_voip_sandbox_channel",
+ "aws_pinpoint_app",
+ "aws_pinpoint_baidu_channel",
+ "aws_pinpoint_email_channel",
+ "aws_pinpoint_event_stream",
+ "aws_pinpoint_gcm_channel",
+ "aws_pinpoint_sms_channel",
+ "aws_qldb_ledger",
+ "aws_quicksight_group",
+ "aws_quicksight_user",
+ "aws_ram_principal_association",
+ "aws_ram_resource_association",
+ "aws_ram_resource_share",
+ "aws_ram_resource_share_accepter",
+ "aws_db_cluster_snapshot",
+ "aws_db_event_subscription",
+ "aws_db_instance",
+ "aws_db_instance_role_association",
+ "aws_db_option_group",
+ "aws_db_parameter_group",
+ "aws_db_security_group",
+ "aws_db_snapshot",
+ "aws_db_subnet_group",
+ "aws_rds_cluster",
+ "aws_rds_cluster_endpoint",
+ "aws_rds_cluster_instance",
+ "aws_rds_cluster_parameter_group",
+ "aws_rds_global_cluster",
+ "aws_redshift_cluster",
+ "aws_redshift_event_subscription",
+ "aws_redshift_parameter_group",
+ "aws_redshift_security_group",
+ "aws_redshift_snapshot_copy_grant",
+ "aws_redshift_snapshot_schedule",
+ "aws_redshift_snapshot_schedule_association",
+ "aws_redshift_subnet_group",
+ "aws_resourcegroups_group",
+ "aws_route53_delegation_set",
+ "aws_route53_health_check",
+ "aws_route53_query_log",
+ "aws_route53_record",
+ "aws_route53_zone",
+ "aws_route53_zone_association",
+ "aws_route53_resolver_endpoint",
+ "aws_route53_resolver_rule",
+ "aws_route53_resolver_rule_association",
+ "aws_s3_access_point",
+ "aws_s3_account_public_access_block",
+ "aws_s3_bucket",
+ "aws_s3_bucket_analytics_configuration",
+ "aws_s3_bucket_inventory",
+ "aws_s3_bucket_metric",
+ "aws_s3_bucket_notification",
+ "aws_s3_bucket_object",
+ "aws_s3_bucket_policy",
+ "aws_s3_bucket_public_access_block",
+ "aws_ses_active_receipt_rule_set",
+ "aws_ses_configuration_set",
+ "aws_ses_domain_dkim",
+ "aws_ses_domain_identity",
+ "aws_ses_domain_identity_verification",
+ "aws_ses_domain_mail_from",
+ "aws_ses_email_identity",
+ "aws_ses_event_destination",
+ "aws_ses_identity_notification_topic",
+ "aws_ses_identity_policy",
+ "aws_ses_receipt_filter",
+ "aws_ses_receipt_rule",
+ "aws_ses_receipt_rule_set",
+ "aws_ses_template",
+ "aws_sns_platform_application",
+ "aws_sns_sms_preferences",
+ "aws_sns_topic",
+ "aws_sns_topic_policy",
+ "aws_sns_topic_subscription",
+ "aws_sqs_queue",
+ "aws_sqs_queue_policy",
+ "aws_ssm_activation",
+ "aws_ssm_association",
+ "aws_ssm_document",
+ "aws_ssm_maintenance_window",
+ "aws_ssm_maintenance_window_target",
+ "aws_ssm_maintenance_window_task",
+ "aws_ssm_parameter",
+ "aws_ssm_patch_baseline",
+ "aws_ssm_patch_group",
+ "aws_ssm_resource_data_sync",
+ "aws_swf_domain",
+ "aws_sagemaker_endpoint",
+ "aws_sagemaker_endpoint_configuration",
+ "aws_sagemaker_model",
+ "aws_sagemaker_notebook_instance",
+ "aws_sagemaker_notebook_instance_lifecycle_configuration",
+ "aws_secretsmanager_secret",
+ "aws_secretsmanager_secret_rotation",
+ "aws_secretsmanager_secret_version",
+ "aws_securityhub_account",
+ "aws_securityhub_member",
+ "aws_securityhub_product_subscription",
+ "aws_securityhub_standards_subscription",
+ "aws_servicecatalog_portfolio",
+ "aws_service_discovery_http_namespace",
+ "aws_service_discovery_private_dns_namespace",
+ "aws_service_discovery_public_dns_namespace",
+ "aws_service_discovery_service",
+ "aws_servicequotas_service_quota",
+ "aws_shield_protection",
+ "aws_simpledb_domain",
+ "aws_sfn_activity",
+ "aws_sfn_state_machine",
+ "aws_storagegateway_cache",
+ "aws_storagegateway_cached_iscsi_volume",
+ "aws_storagegateway_gateway",
+ "aws_storagegateway_nfs_file_share",
+ "aws_storagegateway_smb_file_share",
+ "aws_storagegateway_upload_buffer",
+ "aws_storagegateway_working_storage",
+ "aws_transfer_server",
+ "aws_transfer_ssh_key",
+ "aws_transfer_user",
+ "aws_customer_gateway",
+ "aws_default_network_acl",
+ "aws_default_route_table",
+ "aws_default_security_group",
+ "aws_default_subnet",
+ "aws_default_vpc",
+ "aws_default_vpc_dhcp_options",
+ "aws_egress_only_internet_gateway",
+ "aws_flow_log",
+ "aws_internet_gateway",
+ "aws_main_route_table_association",
+ "aws_nat_gateway",
+ "aws_network_acl",
+ "aws_network_acl_rule",
+ "aws_network_interface",
+ "aws_network_interface_attachment",
+ "aws_network_interface_sg_attachment",
+ "aws_route",
+ "aws_route_table",
+ "aws_route_table_association",
+ "aws_security_group",
+ "aws_security_group_rule",
+ "aws_subnet",
+ "aws_vpc",
+ "aws_vpc_dhcp_options",
+ "aws_vpc_dhcp_options_association",
+ "aws_vpc_endpoint",
+ "aws_vpc_endpoint_connection_notification",
+ "aws_vpc_endpoint_route_table_association",
+ "aws_vpc_endpoint_service",
+ "aws_vpc_endpoint_service_allowed_principal",
+ "aws_vpc_endpoint_subnet_association",
+ "aws_vpc_ipv4_cidr_block_association",
+ "aws_vpc_peering_connection",
+ "aws_vpc_peering_connection_accepter",
+ "aws_vpc_peering_connection_options",
+ "aws_vpn_connection",
+ "aws_vpn_connection_route",
+ "aws_vpn_gateway",
+ "aws_vpn_gateway_attachment",
+ "aws_vpn_gateway_route_propagation",
+ "aws_waf_byte_match_set",
+ "aws_waf_geo_match_set",
+ "aws_waf_ipset",
+ "aws_waf_rate_based_rule",
+ "aws_waf_regex_match_set",
+ "aws_waf_regex_pattern_set",
+ "aws_waf_rule",
+ "aws_waf_rule_group",
+ "aws_waf_size_constraint_set",
+ "aws_waf_sql_injection_match_set",
+ "aws_waf_web_acl",
+ "aws_waf_xss_match_set",
+ "aws_wafregional_byte_match_set",
+ "aws_wafregional_geo_match_set",
+ "aws_wafregional_ipset",
+ "aws_wafregional_rate_based_rule",
+ "aws_wafregional_regex_match_set",
+ "aws_wafregional_regex_pattern_set",
+ "aws_wafregional_rule",
+ "aws_wafregional_rule_group",
+ "aws_wafregional_size_constraint_set",
+ "aws_wafregional_sql_injection_match_set",
+ "aws_wafregional_web_acl",
+ "aws_wafregional_web_acl_association",
+ "aws_wafregional_xss_match_set",
+ "aws_wafv2_ip_set",
+ "aws_wafv2_regex_pattern_set",
+ "aws_wafv2_rule_group",
+ "aws_wafv2_web_acl",
+ "aws_wafv2_web_acl_association",
+ "aws_wafv2_web_acl_logging_configuration",
+ "aws_worklink_fleet",
+ "aws_worklink_website_certificate_authority_association",
+ "aws_workspaces_directory",
+ "aws_workspaces_ip_group",
+ "aws_workspaces_workspace",
+ "aws_xray_sampling_rule",
+ "aws_route53_vpc_association_authorization"
+ ],
+ "gcp": [
+ "google_access_context_manager_access_level",
+ "google_access_context_manager_access_policy",
+ "google_access_context_manager_service_perimeter",
+ "google_access_context_manager_service_perimeter_resource",
+ "google_app_engine_application",
+ "google_app_engine_application_url_dispatch_rules",
+ "google_app_engine_domain_mapping",
+ "google_app_engine_firewall_rule",
+ "google_app_engine_flexible_app_version",
+ "google_app_engine_service_split_traffic",
+ "google_app_engine_standard_app_version",
+ "google_bigquery_dataset",
+ "google_bigquery_dataset_access",
+ "google_bigquery_dataset_iam",
+ "google_bigquery_job",
+ "google_bigquery_table",
+ "google_bigquery_data_transfer_config",
+ "google_binary_authorization_attestor",
+ "google_binary_authorization_attestor_iam",
+ "google_binary_authorization_policy",
+ "google_logging_billing_account_bucket_config",
+ "google_logging_billing_account_exclusion",
+ "google_logging_billing_account_sink",
+ "google_logging_folder_bucket_config",
+ "google_logging_folder_exclusion",
+ "google_logging_folder_sink",
+ "google_logging_metric",
+ "google_logging_organization_bucket_config",
+ "google_logging_organization_exclusion",
+ "google_logging_organization_sink",
+ "google_logging_project_bucket_config",
+ "google_logging_project_exclusion",
+ "google_logging_project_sink",
+ "google_monitoring_alert_policy",
+ "google_monitoring_dashboard",
+ "google_monitoring_group",
+ "google_monitoring_metric_descriptor",
+ "google_monitoring_notification_channel",
+ "google_monitoring_service",
+ "google_monitoring_slo",
+ "google_monitoring_uptime_check_config",
+ "google_cloud_asset_folder_feed",
+ "google_cloud_asset_organization_feed",
+ "google_cloud_asset_project_feed",
+ "google_bigtable_app_profile",
+ "google_bigtable_gc_policy",
+ "google_bigtable_instance",
+ "google_bigtable_instance_iam",
+ "google_bigtable_table",
+ "google_cloudbuild_trigger",
+ "google_composer_environment",
+ "google_dns_managed_zone",
+ "google_dns_policy",
+ "google_dns_record_set",
+ "google_deployment_manager_deployment",
+ "google_endpoints_service",
+ "google_endpoints_service_iam",
+ "google_cloudfunctions_cloud_function_iam",
+ "google_cloudfunctions_function",
+ "google_healthcare_dataset",
+ "google_healthcare_dataset_iam",
+ "google_healthcare_dicom_store",
+ "google_healthcare_dicom_store_iam",
+ "google_healthcare_fhir_store",
+ "google_healthcare_fhir_store_iam",
+ "google_healthcare_hl7_v2_store",
+ "google_healthcare_hl7_v2_store_iam",
+ "google_cloudiot_device",
+ "google_cloudiot_device_registry",
+ "google_kms_crypto_key_iam",
+ "google_kms_key_ring_iam",
+ "google_kms_crypto_key",
+ "google_kms_key_ring",
+ "google_kms_key_ring_import_job",
+ "google_kms_secret_ciphertext",
+ "google_billing_account_iam_binding",
+ "google_billing_account_iam_member",
+ "google_billing_account_iam_policy",
+ "google_folder",
+ "google_folder_iam_audit_config",
+ "google_folder_iam_binding",
+ "google_folder_iam_member",
+ "google_folder_iam_policy",
+ "google_folder_organization_policy",
+ "google_organization_iam_audit_config",
+ "google_organization_iam_binding",
+ "google_organization_iam_custom_role",
+ "google_organization_iam_member",
+ "google_organization_iam_policy",
+ "google_organization_policy",
+ "google_project",
+ "google_project_iam_binding",
+ "google_project_iam_member",
+ "google_project_iam_custom_role",
+ "google_project_organization_policy",
+ "google_project_service",
+ "google_service_account",
+ "google_service_account_iam",
+ "google_service_account_key",
+ "google_usage_export_bucket",
+ "google_pubsub_subscription",
+ "google_pubsub_subscription_iam",
+ "google_pubsub_topic",
+ "google_pubsub_topic_iam",
+ "google_cloud_run_domain_mapping",
+ "google_cloud_run_service",
+ "google_cloud_run_service_iam",
+ "google_sql_database",
+ "google_sql_database_instance",
+ "google_sql_source_representation_instance",
+ "google_sql_ssl_cert",
+ "google_sql_user",
+ "google_cloud_scheduler_job",
+ "google_sourcerepo_repository",
+ "google_sourcerepo_repository_iam",
+ "google_spanner_database",
+ "google_spanner_database_iam",
+ "google_spanner_instance",
+ "google_spanner_instance_iam",
+ "google_storage_bucket",
+ "google_storage_bucket_access_control",
+ "google_storage_bucket_acl",
+ "google_storage_bucket_iam_binding",
+ "google_storage_bucket_iam_member",
+ "google_storage_bucket_object",
+ "google_storage_default_object_access_control",
+ "google_storage_default_object_acl",
+ "google_storage_hmac_key",
+ "google_storage_notification",
+ "google_storage_object_access_control",
+ "google_storage_object_acl",
+ "google_tpu_node",
+ "google_cloud_tasks_queue",
+ "google_compute_address",
+ "google_compute_attached_disk",
+ "google_compute_autoscaler",
+ "google_compute_backend_bucket",
+ "google_compute_backend_bucket_signed_url_key",
+ "google_compute_backend_service",
+ "google_compute_backend_service_signed_url_key",
+ "google_compute_disk",
+ "google_compute_disk_resource_policy_attachment",
+ "google_compute_firewall",
+ "google_compute_forwarding_rule",
+ "google_compute_global_address",
+ "google_compute_global_forwarding_rule",
+ "google_compute_global_network_endpoint",
+ "google_compute_global_network_endpoint_group",
+ "google_compute_health_check",
+ "google_compute_http_health_check",
+ "google_compute_https_health_check",
+ "google_compute_image",
+ "google_compute_instance",
+ "google_compute_instance_from_template",
+ "google_compute_instance_group",
+ "google_compute_instance_group_manager",
+ "google_compute_instance_group_named_port",
+ "google_compute_instance_iam",
+ "google_compute_instance_template",
+ "google_compute_interconnect_attachment",
+ "google_compute_network",
+ "google_compute_network_endpoint",
+ "google_compute_network_endpoint_group",
+ "google_compute_network_peering",
+ "google_compute_network_peering_routes_config",
+ "google_compute_node_group",
+ "google_compute_node_template",
+ "google_compute_project_default_network_tier",
+ "google_compute_project_metadata",
+ "google_compute_project_metadata_item",
+ "google_compute_region_autoscaler",
+ "google_compute_region_backend_service",
+ "google_compute_region_disk",
+ "google_compute_region_disk_resource_policy_attachment",
+ "google_compute_region_health_check",
+ "google_compute_region_instance_group_manager",
+ "google_compute_region_ssl_certificate",
+ "google_compute_region_target_http_proxy",
+ "google_compute_region_target_https_proxy",
+ "google_compute_region_url_map",
+ "google_compute_reservation",
+ "google_compute_resource_policy",
+ "google_compute_route",
+ "google_compute_router",
+ "google_compute_router_bgp_peer",
+ "google_compute_router_interface",
+ "google_compute_router_nat",
+ "google_compute_security_policy",
+ "google_compute_shared_vpc_host_project",
+ "google_compute_shared_vpc_service_project",
+ "google_compute_snapshot",
+ "google_compute_ssl_certificate",
+ "google_compute_ssl_policy",
+ "google_compute_subnetwork",
+ "google_compute_subnetwork_iam",
+ "google_compute_target_http_proxy",
+ "google_compute_target_https_proxy",
+ "google_compute_target_instance",
+ "google_compute_target_pool",
+ "google_compute_target_ssl_proxy",
+ "google_compute_target_tcp_proxy",
+ "google_compute_url_map",
+ "google_compute_vpn_gateway",
+ "google_compute_vpn_tunnel",
+ "google_container_analysis_note",
+ "google_container_analysis_occurrence",
+ "google_container_registry",
+ "google_data_catalog_entry",
+ "google_data_catalog_entry_group",
+ "google_data_catalog_entry_group_iam",
+ "google_data_catalog_tag",
+ "google_data_catalog_tag_template",
+ "google_dataflow_job",
+ "google_dataproc_autoscaling_policy",
+ "google_dataproc_cluster",
+ "google_dataproc_cluster_iam",
+ "google_dataproc_job",
+ "google_dataproc_job_iam",
+ "google_datastore_index",
+ "google_dialogflow_agent",
+ "google_dialogflow_entity_type",
+ "google_dialogflow_intent",
+ "google_filestore_instance",
+ "google_firestore_index",
+ "google_identity_platform_default_supported_idp_config",
+ "google_identity_platform_inbound_saml_config",
+ "google_identity_platform_oauth_idp_config",
+ "google_identity_platform_tenant",
+ "google_identity_platform_tenant_default_supported_idp_config",
+ "google_identity_platform_tenant_inbound_saml_config",
+ "google_identity_platform_tenant_oauth_idp_config",
+ "google_iap_app_engine_service_iam",
+ "google_iap_app_engine_version_iam",
+ "google_iap_brand",
+ "google_iap_client",
+ "google_iap_tunnel_instance_iam",
+ "google_iap_web_backend_service_iam",
+ "google_iap_web_iam",
+ "google_iap_web_type_app_engine_iam",
+ "google_iap_web_type_compute_iam",
+ "google_container_cluster",
+ "google_container_node_pool",
+ "google_ml_engine_model",
+ "google_redis_instance",
+ "google_network_management_connectivity_test_resource",
+ "google_os_config_patch_deployment",
+ "google_os_login_ssh_public_key",
+ "google_resource_manager_lien",
+ "google_runtimeconfig_config",
+ "google_runtimeconfig_config_iam",
+ "google_runtimeconfig_variable",
+ "google_secret_manager_secret",
+ "google_secret_manager_secret_iam",
+ "google_secret_manager_secret_version",
+ "google_scc_source",
+ "google_vpc_access_connector",
+ "google_service_networking_connection",
+ "google_storage_transfer_job",
+ "google_project_iam",
+ "google_storage_bucket_iam",
+ "google_dataflow_flex_template_job",
+ "google_active_directory_domain"
+ ],
+ "azure": [
+ "azurerm_api_management",
+ "azurerm_api_management_api",
+ "azurerm_api_management_api_operation",
+ "azurerm_api_management_api_operation_policy",
+ "azurerm_api_management_api_policy",
+ "azurerm_api_management_api_schema",
+ "azurerm_api_management_api_version_set",
+ "azurerm_api_management_authorization_server",
+ "azurerm_api_management_backend",
+ "azurerm_api_management_certificate",
+ "azurerm_api_management_diagnostic",
+ "azurerm_api_management_group",
+ "azurerm_api_management_group_user",
+ "azurerm_api_management_identity_provider_aad",
+ "azurerm_api_management_identity_provider_facebook",
+ "azurerm_api_management_identity_provider_google",
+ "azurerm_api_management_identity_provider_microsoft",
+ "azurerm_api_management_identity_provider_twitter",
+ "azurerm_api_management_logger",
+ "azurerm_api_management_named_value",
+ "azurerm_api_management_openid_connect_provider",
+ "azurerm_api_management_product",
+ "azurerm_api_management_product_api",
+ "azurerm_api_management_product_group",
+ "azurerm_api_management_product_policy",
+ "azurerm_api_management_property",
+ "azurerm_api_management_subscription",
+ "azurerm_api_management_user",
+ "azurerm_analysis_services_server",
+ "azurerm_app_configuration",
+ "azurerm_app_service",
+ "azurerm_app_service_active_slot",
+ "azurerm_app_service_certificate",
+ "azurerm_app_service_certificate_order",
+ "azurerm_app_service_custom_hostname_binding",
+ "azurerm_app_service_environment",
+ "azurerm_app_service_hybrid_connection",
+ "azurerm_app_service_plan",
+ "azurerm_app_service_slot",
+ "azurerm_app_service_slot_virtual_network_swift_connection",
+ "azurerm_app_service_source_control_token",
+ "azurerm_app_service_virtual_network_swift_connection",
+ "azurerm_function_app",
+ "azurerm_function_app_slot",
+ "azurerm_application_insights",
+ "azurerm_application_insights_analytics_item",
+ "azurerm_application_insights_api_key",
+ "azurerm_application_insights_web_test",
+ "azurerm_role_assignment",
+ "azurerm_role_definition",
+ "azurerm_user_assigned_identity",
+ "azurerm_automation_account",
+ "azurerm_automation_certificate",
+ "azurerm_automation_connection",
+ "azurerm_automation_connection_certificate",
+ "azurerm_automation_connection_classic_certificate",
+ "azurerm_automation_connection_service_principal",
+ "azurerm_automation_credential",
+ "azurerm_automation_dsc_configuration",
+ "azurerm_automation_dsc_nodeconfiguration",
+ "azurerm_automation_job_schedule",
+ "azurerm_automation_module",
+ "azurerm_automation_runbook",
+ "azurerm_automation_schedule",
+ "azurerm_automation_variable_bool",
+ "azurerm_automation_variable_datetime",
+ "azurerm_automation_variable_int",
+ "azurerm_automation_variable_string",
+ "azurerm_resource_group",
+ "azurerm_batch_account",
+ "azurerm_batch_application",
+ "azurerm_batch_certificate",
+ "azurerm_batch_pool",
+ "azurerm_blueprint_assignment",
+ "azurerm_bot_channel_directline",
+ "azurerm_bot_channel_email",
+ "azurerm_bot_channel_ms_teams",
+ "azurerm_bot_channel_slack",
+ "azurerm_bot_channels_registration",
+ "azurerm_bot_connection",
+ "azurerm_bot_web_app",
+ "azurerm_cdn_endpoint",
+ "azurerm_cdn_profile",
+ "azurerm_cognitive_account",
+ "azurerm_availability_set",
+ "azurerm_dedicated_host",
+ "azurerm_dedicated_host_group",
+ "azurerm_disk_encryption_set",
+ "azurerm_image",
+ "azurerm_linux_virtual_machine",
+ "azurerm_linux_virtual_machine_scale_set",
+ "azurerm_managed_disk",
+ "azurerm_marketplace_agreement",
+ "azurerm_orchestrated_virtual_machine_scale_set",
+ "azurerm_proximity_placement_group",
+ "azurerm_shared_image",
+ "azurerm_shared_image_gallery",
+ "azurerm_shared_image_version",
+ "azurerm_snapshot",
+ "azurerm_virtual_machine",
+ "azurerm_virtual_machine_data_disk_attachment",
+ "azurerm_virtual_machine_extension",
+ "azurerm_virtual_machine_scale_set",
+ "azurerm_virtual_machine_scale_set_extension",
+ "azurerm_windows_virtual_machine",
+ "azurerm_windows_virtual_machine_scale_set",
+ "azurerm_container_group",
+ "azurerm_container_registry",
+ "azurerm_container_registry_webhook",
+ "azurerm_kubernetes_cluster",
+ "azurerm_kubernetes_cluster_node_pool",
+ "azurerm_cosmosdb_account",
+ "azurerm_cosmosdb_cassandra_keyspace",
+ "azurerm_cosmosdb_gremlin_database",
+ "azurerm_cosmosdb_gremlin_graph",
+ "azurerm_cosmosdb_mongo_collection",
+ "azurerm_cosmosdb_mongo_database",
+ "azurerm_cosmosdb_sql_container",
+ "azurerm_cosmosdb_sql_database",
+ "azurerm_cosmosdb_table",
+ "azurerm_cost_management_export_resource_group",
+ "azurerm_custom_provider",
+ "azurerm_dns_a_record",
+ "azurerm_dns_aaaa_record",
+ "azurerm_dns_caa_record",
+ "azurerm_dns_cname_record",
+ "azurerm_dns_mx_record",
+ "azurerm_dns_ns_record",
+ "azurerm_dns_ptr_record",
+ "azurerm_dns_srv_record",
+ "azurerm_dns_txt_record",
+ "azurerm_dns_zone",
+ "azurerm_kusto_attached_database_configuration",
+ "azurerm_kusto_cluster",
+ "azurerm_kusto_cluster_customer_managed_key",
+ "azurerm_kusto_cluster_principal_assignment",
+ "azurerm_kusto_database",
+ "azurerm_kusto_database_principal",
+ "azurerm_kusto_database_principal_assignment",
+ "azurerm_kusto_eventhub_data_connection",
+ "azurerm_data_factory",
+ "azurerm_data_factory_dataset_azure_blob",
+ "azurerm_data_factory_dataset_cosmosdb_sqlapi",
+ "azurerm_data_factory_dataset_delimited_text",
+ "azurerm_data_factory_dataset_http",
+ "azurerm_data_factory_dataset_json",
+ "azurerm_data_factory_dataset_mysql",
+ "azurerm_data_factory_dataset_postgresql",
+ "azurerm_data_factory_dataset_sql_server_table",
+ "azurerm_data_factory_integration_runtime_managed",
+ "azurerm_data_factory_integration_runtime_self_hosted",
+ "azurerm_data_factory_linked_service_azure_blob_storage",
+ "azurerm_data_factory_linked_service_azure_file_storage",
+ "azurerm_data_factory_linked_service_azure_function",
+ "azurerm_data_factory_linked_service_cosmosdb",
+ "azurerm_data_factory_linked_service_data_lake_storage_gen2",
+ "azurerm_data_factory_linked_service_key_vault",
+ "azurerm_data_factory_linked_service_mysql",
+ "azurerm_data_factory_linked_service_postgresql",
+ "azurerm_data_factory_linked_service_sftp",
+ "azurerm_data_factory_linked_service_sql_server",
+ "azurerm_data_factory_linked_service_web",
+ "azurerm_data_factory_pipeline",
+ "azurerm_data_factory_trigger_schedule",
+ "azurerm_data_lake_analytics_account",
+ "azurerm_data_lake_analytics_firewall_rule",
+ "azurerm_data_lake_store",
+ "azurerm_data_lake_store_file",
+ "azurerm_data_lake_store_firewall_rule",
+ "azurerm_data_share",
+ "azurerm_data_share_account",
+ "azurerm_data_share_dataset_blob_storage",
+ "azurerm_data_share_dataset_data_lake_gen1",
+ "azurerm_mariadb_configuration",
+ "azurerm_mariadb_database",
+ "azurerm_mariadb_firewall_rule",
+ "azurerm_mariadb_server",
+ "azurerm_mariadb_virtual_network_rule",
+ "azurerm_mssql_database",
+ "azurerm_mssql_database_vulnerability_assessment_rule_baseline",
+ "azurerm_mssql_elasticpool",
+ "azurerm_mssql_server",
+ "azurerm_mssql_server_security_alert_policy",
+ "azurerm_mssql_server_vulnerability_assessment",
+ "azurerm_mssql_virtual_machine",
+ "azurerm_mysql_active_directory_administrator",
+ "azurerm_mysql_configuration",
+ "azurerm_mysql_database",
+ "azurerm_mysql_firewall_rule",
+ "azurerm_mysql_server",
+ "azurerm_mysql_virtual_network_rule",
+ "azurerm_postgresql_active_directory_administrator",
+ "azurerm_postgresql_configuration",
+ "azurerm_postgresql_database",
+ "azurerm_postgresql_firewall_rule",
+ "azurerm_postgresql_server",
+ "azurerm_postgresql_virtual_network_rule",
+ "azurerm_sql_active_directory_administrator",
+ "azurerm_sql_database",
+ "azurerm_sql_elasticpool",
+ "azurerm_sql_failover_group",
+ "azurerm_sql_firewall_rule",
+ "azurerm_sql_server",
+ "azurerm_sql_virtual_network_rule",
+ "azurerm_database_migration_project",
+ "azurerm_database_migration_service",
+ "azurerm_databricks_workspace",
+ "azurerm_dev_test_global_vm_shutdown_schedule",
+ "azurerm_dev_test_lab",
+ "azurerm_dev_test_linux_virtual_machine",
+ "azurerm_dev_test_policy",
+ "azurerm_dev_test_schedule",
+ "azurerm_dev_test_virtual_network",
+ "azurerm_dev_test_windows_virtual_machine",
+ "azurerm_devspace_controller",
+ "azurerm_hdinsight_hadoop_cluster",
+ "azurerm_hdinsight_hbase_cluster",
+ "azurerm_hdinsight_interactive_query_cluster",
+ "azurerm_hdinsight_kafka_cluster",
+ "azurerm_hdinsight_ml_services_cluster",
+ "azurerm_hdinsight_rserver_cluster",
+ "azurerm_hdinsight_spark_cluster",
+ "azurerm_hdinsight_storm_cluster",
+ "azurerm_dedicated_hardware_security_module",
+ "azurerm_healthcare_service",
+ "azurerm_iotcentral_application",
+ "azurerm_iothub",
+ "azurerm_iothub_consumer_group",
+ "azurerm_iothub_dps",
+ "azurerm_iothub_dps_certificate",
+ "azurerm_iothub_dps_shared_access_policy",
+ "azurerm_iothub_shared_access_policy",
+ "azurerm_key_vault",
+ "azurerm_key_vault_access_policy",
+ "azurerm_key_vault_certificate",
+ "azurerm_key_vault_certificate_issuer",
+ "azurerm_key_vault_key",
+ "azurerm_key_vault_secret",
+ "azurerm_lb",
+ "azurerm_lb_backend_address_pool",
+ "azurerm_lb_nat_pool",
+ "azurerm_lb_nat_rule",
+ "azurerm_lb_outbound_rule",
+ "azurerm_lb_probe",
+ "azurerm_lb_rule",
+ "azurerm_log_analytics_datasource_windows_event",
+ "azurerm_log_analytics_datasource_windows_performance_counter",
+ "azurerm_log_analytics_linked_service",
+ "azurerm_log_analytics_solution",
+ "azurerm_log_analytics_workspace",
+ "azurerm_logic_app_action_custom",
+ "azurerm_logic_app_action_http",
+ "azurerm_logic_app_integration_account",
+ "azurerm_logic_app_trigger_custom",
+ "azurerm_logic_app_trigger_http_request",
+ "azurerm_logic_app_trigger_recurrence",
+ "azurerm_logic_app_workflow",
+ "azurerm_machine_learning_workspace",
+ "azurerm_maintenance_assignment_dedicated_host",
+ "azurerm_maintenance_assignment_virtual_machine",
+ "azurerm_maintenance_configuration",
+ "azurerm_managed_application",
+ "azurerm_managed_application_definition",
+ "azurerm_management_group",
+ "azurerm_management_lock",
+ "azurerm_maps_account",
+ "azurerm_media_services_account",
+ "azurerm_eventgrid_domain",
+ "azurerm_eventgrid_domain_topic",
+ "azurerm_eventgrid_event_subscription",
+ "azurerm_eventgrid_topic",
+ "azurerm_eventhub",
+ "azurerm_eventhub_authorization_rule",
+ "azurerm_eventhub_cluster",
+ "azurerm_eventhub_consumer_group",
+ "azurerm_eventhub_namespace",
+ "azurerm_eventhub_namespace_authorization_rule",
+ "azurerm_eventhub_namespace_disaster_recovery_config",
+ "azurerm_iothub_endpoint_eventhub",
+ "azurerm_iothub_endpoint_servicebus_queue",
+ "azurerm_iothub_endpoint_servicebus_topic",
+ "azurerm_iothub_endpoint_storage_container",
+ "azurerm_iothub_fallback_route",
+ "azurerm_iothub_route",
+ "azurerm_notification_hub",
+ "azurerm_notification_hub_authorization_rule",
+ "azurerm_notification_hub_namespace",
+ "azurerm_relay_hybrid_connection",
+ "azurerm_relay_namespace",
+ "azurerm_servicebus_namespace",
+ "azurerm_servicebus_namespace_authorization_rule",
+ "azurerm_servicebus_namespace_network_rule_set",
+ "azurerm_servicebus_queue",
+ "azurerm_servicebus_queue_authorization_rule",
+ "azurerm_servicebus_subscription",
+ "azurerm_servicebus_subscription_rule",
+ "azurerm_servicebus_topic",
+ "azurerm_servicebus_topic_authorization_rule",
+ "azurerm_signalr_service",
+ "azurerm_spatial_anchors_account",
+ "azurerm_monitor_action_group",
+ "azurerm_monitor_action_rule_action_group",
+ "azurerm_monitor_action_rule_suppression",
+ "azurerm_monitor_activity_log_alert",
+ "azurerm_monitor_autoscale_setting",
+ "azurerm_monitor_diagnostic_setting",
+ "azurerm_monitor_log_profile",
+ "azurerm_monitor_metric_alert",
+ "azurerm_monitor_scheduled_query_rules_alert",
+ "azurerm_monitor_scheduled_query_rules_log",
+ "azurerm_netapp_account",
+ "azurerm_netapp_pool",
+ "azurerm_netapp_snapshot",
+ "azurerm_netapp_volume",
+ "azurerm_application_gateway",
+ "azurerm_application_security_group",
+ "azurerm_bastion_host",
+ "azurerm_express_route_circuit",
+ "azurerm_express_route_circuit_authorization",
+ "azurerm_express_route_circuit_peering",
+ "azurerm_express_route_gateway",
+ "azurerm_firewall",
+ "azurerm_firewall_application_rule_collection",
+ "azurerm_firewall_nat_rule_collection",
+ "azurerm_firewall_network_rule_collection",
+ "azurerm_frontdoor",
+ "azurerm_frontdoor_custom_https_configuration",
+ "azurerm_frontdoor_firewall_policy",
+ "azurerm_local_network_gateway",
+ "azurerm_nat_gateway",
+ "azurerm_nat_gateway_public_ip_association",
+ "azurerm_network_ddos_protection_plan",
+ "azurerm_network_interface",
+ "azurerm_network_interface_application_gateway_backend_address_pool_association",
+ "azurerm_network_interface_application_security_group_association",
+ "azurerm_network_interface_backend_address_pool_association",
+ "azurerm_network_interface_nat_rule_association",
+ "azurerm_network_interface_security_group_association",
+ "azurerm_network_packet_capture",
+ "azurerm_network_profile",
+ "azurerm_network_security_group",
+ "azurerm_network_security_rule",
+ "azurerm_network_watcher",
+ "azurerm_network_watcher_flow_log",
+ "azurerm_packet_capture",
+ "azurerm_point_to_site_vpn_gateway",
+ "azurerm_private_endpoint",
+ "azurerm_private_link_service",
+ "azurerm_public_ip",
+ "azurerm_public_ip_prefix",
+ "azurerm_route",
+ "azurerm_route_filter",
+ "azurerm_route_table",
+ "azurerm_subnet",
+ "azurerm_subnet_nat_gateway_association",
+ "azurerm_subnet_network_security_group_association",
+ "azurerm_subnet_route_table_association",
+ "azurerm_traffic_manager_endpoint",
+ "azurerm_traffic_manager_profile",
+ "azurerm_virtual_hub",
+ "azurerm_virtual_hub_connection",
+ "azurerm_virtual_network",
+ "azurerm_virtual_network_gateway",
+ "azurerm_virtual_network_gateway_connection",
+ "azurerm_virtual_network_peering",
+ "azurerm_virtual_wan",
+ "azurerm_vpn_gateway",
+ "azurerm_vpn_server_configuration",
+ "azurerm_web_application_firewall_policy",
+ "azurerm_policy_assignment",
+ "azurerm_policy_definition",
+ "azurerm_policy_remediation",
+ "azurerm_policy_set_definition",
+ "azurerm_dashboard",
+ "azurerm_powerbi_embedded",
+ "azurerm_private_dns_a_record",
+ "azurerm_private_dns_aaaa_record",
+ "azurerm_private_dns_cname_record",
+ "azurerm_private_dns_mx_record",
+ "azurerm_private_dns_ptr_record",
+ "azurerm_private_dns_srv_record",
+ "azurerm_private_dns_txt_record",
+ "azurerm_private_dns_zone",
+ "azurerm_private_dns_zone_virtual_network_link",
+ "azurerm_backup_container_storage_account",
+ "azurerm_backup_policy_file_share",
+ "azurerm_backup_policy_vm",
+ "azurerm_backup_protected_file_share",
+ "azurerm_backup_protected_vm",
+ "azurerm_recovery_services_vault",
+ "azurerm_site_recovery_fabric",
+ "azurerm_site_recovery_network_mapping",
+ "azurerm_site_recovery_protection_container",
+ "azurerm_site_recovery_protection_container_mapping",
+ "azurerm_site_recovery_replicated_vm",
+ "azurerm_site_recovery_replication_policy",
+ "azurerm_redis_cache",
+ "azurerm_redis_firewall_rule",
+ "azurerm_search_service",
+ "azurerm_advanced_threat_protection",
+ "azurerm_security_center_contact",
+ "azurerm_security_center_subscription_pricing",
+ "azurerm_security_center_workspace",
+ "azurerm_sentinel_alert_rule_ms_security_incident",
+ "azurerm_sentinel_alert_rule_scheduled",
+ "azurerm_service_fabric_cluster",
+ "azurerm_spring_cloud_app",
+ "azurerm_spring_cloud_service",
+ "azurerm_hpc_cache",
+ "azurerm_hpc_cache_blob_target",
+ "azurerm_hpc_cache_nfs_target",
+ "azurerm_storage_account",
+ "azurerm_storage_account_customer_managed_key",
+ "azurerm_storage_account_network_rules",
+ "azurerm_storage_blob",
+ "azurerm_storage_container",
+ "azurerm_storage_data_lake_gen2_filesystem",
+ "azurerm_storage_management_policy",
+ "azurerm_storage_queue",
+ "azurerm_storage_share",
+ "azurerm_storage_share_directory",
+ "azurerm_storage_table",
+ "azurerm_storage_table_entity",
+ "azurerm_stream_analytics_function_javascript_udf",
+ "azurerm_stream_analytics_job",
+ "azurerm_stream_analytics_output_blob",
+ "azurerm_stream_analytics_output_eventhub",
+ "azurerm_stream_analytics_output_mssql",
+ "azurerm_stream_analytics_output_servicebus_queue",
+ "azurerm_stream_analytics_output_servicebus_topic",
+ "azurerm_stream_analytics_reference_input_blob",
+ "azurerm_stream_analytics_stream_input_blob",
+ "azurerm_stream_analytics_stream_input_eventhub",
+ "azurerm_stream_analytics_stream_input_iothub",
+ "azurerm_synapse_workspace",
+ "azurerm_template_deployment",
+ "azurerm_iot_time_series_insights_access_policy",
+ "azurerm_iot_time_series_insights_reference_data_set",
+ "azurerm_iot_time_series_insights_standard_environment"
+ ]
+}
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/__init__.py b/checkov/common/checks_infra/solvers/__init__.py
new file mode 100644
index 0000000000..6c3d551ae0
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/__init__.py
@@ -0,0 +1,4 @@
+from checkov.common.checks_infra.solvers.attribute_solvers import *
+from checkov.common.checks_infra.solvers.complex_solvers import *
+from checkov.common.checks_infra.solvers.connections_solvers import *
+from checkov.common.checks_infra.solvers.filter_solvers import *
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/__init__.py b/checkov/common/checks_infra/solvers/attribute_solvers/__init__.py
new file mode 100644
index 0000000000..70b3f2e57a
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/__init__.py
@@ -0,0 +1,12 @@
+from checkov.common.checks_infra.solvers.attribute_solvers.any_attribute_solver import AnyResourceSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.contains_attribute_solver import ContainsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.not_contains_attribute_solver import NotContainsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.ending_with_attribute_solver import EndingWithAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.equals_attribute_solver import EqualsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.exists_attribute_solver import ExistsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.not_ending_with_attribute_solver import NotEndingWithAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.not_equals_attribute_solver import NotEqualsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.not_exists_attribute_solver import NotExistsAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.not_starting_with_attribute_solver import NotStartingWithAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.starting_with_attribute_solver import StartingWithAttributeSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.within_attribute_solver import WithinAttributeSolver
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/any_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/any_attribute_solver.py
new file mode 100644
index 0000000000..6b15cdcfc3
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/any_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class AnyResourceSolver(BaseAttributeSolver):
+ operator = Operators.EXISTS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return vertex is not None
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/base_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/base_attribute_solver.py
new file mode 100644
index 0000000000..55c1ac4a89
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/base_attribute_solver.py
@@ -0,0 +1,83 @@
+import concurrent.futures
+import re
+from typing import List, Tuple, Dict, Any, Optional, Pattern
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+
+from concurrent.futures import ThreadPoolExecutor
+
+WILDCARD_PATTERN = re.compile(r"(\S+[.][*][.]*)+")
+
+
+class BaseAttributeSolver(BaseSolver):
+ operator = ""
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(SolverType.ATTRIBUTE)
+ self.resource_types = resource_types
+ self.attribute = attribute
+ self.value = value
+
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ executer = ThreadPoolExecutor()
+ jobs = []
+ passed_vertices = []
+ failed_vertices = []
+ for _, data in graph_connector.nodes(data=True):
+ jobs.append(executer.submit(self._process_node, data, passed_vertices, failed_vertices))
+
+ concurrent.futures.wait(jobs)
+ return passed_vertices, failed_vertices
+
+ def get_operation(self, vertex: Dict[str, Any]) -> bool:
+ if self.attribute and re.match(WILDCARD_PATTERN, self.attribute):
+ attribute_patterns = self.get_attribute_patterns(self.attribute)
+ attribute_matches = [
+ attr
+ for attr in vertex
+ if any(re.match(attribute_pattern, attr) for attribute_pattern in attribute_patterns)
+ ]
+ if attribute_matches:
+ return self.resource_type_pred(vertex, self.resource_types) and any(
+ self._get_operation(vertex=vertex, attribute=attr) for attr in attribute_matches
+ )
+ return self.resource_type_pred(vertex, self.resource_types) and self._get_operation(
+ vertex=vertex, attribute=self.attribute
+ )
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ raise NotImplementedError
+
+ def _process_node(self, data, passed_vartices, failed_vertices):
+ if not self.resource_type_pred(data, self.resource_types):
+ return
+ if self.get_operation(vertex=data):
+ passed_vartices.append(data)
+ else:
+ failed_vertices.append(data)
+
+ @staticmethod
+ def get_attribute_patterns(attribute: str) -> Tuple[Pattern[str], Pattern[str]]:
+ index_pattern = r"[\d+]"
+ split_by_dots = attribute.split(".")
+
+ pattern_parts = []
+ pattern_parts_without_index = []
+ for attr_part in split_by_dots:
+ if attr_part == "*":
+ pattern_parts.append(index_pattern)
+ else:
+ attr_part_pattern = f"({attr_part})"
+ pattern_parts.append(attr_part_pattern)
+ pattern_parts_without_index.append(attr_part_pattern)
+
+ pattern = "[.]".join(pattern_parts)
+ pattern_with_index = re.compile(pattern)
+
+ pattern = "[.]".join(pattern_parts_without_index)
+ pattern_without_index = re.compile(pattern)
+
+ return pattern_with_index, pattern_without_index
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/contains_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/contains_attribute_solver.py
new file mode 100644
index 0000000000..1710a80092
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/contains_attribute_solver.py
@@ -0,0 +1,26 @@
+import json
+import logging
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+logger = logging.getLogger(__name__)
+
+
+class ContainsAttributeSolver(BaseAttributeSolver):
+ operator = Operators.CONTAINS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ att = vertex.get(attribute, "{}")
+ if isinstance(att, str):
+ try:
+ att = json.loads(att.replace("'", '"'))
+ except ValueError:
+ pass
+ if isinstance(att, dict):
+ return self.value in att or any(self.value in val for val in att.values() if type(val) in [str, list, set, dict])
+ return self.value in att
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/ending_with_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/ending_with_attribute_solver.py
new file mode 100644
index 0000000000..5ab3cf0ba5
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/ending_with_attribute_solver.py
@@ -0,0 +1,15 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class EndingWithAttributeSolver(BaseAttributeSolver):
+ operator = Operators.CONTAINS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ attr = vertex.get(attribute)
+ return isinstance(attr, str) and attr.endswith(self.value)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/equals_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/equals_attribute_solver.py
new file mode 100644
index 0000000000..1ec1eb4342
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/equals_attribute_solver.py
@@ -0,0 +1,15 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class EqualsAttributeSolver(BaseAttributeSolver):
+ operator = Operators.EQUALS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types,
+ attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return str(vertex.get(attribute)) == str(self.value)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/exists_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/exists_attribute_solver.py
new file mode 100644
index 0000000000..21596d99a7
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/exists_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class ExistsAttributeSolver(BaseAttributeSolver):
+ operator = Operators.EXISTS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return vertex.get(attribute) is not None
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/not_contains_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/not_contains_attribute_solver.py
new file mode 100644
index 0000000000..f42d3903f2
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/not_contains_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from .contains_attribute_solver import ContainsAttributeSolver
+
+
+class NotContainsAttributeSolver(ContainsAttributeSolver):
+ operator = Operators.NOT_CONTAINS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return not super()._get_operation(vertex, attribute)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/not_ending_with_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/not_ending_with_attribute_solver.py
new file mode 100644
index 0000000000..82c1c7700e
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/not_ending_with_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from .ending_with_attribute_solver import EndingWithAttributeSolver
+
+
+class NotEndingWithAttributeSolver(EndingWithAttributeSolver):
+ operator = Operators.NOT_ENDING_WITH
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return not super()._get_operation(vertex, attribute)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/not_equals_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/not_equals_attribute_solver.py
new file mode 100644
index 0000000000..791d1793cc
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/not_equals_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from .equals_attribute_solver import EqualsAttributeSolver
+
+
+class NotEqualsAttributeSolver(EqualsAttributeSolver):
+ operator = Operators.NOT_EQUALS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return not super()._get_operation(vertex, attribute)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/not_exists_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/not_exists_attribute_solver.py
new file mode 100644
index 0000000000..e757b2c740
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/not_exists_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from .exists_attribute_solver import ExistsAttributeSolver
+
+
+class NotExistsAttributeSolver(ExistsAttributeSolver):
+ operator = Operators.NOT_EXISTS
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return not super()._get_operation(vertex, attribute)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/not_starting_with_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/not_starting_with_attribute_solver.py
new file mode 100644
index 0000000000..f8abe8b060
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/not_starting_with_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from .starting_with_attribute_solver import StartingWithAttributeSolver
+
+
+class NotStartingWithAttributeSolver(StartingWithAttributeSolver):
+ operator = Operators.NOT_STARTING_WITH
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return not super()._get_operation(vertex, attribute)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/starting_with_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/starting_with_attribute_solver.py
new file mode 100644
index 0000000000..da8c499c7b
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/starting_with_attribute_solver.py
@@ -0,0 +1,15 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class StartingWithAttributeSolver(BaseAttributeSolver):
+ operator = Operators.STARTING_WITH
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ attr = vertex.get(attribute)
+ return isinstance(attr, str) and attr.startswith(self.value)
diff --git a/checkov/common/checks_infra/solvers/attribute_solvers/within_attribute_solver.py b/checkov/common/checks_infra/solvers/attribute_solvers/within_attribute_solver.py
new file mode 100644
index 0000000000..e0b01d712b
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/attribute_solvers/within_attribute_solver.py
@@ -0,0 +1,14 @@
+from typing import List, Optional, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+
+
+class WithinAttributeSolver(BaseAttributeSolver):
+ operator = Operators.WITHIN
+
+ def __init__(self, resource_types: List[str], attribute: Optional[str], value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
+ return vertex.get(attribute) in self.value
diff --git a/checkov/common/checks_infra/solvers/complex_solvers/__init__.py b/checkov/common/checks_infra/solvers/complex_solvers/__init__.py
new file mode 100644
index 0000000000..98967efe87
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/complex_solvers/__init__.py
@@ -0,0 +1,2 @@
+from checkov.common.checks_infra.solvers.complex_solvers.or_solver import OrSolver
+from checkov.common.checks_infra.solvers.complex_solvers.and_solver import AndSolver
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/complex_solvers/and_solver.py b/checkov/common/checks_infra/solvers/complex_solvers/and_solver.py
new file mode 100644
index 0000000000..08f764813d
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/complex_solvers/and_solver.py
@@ -0,0 +1,22 @@
+from typing import List, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers.complex_solvers.base_complex_solver import BaseComplexSolver
+from functools import reduce
+from operator import and_
+
+
+class AndSolver(BaseComplexSolver):
+ operator = Operators.AND
+
+ def __init__(self, solvers: List[BaseSolver], resource_types: List[str]) -> None:
+ super().__init__(solvers, resource_types)
+
+ def _get_operation(self, *args: Any) -> Any:
+ return reduce(and_, args)
+
+ def get_operation(self, vertex: Dict[str, Any]) -> bool:
+ if any(not solver.get_operation(vertex) for solver in self.solvers):
+ return False
+ return True
diff --git a/checkov/common/checks_infra/solvers/complex_solvers/base_complex_solver.py b/checkov/common/checks_infra/solvers/complex_solvers/base_complex_solver.py
new file mode 100644
index 0000000000..050224328f
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/complex_solvers/base_complex_solver.py
@@ -0,0 +1,31 @@
+from typing import List, Any, Tuple, Dict
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+
+
+class BaseComplexSolver(BaseSolver):
+ operator = ""
+
+ def __init__(self, solvers: List[BaseSolver], resource_types: List[str]) -> None:
+ if solvers is None:
+ solvers = []
+ self.solvers = solvers
+ self.resource_types = resource_types
+ super().__init__(SolverType.COMPLEX)
+
+ def _get_operation(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
+ def _get_negative_op(self, *args: Any) -> Any:
+ return not self._get_operation(args)
+
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ all_vertices_resource_types = [
+ data for _, data in graph_connector.nodes(data=True) if self.resource_type_pred(data, self.resource_types)
+ ]
+ passed_vertices = [data for data in all_vertices_resource_types if self.get_operation(data)]
+ failed_vertices = [resource for resource in all_vertices_resource_types if resource not in passed_vertices]
+ return passed_vertices, failed_vertices
diff --git a/checkov/common/checks_infra/solvers/complex_solvers/or_solver.py b/checkov/common/checks_infra/solvers/complex_solvers/or_solver.py
new file mode 100644
index 0000000000..e1a03e0706
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/complex_solvers/or_solver.py
@@ -0,0 +1,22 @@
+from typing import List, Any, Dict
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers.complex_solvers.base_complex_solver import BaseComplexSolver
+from functools import reduce
+from operator import or_
+
+
+class OrSolver(BaseComplexSolver):
+ operator = Operators.OR
+
+ def __init__(self, solvers: List[BaseSolver], resource_types: List[str]) -> None:
+ super().__init__(solvers, resource_types)
+
+ def _get_operation(self, *args: Any) -> Any:
+ return reduce(or_, args)
+
+ def get_operation(self, vertex: Dict[str, Any]) -> bool:
+ if any(solver.get_operation(vertex) for solver in self.solvers):
+ return True
+ return False
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/__init__.py b/checkov/common/checks_infra/solvers/connections_solvers/__init__.py
new file mode 100644
index 0000000000..b555bba82a
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/__init__.py
@@ -0,0 +1,5 @@
+from checkov.common.checks_infra.solvers.connections_solvers.and_connection_solver import AndConnectionSolver
+from checkov.common.checks_infra.solvers.connections_solvers.complex_connection_solver import ComplexConnectionSolver
+from checkov.common.checks_infra.solvers.connections_solvers.connection_exists_solver import ConnectionExistsSolver
+from checkov.common.checks_infra.solvers.connections_solvers.connection_not_exists_solver import ConnectionNotExistsSolver
+from checkov.common.checks_infra.solvers.connections_solvers.or_connection_solver import OrConnectionSolver
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/and_connection_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/and_connection_solver.py
new file mode 100644
index 0000000000..73991fc512
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/and_connection_solver.py
@@ -0,0 +1,34 @@
+from typing import Optional, List, Tuple, Dict, Any
+
+from networkx.classes.digraph import DiGraph
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers.connections_solvers.complex_connection_solver import ComplexConnectionSolver
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+
+
+class AndConnectionSolver(ComplexConnectionSolver):
+ operator = Operators.AND
+
+ def __init__(self, solvers: Optional[List[BaseSolver]], operator: str) -> None:
+ super().__init__(solvers, operator)
+
+ def get_operation(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ passed, failed = self.run_attribute_solvers(graph_connector)
+ failed_ids = [f[CustomAttributes.ID] for f in failed]
+ passed = [p for p in passed if p[CustomAttributes.ID] not in failed_ids]
+
+ for connection_solver in self.get_sorted_connection_solvers():
+ connection_solver.set_vertices(graph_connector, failed)
+ passed_solver, failed_solver = connection_solver.get_operation(graph_connector)
+ passed.extend(passed_solver)
+ failed.extend(failed_solver)
+ failed_ids.extend([f[CustomAttributes.ID] for f in failed_solver])
+
+ passed = [p for p in passed if p[CustomAttributes.ID] not in failed_ids]
+
+ return self.filter_results(passed, failed)
+
+ def _get_operation(self, *args: Any, **kwargs: Any) -> None:
+ pass
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/base_connection_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/base_connection_solver.py
new file mode 100644
index 0000000000..d9a74f2554
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/base_connection_solver.py
@@ -0,0 +1,54 @@
+import itertools
+from typing import Any, List, Dict, Optional, Tuple
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+
+
+class BaseConnectionSolver(BaseSolver):
+ def __init__(
+ self,
+ resource_types: List[str],
+ connected_resources_types: List[str],
+ vertices_under_resource_types: Optional[List[Dict[str, Any]]] = None,
+ vertices_under_connected_resources_types: Optional[List[Dict[str, Any]]] = None,
+ ) -> None:
+ super().__init__(SolverType.CONNECTION)
+ self.resource_types = resource_types
+ self.connected_resources_types = connected_resources_types
+ self.vertices_under_resource_types = vertices_under_resource_types or []
+ self.vertices_under_connected_resources_types = vertices_under_connected_resources_types or []
+ self.excluded_vertices: List[Dict[str, Any]] = []
+
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ self.set_vertices(graph_connector, [])
+ return self.get_operation(graph_connector)
+
+ def is_associated_edge(self, origin_type: str, destination_type: str) -> bool:
+ return (origin_type in self.resource_types and destination_type in self.connected_resources_types) or (
+ origin_type in self.connected_resources_types and destination_type in self.resource_types
+ )
+
+ def is_associated_vertex(self, vertex_type: str) -> bool:
+ return vertex_type in itertools.chain(self.resource_types, self.connected_resources_types)
+
+ def set_vertices(self, graph_connector: DiGraph, exclude_vertices: List[Dict[str, Any]]) -> None:
+ self.vertices_under_resource_types = [
+ v for _, v in graph_connector.nodes(data=True) if self.resource_type_pred(v, self.resource_types)
+ ]
+ self.vertices_under_connected_resources_types = [
+ v for _, v in graph_connector.nodes(data=True) if self.resource_type_pred(v, self.connected_resources_types)
+ ]
+ self.excluded_vertices = [
+ v
+ for v in self.vertices_under_resource_types + self.vertices_under_connected_resources_types
+ if v in exclude_vertices
+ ]
+
+ def get_operation(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ raise NotImplementedError
+
+ def _get_operation(self, *args: Any, **kwargs: Any):
+ raise NotImplementedError
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/complex_connection_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/complex_connection_solver.py
new file mode 100644
index 0000000000..3b41410029
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/complex_connection_solver.py
@@ -0,0 +1,83 @@
+import itertools
+from typing import List, Optional, Dict, Any, Tuple
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
+from checkov.common.checks_infra.solvers.complex_solvers.base_complex_solver import BaseComplexSolver
+from checkov.common.checks_infra.solvers.connections_solvers.base_connection_solver import BaseConnectionSolver
+from checkov.common.checks_infra.solvers.filter_solvers.base_filter_solver import BaseFilterSolver
+
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+
+
+class ComplexConnectionSolver(BaseConnectionSolver):
+ def __init__(self, solvers: Optional[List[BaseSolver]], operator: str) -> None:
+ self.solver_type = SolverType.COMPLEX_CONNECTION
+ self.solvers = solvers if solvers else []
+ self.operator = operator
+
+ resource_types = set()
+ connected_resources_types = set()
+ for sub_solver in self.solvers:
+ if isinstance(sub_solver, BaseConnectionSolver):
+ resource_types.update(sub_solver.resource_types)
+ connected_resources_types.update(sub_solver.connected_resources_types)
+ elif isinstance(sub_solver, BaseAttributeSolver):
+ resource_types.update(sub_solver.resource_types)
+
+ super().__init__(list(resource_types), list(connected_resources_types))
+
+ @staticmethod
+ def filter_duplicates(checks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ return list({check[CustomAttributes.ID]: check for check in checks}.values())
+
+ def filter_results(
+ self, passed: List[Dict[str, Any]], failed: List[Dict[str, Any]]
+ ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ filter_solvers = [sub_solver for sub_solver in self.solvers if isinstance(sub_solver, BaseFilterSolver)]
+ for sub_solver in filter_solvers:
+ filter_pred = sub_solver._get_operation()
+ passed = list(filter(filter_pred, passed))
+ failed = list(filter(filter_pred, failed))
+ passed = self.filter_duplicates(passed)
+ failed = self.filter_duplicates(failed)
+ return passed, failed
+
+ def get_sorted_connection_solvers(self) -> List[BaseConnectionSolver]:
+ connection_solvers = [sub_solver for sub_solver in self.solvers if isinstance(sub_solver, BaseConnectionSolver)]
+ filter_solvers = [sub_solver for sub_solver in self.solvers if isinstance(sub_solver, BaseFilterSolver)]
+
+ resource_types_to_filter = []
+ for filter_solver in filter_solvers:
+ if filter_solver.attribute == "resource_type":
+ resource_types_to_filter.extend(filter_solver.value)
+
+ sorted_connection_solvers = []
+ connection_solvers_with_filtered_resource_types = []
+ for connection_solver in connection_solvers:
+ if any(
+ r in resource_types_to_filter
+ for r in itertools.chain(connection_solver.resource_types, connection_solver.connected_resources_types)
+ ):
+ connection_solvers_with_filtered_resource_types.append(connection_solver)
+ else:
+ sorted_connection_solvers.append(connection_solver)
+
+ sorted_connection_solvers.extend(connection_solvers_with_filtered_resource_types)
+ return sorted_connection_solvers
+
+ def run_attribute_solvers(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ attribute_solvers = [
+ sub_solver
+ for sub_solver in self.solvers
+ if isinstance(sub_solver, (BaseAttributeSolver, BaseComplexSolver))
+ ]
+ passed_attributes, failed_attributes = [], []
+ for attribute_solver in attribute_solvers:
+ passed_solver, failed_solver = attribute_solver.run(graph_connector)
+ passed_attributes.extend(passed_solver)
+ failed_attributes.extend(failed_solver)
+ return passed_attributes, failed_attributes
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/connection_exists_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/connection_exists_solver.py
new file mode 100644
index 0000000000..39d20606e2
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/connection_exists_solver.py
@@ -0,0 +1,71 @@
+import itertools
+from typing import List, Optional, Dict, Any, Tuple
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.connections_solvers.base_connection_solver import BaseConnectionSolver
+from networkx import edge_dfs, DiGraph
+from checkov.common.graph.graph_builder import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+
+
+class ConnectionExistsSolver(BaseConnectionSolver):
+ operator = Operators.EXISTS
+
+ def __init__(
+ self,
+ resource_types: List[str],
+ connected_resources_types: List[str],
+ vertices_under_resource_types: Optional[List[Dict[str, Any]]] = None,
+ vertices_under_connected_resources_types: Optional[List[Dict[str, Any]]] = None,
+ ) -> None:
+ super().__init__(
+ resource_types,
+ connected_resources_types,
+ vertices_under_resource_types,
+ vertices_under_connected_resources_types,
+ )
+
+ def get_operation(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ passed = []
+ failed = []
+ for u, v in edge_dfs(graph_connector):
+ origin_attributes = graph_connector.nodes(data=True)[u]
+ opposite_vertices = None
+ if origin_attributes in self.vertices_under_resource_types:
+ opposite_vertices = self.vertices_under_connected_resources_types
+ elif origin_attributes in self.vertices_under_connected_resources_types:
+ opposite_vertices = self.vertices_under_resource_types
+ if not opposite_vertices:
+ continue
+
+ destination_attributes = graph_connector.nodes(data=True)[v]
+ if destination_attributes in opposite_vertices:
+ if origin_attributes in self.excluded_vertices or destination_attributes in self.excluded_vertices:
+ failed.extend([origin_attributes, destination_attributes])
+ else:
+ passed.extend([origin_attributes, destination_attributes])
+ continue
+
+ destination_block_type = destination_attributes.get(CustomAttributes.BLOCK_TYPE)
+ if destination_block_type == BlockType.OUTPUT:
+ try:
+ output_edges = graph_connector.edges(v, data=True)
+ _, output_destination, _ = next(iter(output_edges))
+ output_destination = graph_connector.nodes(data=True)[output_destination]
+ output_destination_type = output_destination.get(CustomAttributes.RESOURCE_TYPE)
+ if self.is_associated_edge(
+ origin_attributes.get(CustomAttributes.RESOURCE_TYPE), output_destination_type
+ ):
+ passed.extend([origin_attributes, output_destination])
+ except StopIteration:
+ continue
+ failed.extend(
+ [
+ v
+ for v in itertools.chain(
+ self.vertices_under_resource_types, self.vertices_under_connected_resources_types
+ )
+ if v not in passed
+ ]
+ )
+ return passed, failed
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/connection_not_exists_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/connection_not_exists_solver.py
new file mode 100644
index 0000000000..3d2ad27ab2
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/connection_not_exists_solver.py
@@ -0,0 +1,28 @@
+from typing import List, Optional, Dict, Any, Tuple
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.connections_solvers.connection_exists_solver import ConnectionExistsSolver
+
+
+class ConnectionNotExistsSolver(ConnectionExistsSolver):
+ operator = Operators.NOT_EXISTS
+
+ def __init__(
+ self,
+ resource_types: List[str],
+ connected_resources_types: List[str],
+ vertices_under_resource_types: Optional[List[Dict[str, Any]]] = None,
+ vertices_under_connected_resources_types: Optional[List[Dict[str, Any]]] = None,
+ ) -> None:
+ super().__init__(
+ resource_types,
+ connected_resources_types,
+ vertices_under_resource_types,
+ vertices_under_connected_resources_types,
+ )
+
+ def get_operation(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ passed, failed = super().get_operation(graph_connector)
+ return failed, passed
diff --git a/checkov/common/checks_infra/solvers/connections_solvers/or_connection_solver.py b/checkov/common/checks_infra/solvers/connections_solvers/or_connection_solver.py
new file mode 100644
index 0000000000..e09be48adc
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/connections_solvers/or_connection_solver.py
@@ -0,0 +1,39 @@
+from typing import Optional, List, Tuple, Dict, Any
+
+from networkx.classes.digraph import DiGraph
+from checkov.common.graph.checks_infra.enums import SolverType, Operators
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+from checkov.common.checks_infra.solvers.connections_solvers.base_connection_solver import BaseConnectionSolver
+from checkov.common.checks_infra.solvers.connections_solvers.complex_connection_solver import ComplexConnectionSolver
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+
+
+class OrConnectionSolver(ComplexConnectionSolver):
+ operator = Operators.OR
+
+ def __init__(self, solvers: Optional[List[BaseSolver]], operator: str) -> None:
+ super().__init__(solvers, operator)
+
+ def get_operation(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ passed, failed = self.run_attribute_solvers(graph_connector)
+ failed = [f for f in failed if f[CustomAttributes.ID] not in [p[CustomAttributes.ID] for p in passed]]
+ connection_solvers = [sub_solver for sub_solver in self.solvers if isinstance(sub_solver, BaseConnectionSolver)]
+ passed_connections = []
+ failed_by_hash: Dict[str, Dict[str, Any]] = {}
+ for connection_solver in connection_solvers:
+ connection_solver.set_vertices(graph_connector, [])
+ passed_solver, failed_solvers = connection_solver.get_operation(graph_connector)
+ passed_connections.extend(passed_solver)
+ for f in failed_solvers:
+ if f[CustomAttributes.ID] not in [p[CustomAttributes.ID] for p in passed_connections]:
+ failed_by_hash.setdefault(f[CustomAttributes.HASH], {"v": f, "count": 0})
+ failed_by_hash[f[CustomAttributes.HASH]]["count"] += 1
+
+ for data in failed_by_hash.values():
+ if data["count"] == len(connection_solvers) or data["v"] not in passed_connections:
+ failed.append(data["v"])
+
+ passed.extend(passed_connections)
+ failed = [f for f in failed if f[CustomAttributes.ID] not in [p[CustomAttributes.ID] for p in passed]]
+
+ return self.filter_results(passed, failed)
diff --git a/checkov/common/checks_infra/solvers/filter_solvers/__init__.py b/checkov/common/checks_infra/solvers/filter_solvers/__init__.py
new file mode 100644
index 0000000000..78d6b10afb
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/filter_solvers/__init__.py
@@ -0,0 +1 @@
+from checkov.common.checks_infra.solvers.filter_solvers.within_filter_solver import WithinFilterSolver
\ No newline at end of file
diff --git a/checkov/common/checks_infra/solvers/filter_solvers/base_filter_solver.py b/checkov/common/checks_infra/solvers/filter_solvers/base_filter_solver.py
new file mode 100644
index 0000000000..8f5f779042
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/filter_solvers/base_filter_solver.py
@@ -0,0 +1,24 @@
+from typing import List, Any, Dict, Callable, Tuple
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+
+
+class BaseFilterSolver(BaseSolver):
+ def __init__(self, resource_types: List[str], attribute: str, value: Any) -> None:
+ super().__init__(SolverType.FILTER)
+ self.resource_types = resource_types
+ self.attribute = attribute
+ self.value = value
+ self.vertices: List[Dict[str, Any]] = []
+
+ def get_operation(self, *args: Any, **kwargs: Any) -> bool:
+ raise NotImplementedError()
+
+ def _get_operation(self, *args: Any, **kwargs: Any) -> Callable[..., bool]:
+ raise NotImplementedError()
+
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ raise NotImplementedError()
diff --git a/checkov/common/checks_infra/solvers/filter_solvers/within_filter_solver.py b/checkov/common/checks_infra/solvers/filter_solvers/within_filter_solver.py
new file mode 100644
index 0000000000..b72fdb15de
--- /dev/null
+++ b/checkov/common/checks_infra/solvers/filter_solvers/within_filter_solver.py
@@ -0,0 +1,20 @@
+from typing import List, Any, Callable
+
+from checkov.common.graph.checks_infra.enums import Operators
+from checkov.common.checks_infra.solvers.filter_solvers.base_filter_solver import BaseFilterSolver
+
+
+class WithinFilterSolver(BaseFilterSolver):
+ operator = Operators.WITHIN
+
+ def __init__(self, resource_types: List[str], attribute: str, value: Any) -> None:
+ super().__init__(resource_types=resource_types, attribute=attribute, value=value)
+
+ def get_operation(self, *args: Any, **kwargs: Any) -> bool:
+ return self._get_operation()(*args)
+
+ def _get_operation(self, *args: Any, **kwargs: Any) -> Callable[..., bool]:
+ def op(check):
+ val = check.get(self.attribute)
+ return val and (val in self.value)
+ return op
diff --git a/checkov/common/goget/github/get_git.py b/checkov/common/goget/github/get_git.py
index ad93d6d2fb..e1e647b45b 100644
--- a/checkov/common/goget/github/get_git.py
+++ b/checkov/common/goget/github/get_git.py
@@ -2,7 +2,7 @@
import re
import shutil
-TAG_PATTERN = re.compile(r'\?(ref=)(?Pv\d+.\d+.\d+)')
+TAG_PATTERN = re.compile(r'\?(ref=)(?P(.*))')
try:
from git import Repo
git_import_error = None
@@ -22,6 +22,8 @@ def __init__(self, url, create_clone_and_result_dirs=True):
search_tag = re.search(TAG_PATTERN, url)
if search_tag:
self.tag = search_tag.groupdict().get('tag')
+ #remove tag/ or tags/ from ref= to get actual branch name
+ self.tag = re.sub('tag.*/','', self.tag)
url = re.sub(TAG_PATTERN, '', url)
super().__init__(url)
@@ -31,22 +33,47 @@ def do_get(self):
raise ImportError("Unable to load git module (is the git executable available?)") \
from git_import_error
+ git_url, internal_dir = self._source_subdir()
+
clone_dir = self.temp_dir + "/clone/" if self.create_clone_and_res_dirs else self.temp_dir
- result_dir = self.temp_dir + "/result/"
+ self._clone(git_url, clone_dir)
- if ".git//" in self.url:
- git_url, internal_dir = self.url.split(".git//")
- self._clone(git_url + ".git", clone_dir, result_dir, internal_dir)
- else:
- self._clone(self.url, clone_dir, result_dir)
+ if internal_dir:
+ clone_dir = clone_dir + internal_dir
- return result_dir
+ if self.create_clone_and_res_dirs:
+ result_dir = self.temp_dir + "/result/"
+ shutil.copytree(clone_dir, result_dir)
+ return result_dir
- def _clone(self, git_url, clone_dir, result_dir, internal_dir=''):
+ return clone_dir
+
+ def _clone(self, git_url, clone_dir):
self.logger.debug("cloning {} to {}".format(self.url, clone_dir))
if self.tag:
Repo.clone_from(git_url, clone_dir, b=self.tag)
else:
Repo.clone_from(git_url, clone_dir)
- if self.create_clone_and_res_dirs:
- shutil.copytree(clone_dir + internal_dir, result_dir)
+
+ # Split source url into Git url and subdirectory path e.g. test.com/repo//repo/subpath becomes 'test.com/repo', '/repo/subpath')
+ # Also see reference implementation @ go-getter https://github.com/hashicorp/go-getter/blob/main/source.go
+ def _source_subdir(self):
+ stop = len(self.url)
+
+ query_index = self.url.find("?")
+ if query_index > -1:
+ stop = query_index
+
+ start = 0
+ scheme_index = self.url.find("://", start, stop)
+ if scheme_index > -1:
+ start = scheme_index + 3
+
+ subdir_index = self.url.find("//", start, stop)
+ if subdir_index == -1:
+ return (self.url, "")
+
+ internal_dir = self.url[subdir_index + 1:stop] # Note: Internal dir is expected to start with /
+ git_url = self.url[:subdir_index] + self.url[stop:]
+
+ return (git_url, internal_dir)
diff --git a/tests/terraform/runner/resources/valid_tf_only_skipped_checks/__init__.py b/checkov/common/graph/__init__.py
similarity index 100%
rename from tests/terraform/runner/resources/valid_tf_only_skipped_checks/__init__.py
rename to checkov/common/graph/__init__.py
diff --git a/checkov/common/graph/checks_infra/__init__.py b/checkov/common/graph/checks_infra/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/common/graph/checks_infra/base_check.py b/checkov/common/graph/checks_infra/base_check.py
new file mode 100644
index 0000000000..1212823ae6
--- /dev/null
+++ b/checkov/common/graph/checks_infra/base_check.py
@@ -0,0 +1,30 @@
+from typing import Optional, Tuple, List, Dict, Any
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+from checkov.common.graph.checks_infra.solvers.base_solver import BaseSolver
+
+
+class BaseGraphCheck:
+ def __init__(self) -> None:
+ self.id = ""
+ self.bc_id = None
+ self.name = ""
+ self.resource_types: List[str] = []
+ self.connected_resources_types: List[str] = []
+ self.operator = ""
+ self.attribute: Optional[str] = None
+ self.attribute_value: Optional[str] = None
+ self.sub_checks: List["BaseGraphCheck"] = []
+ self.type: Optional[SolverType] = None
+ self.solver: Optional[BaseSolver] = None
+
+ def set_solver(self, solver: BaseSolver) -> None:
+ self.solver = solver
+
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ return self.solver.run(graph_connector=graph_connector)
+
+ def get_output_id(self, use_bc_ids: bool) -> str:
+ return self.bc_id if self.bc_id and use_bc_ids else self.id
diff --git a/checkov/common/graph/checks_infra/base_parser.py b/checkov/common/graph/checks_infra/base_parser.py
new file mode 100644
index 0000000000..fc8715bb1f
--- /dev/null
+++ b/checkov/common/graph/checks_infra/base_parser.py
@@ -0,0 +1,8 @@
+from typing import Dict, Any
+
+from checkov.common.graph.checks_infra.base_check import BaseGraphCheck
+
+
+class BaseGraphCheckParser:
+ def parse_raw_check(self, raw_check: Dict[str, Dict[str, Any]], **kwargs: Any) -> BaseGraphCheck:
+ raise NotImplementedError
diff --git a/checkov/common/graph/checks_infra/enums.py b/checkov/common/graph/checks_infra/enums.py
new file mode 100644
index 0000000000..f84dbf3c85
--- /dev/null
+++ b/checkov/common/graph/checks_infra/enums.py
@@ -0,0 +1,38 @@
+from enum import Enum
+
+
+class SolverType(str, Enum):
+ # A solver is a class that resolves YAML syntax into a graph query
+ # It can have the following types:
+ ATTRIBUTE = "ATTRIBUTE"
+ # An attribute query, i.e. id equals 3
+
+ COMPLEX = "COMPLEX"
+ # A combination of queries, i.e. AND
+
+ CONNECTION = "CONNECTION"
+ # A connection between two entities, i.e. ec2 instance connected to security group
+
+ # TODO: merge with COMPLEX
+ COMPLEX_CONNECTION = "COMPLEX_CONNECTION"
+ # A combination of CONNECTION solver and any other solver
+
+ FILTER = "FILTER"
+ # Filters results according to specific value / type, i.e. resource type is aws_s3_bucket
+
+
+class Operators:
+ ANY = 'any'
+ EXISTS = 'exists'
+ NOT_EXISTS = 'not_exists'
+ CONTAINS = 'contains'
+ NOT_CONTAINS = 'not_contains'
+ ENDING_WITH = 'ending_with'
+ NOT_ENDING_WITH = 'not_ending_with'
+ EQUALS = 'equals'
+ NOT_EQUALS = 'not_equals'
+ STARTING_WITH = 'starting_with'
+ NOT_STARTING_WITH = 'not_starting_with'
+ WITHIN = 'within'
+ AND = 'and'
+ OR = 'or'
diff --git a/checkov/common/graph/checks_infra/registry.py b/checkov/common/graph/checks_infra/registry.py
new file mode 100644
index 0000000000..a70bd4f580
--- /dev/null
+++ b/checkov/common/graph/checks_infra/registry.py
@@ -0,0 +1,38 @@
+import logging
+from typing import List, Dict, Any
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.base_check import BaseGraphCheck
+from checkov.common.graph.checks_infra.base_parser import BaseGraphCheckParser
+from checkov.common.models.enums import CheckResult
+from checkov.runner_filter import RunnerFilter
+
+
+class BaseRegistry:
+ def __init__(self, parser: BaseGraphCheckParser) -> None:
+ self.checks: List[BaseGraphCheck] = []
+ self.parser = parser
+
+ def load_checks(self):
+ raise NotImplementedError
+
+ def run_checks(
+ self, graph_connector: DiGraph, runner_filter: RunnerFilter
+ ) -> Dict[BaseGraphCheck, List[Dict[str, Any]]]:
+ check_results = {}
+ for check in self.checks:
+ if not runner_filter.should_run_check(check.id, check.bc_id):
+ continue
+ logging.debug(f'Running graph check: {check.id}')
+ passed, failed = check.run(graph_connector)
+ check_result = self._process_check_result(passed, [], CheckResult.PASSED)
+ check_result = self._process_check_result(failed, check_result, CheckResult.FAILED)
+ check_results[check] = check_result
+ return check_results
+
+ @staticmethod
+ def _process_check_result(results, processed_results, result):
+ for vertex in results:
+ processed_results.append({"result": result, "entity": vertex})
+ return processed_results
diff --git a/checkov/common/graph/checks_infra/solvers/__init__.py b/checkov/common/graph/checks_infra/solvers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/common/graph/checks_infra/solvers/base_solver.py b/checkov/common/graph/checks_infra/solvers/base_solver.py
new file mode 100644
index 0000000000..8099a463d6
--- /dev/null
+++ b/checkov/common/graph/checks_infra/solvers/base_solver.py
@@ -0,0 +1,29 @@
+from abc import abstractmethod
+from typing import Tuple, List, Dict, Any
+
+from networkx import DiGraph
+
+from checkov.common.graph.checks_infra.enums import SolverType
+
+
+class BaseSolver:
+ operator = ""
+
+ def __init__(self, solver_type: SolverType) -> None:
+ self.solver_type = solver_type
+
+ @abstractmethod
+ def get_operation(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
+ @abstractmethod
+ def _get_operation(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
+ @abstractmethod
+ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ raise NotImplementedError()
+
+ @staticmethod
+ def resource_type_pred(v: Dict[str, Any], resource_types: List[str]) -> bool:
+ return len(resource_types) == 0 or v.get("resource_type") in resource_types
diff --git a/checkov/common/graph/db_connectors/__init__.py b/checkov/common/graph/db_connectors/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/common/graph/db_connectors/db_connector.py b/checkov/common/graph/db_connectors/db_connector.py
new file mode 100644
index 0000000000..165d7e72ff
--- /dev/null
+++ b/checkov/common/graph/db_connectors/db_connector.py
@@ -0,0 +1,12 @@
+class DBConnector:
+ def save_graph(self, local_graph):
+ pass
+
+ def get_reader_endpoint(self):
+ pass
+
+ def get_writer_endpoint(self):
+ pass
+
+ def disconnect(self):
+ pass
diff --git a/checkov/common/graph/db_connectors/networkx/__init__.py b/checkov/common/graph/db_connectors/networkx/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/common/graph/db_connectors/networkx/networkx_db_connector.py b/checkov/common/graph/db_connectors/networkx/networkx_db_connector.py
new file mode 100644
index 0000000000..0110371689
--- /dev/null
+++ b/checkov/common/graph/db_connectors/networkx/networkx_db_connector.py
@@ -0,0 +1,29 @@
+import networkx as nx
+
+from checkov.common.graph.db_connectors.db_connector import DBConnector
+from checkov.common.graph.graph_builder import CustomAttributes
+
+
+class NetworkxConnector(DBConnector):
+ def __init__(self):
+ self.graph = nx.DiGraph()
+
+ def save_graph(self, local_graph, add_bulk_edges=False):
+ return self.networkx_from_local_graph(local_graph)
+
+ def get_reader_endpoint(self):
+ return self.graph
+
+ def get_writer_endpoint(self):
+ return self.graph
+
+ def networkx_from_local_graph(self, local_graph):
+ self.graph = nx.DiGraph()
+ vertices_attributes = [v.get_attribute_dict() for v in local_graph.vertices]
+ vertices_to_add = [(attr[CustomAttributes.HASH], attr) for attr in vertices_attributes]
+ edges_to_add = [(vertices_attributes[e.origin][CustomAttributes.HASH], vertices_attributes[e.dest][CustomAttributes.HASH], {'label': e.label}) for e in local_graph.edges]
+
+ self.graph.add_nodes_from(vertices_to_add)
+ self.graph.add_edges_from(edges_to_add)
+
+ return self.graph
diff --git a/checkov/common/graph/graph_builder/__init__.py b/checkov/common/graph/graph_builder/__init__.py
new file mode 100644
index 0000000000..c805bc1291
--- /dev/null
+++ b/checkov/common/graph/graph_builder/__init__.py
@@ -0,0 +1,2 @@
+from .graph_components.edge import *
+from .graph_components.attribute_names import *
\ No newline at end of file
diff --git a/checkov/common/graph/graph_builder/graph_components/__init__.py b/checkov/common/graph/graph_builder/graph_components/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/common/graph/graph_builder/graph_components/attribute_names.py b/checkov/common/graph/graph_builder/graph_components/attribute_names.py
new file mode 100644
index 0000000000..e5786c5d0e
--- /dev/null
+++ b/checkov/common/graph/graph_builder/graph_components/attribute_names.py
@@ -0,0 +1,40 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import List, Any
+
+
+@dataclass
+class CustomAttributes:
+ BLOCK_NAME = "block_name_"
+ BLOCK_TYPE = "block_type_"
+ FILE_PATH = "file_path_"
+ CONFIG = "config_"
+ ATTRIBUTES = "attributes_"
+ LABEL = "label_"
+ ID = "id_"
+ HASH = "hash"
+ RENDERING_BREADCRUMBS = "rendering_breadcrumbs_"
+ SOURCE = "source_"
+ RESOURCE_TYPE = "resource_type"
+ RESOURCE_ID = "resource_id"
+ SOURCE_MODULE = "source_module_"
+
+
+def props(cls: Any) -> List[str]:
+ return [i for i in cls.__dict__.keys() if i[:1] != "_"]
+
+
+reserved_attribute_names = props(CustomAttributes)
+
+
+class EncryptionValues(Enum):
+ ENCRYPTED = "ENCRYPTED"
+ UNENCRYPTED = "UNENCRYPTED"
+
+
+class EncryptionTypes(Enum):
+ KMS_VALUE = "KMS"
+ NODE_TO_NODE = "node-to-node"
+ DEFAULT_KMS = "Default KMS"
+ AES256 = "AES256"
+ AWS_KMS_VALUE = "aws:kms"
diff --git a/checkov/common/graph/graph_builder/graph_components/block_types.py b/checkov/common/graph/graph_builder/graph_components/block_types.py
new file mode 100644
index 0000000000..8de5b28a07
--- /dev/null
+++ b/checkov/common/graph/graph_builder/graph_components/block_types.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class BlockType:
+ RESOURCE = "resource"
+
+ def get(self, attr_name):
+ return getattr(self, attr_name.upper())
diff --git a/checkov/common/graph/graph_builder/graph_components/blocks.py b/checkov/common/graph/graph_builder/graph_components/blocks.py
new file mode 100644
index 0000000000..265e639228
--- /dev/null
+++ b/checkov/common/graph/graph_builder/graph_components/blocks.py
@@ -0,0 +1,177 @@
+from copy import deepcopy
+from typing import Union, Dict, Any, List
+
+from checkov.common.graph.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.common.graph.graph_builder.graph_components.block_types import BlockType
+from checkov.common.graph.graph_builder.utils import calculate_hash, join_trimmed_strings, stringify_value
+
+
+class Block:
+ def __init__(
+ self,
+ name: str,
+ config: Dict[str, Any],
+ path: str,
+ block_type: str,
+ attributes: Dict[str, Any],
+ id: str = "",
+ source: str = "",
+ ) -> None:
+ """
+ :param name: unique name given to the block, for example
+ :param config: the section in tf_definitions that belong to this block
+ :param path: the file location of the block
+ :param block_type: str
+ :param attributes: dictionary of the block's original attributes in the origin file
+ """
+ self.name = name
+ self.config = deepcopy(config)
+ self.path = path
+ self.block_type = block_type
+ self.attributes = attributes
+ self.id = id
+ self.source = source
+ self.changed_attributes: Dict[str, List[Any]] = {}
+ self.breadcrumbs: Dict[str, List[Dict[str, Any]]] = {}
+
+ attributes_to_add = self._extract_inner_attributes()
+ self.attributes.update(attributes_to_add)
+
+ def _extract_inner_attributes(self) -> Dict[str, Any]:
+ attributes_to_add = {}
+ for attribute_key in self.attributes:
+ attribute_value = self.attributes[attribute_key]
+ if isinstance(attribute_value, dict) or (isinstance(attribute_value, list) and len(attribute_value) > 0 and isinstance(attribute_value[0], dict)):
+ inner_attributes = get_inner_attributes(attribute_key, attribute_value)
+ attributes_to_add.update(inner_attributes)
+ return attributes_to_add
+
+ def __str__(self) -> str:
+ return f"{self.block_type}: {self.name}"
+
+ def get_attribute_dict(self) -> Dict[str, Any]:
+ """
+ :return: map of all the block's native attributes (from the source file),
+ combined with the attributes generated by the module builder.
+ If the attributes are not a primitive type, they are converted to strings.
+ """
+ base_attributes = self.get_base_attributes()
+ self.get_origin_attributes(base_attributes)
+
+ if self.changed_attributes:
+ # add changed attributes only for calculating the hash
+ base_attributes["changed_attributes"] = sorted(self.changed_attributes.keys())
+
+ if self.breadcrumbs:
+ sorted_breadcrumbs = dict(sorted(self.breadcrumbs.items()))
+ base_attributes[CustomAttributes.RENDERING_BREADCRUMBS] = sorted_breadcrumbs
+
+ base_attributes[CustomAttributes.HASH] = calculate_hash(base_attributes)
+
+ if "changed_attributes" in base_attributes:
+ # removed changed attributes if it was added previously for calculating hash.
+ del base_attributes["changed_attributes"]
+
+ return base_attributes
+
+ def get_origin_attributes(self, base_attributes: Dict[str, Any]) -> None:
+ for attribute_key in list(self.attributes.keys()):
+ attribute_value = self.attributes[attribute_key]
+ if isinstance(attribute_value, list) and len(attribute_value) == 1:
+ attribute_value = attribute_value[0]
+ if isinstance(attribute_value, (list, dict)):
+ inner_attributes = get_inner_attributes(attribute_key, attribute_value)
+ base_attributes.update(inner_attributes)
+ if attribute_key == "self":
+ base_attributes["self_"] = attribute_value
+ continue
+ else:
+ base_attributes[attribute_key] = attribute_value
+
+ def get_hash(self) -> str:
+ attributes_dict = self.get_attribute_dict()
+ return attributes_dict.get(CustomAttributes.HASH, "")
+
+ def update_attribute(
+ self, attribute_key: str, attribute_value: Any, change_origin_id: int, previous_breadcrumbs: List[int]
+ ) -> None:
+ if not previous_breadcrumbs or previous_breadcrumbs[-1] != change_origin_id:
+ previous_breadcrumbs.append(change_origin_id)
+
+ self.update_inner_attribute(attribute_key, self.attributes, attribute_value)
+ attribute_key_parts = attribute_key.split(".")
+ if len(attribute_key_parts) == 1:
+ self.attributes[attribute_key] = attribute_value
+ self.changed_attributes[attribute_key] = previous_breadcrumbs
+ return
+ for i in range(len(attribute_key_parts)):
+ key = join_trimmed_strings(char_to_join=".", str_lst=attribute_key_parts, num_to_trim=i)
+ if key.find(".") > -1:
+ self.attributes[key] = attribute_value
+ attribute_value = {attribute_key_parts[len(attribute_key_parts) - 1 - i]: attribute_value}
+ self.changed_attributes[key] = previous_breadcrumbs
+
+ def update_inner_attribute(
+ self, attribute_key: str, nested_attributes: Union[List[Any], Dict[str, Any]], value_to_update: Any
+ ) -> None:
+ split_key = attribute_key.split(".")
+ i = 1
+ curr_key = ".".join(split_key[0:i])
+ if isinstance(nested_attributes, list):
+ if curr_key.isnumeric():
+ curr_key_int = int(curr_key)
+ if curr_key_int < len(nested_attributes):
+ if not isinstance(nested_attributes[curr_key_int], dict):
+ nested_attributes[curr_key_int] = value_to_update
+ else:
+ self.update_inner_attribute(
+ ".".join(split_key[i:]), nested_attributes[curr_key_int], value_to_update
+ )
+ else:
+ for inner in nested_attributes:
+ self.update_inner_attribute(curr_key, inner, value_to_update)
+ elif isinstance(nested_attributes, dict):
+ while curr_key not in nested_attributes and i <= len(split_key):
+ i += 1
+ curr_key = ".".join(split_key[0:i])
+ if attribute_key in nested_attributes.keys():
+ nested_attributes[attribute_key] = value_to_update
+ if len(split_key) == 1 and len(curr_key) > 0:
+ nested_attributes[curr_key] = value_to_update
+ elif curr_key in nested_attributes.keys():
+ self.update_inner_attribute(".".join(split_key[i:]), nested_attributes[curr_key], value_to_update)
+
+ def get_export_data(self) -> Dict[str, Union[bool, str]]:
+ return {"type": self.block_type, "name": self.name, "path": self.path}
+
+ def get_base_attributes(self) -> Dict[str, Union[str, List[str], Dict[str, Any]]]:
+ return {
+ CustomAttributes.BLOCK_NAME: self.name,
+ CustomAttributes.BLOCK_TYPE: self.block_type,
+ CustomAttributes.FILE_PATH: self.path,
+ CustomAttributes.CONFIG: self.config,
+ CustomAttributes.LABEL: str(self),
+ CustomAttributes.ID: self.id,
+ CustomAttributes.SOURCE: self.source,
+ }
+
+
+def get_inner_attributes(attribute_key: str, attribute_value: Union[str, List[str], Dict[str, Any]]) -> Dict[str, Any]:
+ inner_attributes: Dict[str, Any] = {}
+ if isinstance(attribute_value, list) and len(attribute_value) == 1:
+ attribute_value = attribute_value[0]
+
+ if isinstance(attribute_value, (dict, list)):
+ inner_attributes[attribute_key] = [None] * len(attribute_value) if isinstance(attribute_value, list) else {}
+ iterator: Union[range, List[str]] = range(len(attribute_value)) if isinstance(attribute_value, list) else list(attribute_value.keys())
+ for key in iterator:
+ if key != "":
+ inner_key = f"{attribute_key}.{key}"
+ inner_value = attribute_value[key]
+ inner_attributes.update(get_inner_attributes(inner_key, inner_value))
+ inner_attributes[attribute_key][key] = inner_attributes[inner_key]
+ else:
+ del attribute_value[key]
+ else:
+ inner_attributes[attribute_key] = attribute_value
+ return inner_attributes
diff --git a/checkov/common/graph/graph_builder/graph_components/edge.py b/checkov/common/graph/graph_builder/graph_components/edge.py
new file mode 100644
index 0000000000..650d124c63
--- /dev/null
+++ b/checkov/common/graph/graph_builder/graph_components/edge.py
@@ -0,0 +1,17 @@
+class Edge:
+ def __init__(self, origin: int, dest: int, label: str) -> None:
+ self.origin = origin
+ self.dest = dest
+ self.label = label
+
+ def __str__(self) -> str:
+ return f"[{self.origin} -({self.label})-> {self.dest}]"
+
+ def __eq__(self, other) -> bool:
+ return isinstance(other, Edge) and str(self) == str(other)
+
+ def __ne__(self, other) -> bool:
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return hash(str(self))
\ No newline at end of file
diff --git a/checkov/common/graph/graph_builder/local_graph.py b/checkov/common/graph/graph_builder/local_graph.py
new file mode 100644
index 0000000000..547166137a
--- /dev/null
+++ b/checkov/common/graph/graph_builder/local_graph.py
@@ -0,0 +1,22 @@
+from abc import abstractmethod
+from collections import defaultdict
+from typing import List, Dict
+
+from checkov.common.graph.graph_builder import Edge
+from checkov.common.graph.graph_builder.graph_components.block_types import BlockType
+from checkov.common.graph.graph_builder.graph_components.blocks import Block
+
+
+class LocalGraph:
+ def __init__(self) -> None:
+ self.vertices: List[Block] = []
+ self.edges: List[Edge] = []
+ self.in_edges: Dict[int, List[Edge]] = defaultdict(list) # map between vertex index and the edges entering it
+ self.out_edges: Dict[int, List[Edge]] = defaultdict(list) # map between vertex index and the edges exiting it
+ self.vertices_by_block_type: Dict[str, List[int]] = defaultdict(list)
+ self.vertex_hash_cache: Dict[int, str] = defaultdict(str)
+ self.vertices_block_name_map: Dict[str, Dict[str, List[int]]] = defaultdict(lambda: defaultdict(list))
+
+ @abstractmethod
+ def build_graph(self, render_variables: bool) -> None:
+ pass
diff --git a/checkov/common/graph/graph_builder/utils.py b/checkov/common/graph/graph_builder/utils.py
new file mode 100644
index 0000000000..5b93ed39e2
--- /dev/null
+++ b/checkov/common/graph/graph_builder/utils.py
@@ -0,0 +1,43 @@
+import concurrent
+import hashlib
+import json
+from typing import Union, List, Dict, Any, Callable, Optional
+
+
+def stringify_value(value: Union[bool, int, float, str, List[str], Dict[str, Any]]) -> str:
+ if isinstance(value, bool):
+ value = str(value).lower()
+ elif isinstance(value, (float, int)):
+ value = str(value)
+ return json.dumps(value, indent=4, default=str)
+
+
+def calculate_hash(data: Union[bool, int, float, str, Dict[str, Any]]) -> str:
+ encoded_attributes = stringify_value(data)
+ sha256 = hashlib.sha256()
+ sha256.update(repr(encoded_attributes).encode("utf-8"))
+
+ return sha256.hexdigest()
+
+
+def join_trimmed_strings(char_to_join: str, str_lst: List[str], num_to_trim: int) -> str:
+ return char_to_join.join(str_lst[: len(str_lst) - num_to_trim])
+
+
+def run_function_multithreaded(
+ func: Callable[..., Any], data: List[List[Any]], max_group_size: int, num_of_workers: Optional[int] = None
+) -> None:
+ groups_of_data = [data[i : i + max_group_size] for i in range(0, len(data), max_group_size)]
+ if not num_of_workers:
+ num_of_workers = len(groups_of_data)
+ if num_of_workers > 0:
+ with concurrent.futures.ThreadPoolExecutor(max_workers=num_of_workers) as executor:
+ futures = {executor.submit(func, data_group): data_group for data_group in groups_of_data}
+ wait_result = concurrent.futures.wait(futures)
+ if wait_result.not_done:
+ raise Exception(f"failed to perform {func.__name__}")
+ for future in futures:
+ try:
+ future.result()
+ except Exception as e:
+ raise e
diff --git a/checkov/common/graph/graph_manager.py b/checkov/common/graph/graph_manager.py
new file mode 100644
index 0000000000..95f396671c
--- /dev/null
+++ b/checkov/common/graph/graph_manager.py
@@ -0,0 +1,32 @@
+from abc import abstractmethod
+from typing import List
+
+from checkov.common.graph.graph_builder.local_graph import LocalGraph
+
+
+class GraphManager:
+ def __init__(self, db_connector, parser, source=''):
+ self.db_connector = db_connector
+ self.source = source
+ self.parser = parser
+
+ @abstractmethod
+ def build_graph_from_source_directory(self, source_dir, render_variables=True, local_graph_class=LocalGraph,
+ parsing_errors=None, download_external_modules=False, excluded_paths: List[str]=None):
+ pass
+
+ @abstractmethod
+ def build_graph_from_definitions(self, definitions, render_variables=True):
+ pass
+
+ def save_graph(self, graph):
+ return self.db_connector.save_graph(graph)
+
+ def get_reader_endpoint(self):
+ return self.db_connector.get_reader_endpoint()
+
+ def get_writer_endpoint(self):
+ return self.db_connector.get_writer_endpoint()
+
+ def disconnect_from_db(self):
+ self.db_connector.disconnect()
diff --git a/checkov/common/models/consts.py b/checkov/common/models/consts.py
index a62a7fa04d..320f9669c2 100644
--- a/checkov/common/models/consts.py
+++ b/checkov/common/models/consts.py
@@ -4,3 +4,4 @@
access_key_pattern = "(? None:
+ self.__wrappers__: Dict[Any, Callable[..., T]] = {}
- def __call__(self, fn):
+ def __call__(self, fn: Callable[..., T]) -> Callable[..., T]:
fn.add_signature = self.add_signature
fn.__multi_signature_wrappers__ = self.__wrappers__
return fn
- def add_signature(self, *, args, varargs=None, varkw=None):
+ def add_signature(
+ self, *, args: List[str], varargs: Any = None, varkw: Any = None
+ ) -> Callable[[Callable[..., T]], Callable[..., T]]:
"""
Registers a new wrapper for the decorated function.
@@ -102,7 +105,7 @@ def add_signature(self, *, args, varargs=None, varkw=None):
>>> return wrapper
"""
- def wrapper(fn):
+ def wrapper(fn: Callable[..., T]) -> Callable[..., T]:
self.__wrappers__[(tuple(args), varargs, varkw)] = fn
return fn
diff --git a/checkov/common/output/baseline.py b/checkov/common/output/baseline.py
new file mode 100644
index 0000000000..13a02bdbde
--- /dev/null
+++ b/checkov/common/output/baseline.py
@@ -0,0 +1,79 @@
+from collections import defaultdict
+from typing import Dict, List, DefaultDict
+
+from checkov.common.output.report import Report
+import json
+from checkov.common.output.record import Record
+
+
+class Baseline:
+ path = ""
+ failed_checks: DefaultDict[str, List[Dict[str, List[str]]]] = defaultdict(list)
+
+ def add_findings_from_report(self, report: Report) -> None:
+ for check in report.failed_checks:
+ try:
+ existing = next(x for x in self.failed_checks[check.file_path] if x['resource'] == check.resource)
+ except StopIteration:
+ existing = {"resource": check.resource, "check_ids": []}
+ self.failed_checks[check.file_path].append(existing)
+ existing['check_ids'].append(check.check_id)
+ existing['check_ids'].sort() # Sort the check IDs to be nicer to the eye
+
+ def to_dict(self) -> dict:
+ """
+ The output of this class needs to be very explicit, hence the following structure of the dict:
+ {
+ "failed_checks": [
+ {
+ "file": "path/to/file",
+ "findings: [
+ {
+ "resource": "aws_s3_bucket.this",
+ "check_ids": [
+ "CKV_AWS_1",
+ "CKV_AWS_2",
+ "CKV_AWS_3"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ """
+ failed_checks_list = []
+ for file, findings in self.failed_checks.items():
+ formatted_findings = []
+ for finding in findings:
+ formatted_findings.append({"resource": finding['resource'], "check_ids": finding["check_ids"]})
+ failed_checks_list.append({"file": file, "findings": formatted_findings})
+
+ resp = {
+ "failed_checks": failed_checks_list
+ }
+ return resp
+
+ def compare_and_reduce_reports(self, scan_reports: List[Report]) -> None:
+ for scan_report in scan_reports:
+ scan_report.passed_checks = [check for check in scan_report.passed_checks if
+ self._is_check_in_baseline(check)]
+ scan_report.skipped_checks = [check for check in scan_report.skipped_checks if
+ self._is_check_in_baseline(check)]
+ scan_report.failed_checks = [check for check in scan_report.failed_checks if
+ not self._is_check_in_baseline(check)]
+
+ def _is_check_in_baseline(self, check: Record) -> bool:
+ failed_check_id = check.check_id
+ failed_check_resource = check.resource
+ for baseline_failed_check in self.failed_checks:
+ for finding in baseline_failed_check["findings"]:
+ if finding["resource"] == failed_check_resource and failed_check_id in finding["check_ids"]:
+ return True
+ return False
+
+ def from_json(self, file_path: str) -> None:
+
+ self.path = file_path
+ with open(file_path, 'r') as f:
+ baseline_raw = json.load(f)
+ self.failed_checks = baseline_raw.get("failed_checks", {})
diff --git a/checkov/common/output/graph_record.py b/checkov/common/output/graph_record.py
new file mode 100644
index 0000000000..2661efa8e0
--- /dev/null
+++ b/checkov/common/output/graph_record.py
@@ -0,0 +1,13 @@
+from checkov.common.output.record import Record
+
+
+class GraphRecord(Record):
+ breadcrumbs = {}
+
+ def __init__(self, record, breadcrumbs):
+ super().__init__(record.check_id, record.check_name, record.check_result, record.code_block, record.file_path,
+ record.file_line_range, record.resource, record.evaluations, record.check_class,
+ record.file_abs_path, record.entity_tags, record.caller_file_path,
+ record.caller_file_line_range, bc_check_id=record.bc_check_id)
+ self.fixed_definition = record.fixed_definition
+ self.breadcrumbs = breadcrumbs
diff --git a/checkov/common/output/record.py b/checkov/common/output/record.py
index b9c492d403..94bfef8410 100644
--- a/checkov/common/output/record.py
+++ b/checkov/common/output/record.py
@@ -17,11 +17,16 @@ class Record:
code_block = ""
file_path = ""
file_line_range = []
+ caller_file_path = None # When created from a module
+ caller_file_line_range = None #
resource = ""
guideline = None
+ fixed_definition = None
+ entity_tags = None
def __init__(self, check_id, check_name, check_result, code_block, file_path, file_line_range, resource,
- evaluations, check_class, file_abs_path):
+ evaluations, check_class, file_abs_path, entity_tags=None,
+ caller_file_path=None, caller_file_line_range=None, bc_check_id=None):
"""
:param evaluations: A dict with the key being the variable name, value being a dict containing:
- 'var_file'
@@ -29,15 +34,21 @@ def __init__(self, check_id, check_name, check_result, code_block, file_path, fi
- 'definitions', a list of dicts which contain 'definition_expression'
"""
self.check_id = check_id
+ self.bc_check_id = bc_check_id
self.check_name = check_name
self.check_result = check_result
self.code_block = code_block
self.file_path = file_path
+ self.file_abs_path = file_abs_path
self.repo_file_path = f'/{os.path.relpath(file_abs_path)}' # matches file paths given in the BC platform.
self.file_line_range = file_line_range
self.resource = resource
self.evaluations = evaluations
self.check_class = check_class
+ self.fixed_definition = None
+ self.entity_tags = entity_tags
+ self.caller_file_path = caller_file_path
+ self.caller_file_line_range = caller_file_line_range
def set_guideline(self, guideline):
self.guideline = guideline
@@ -63,7 +74,7 @@ def _code_line_string(code_block):
string_block += "\t\t" + Fore.WHITE + str(line_num) + spaces + ' | ' + Fore.YELLOW + line
return string_block
- def __str__(self):
+ def to_string(self, compact=False, use_bc_ids=False):
status = ''
evaluation_message = f''
status_color = "white"
@@ -78,7 +89,7 @@ def __str__(self):
status_color = 'blue'
suppress_comment = "\tSuppress comment: {}\n".format(self.check_result['suppress_comment'])
- check_message = colored("Check: {}: \"{}\"\n".format(self.check_id, self.check_name), "white")
+ check_message = colored("Check: {}: \"{}\"\n".format(self.get_output_id(use_bc_ids), self.check_name), "white")
guideline_message = ''
if self.guideline:
guideline_message = "\tGuide: " + Style.BRIGHT + colored(f"{self.guideline}\n", 'blue', attrs=['underline']) + Style.RESET_ALL
@@ -89,6 +100,12 @@ def __str__(self):
if self.code_block:
code_lines = "\n{}\n".format("".join(
[self._code_line_string(self.code_block)]))
+ caller_file_details = ""
+ if self.caller_file_path and self.caller_file_line_range:
+ caller_file_details = colored(
+ "\tCalling File: {}:{}\n".format(self.caller_file_path,
+ "-".join([str(x) for x in self.caller_file_line_range])),
+ "magenta")
if self.evaluations:
for (var_name, var_evaluations) in self.evaluations.items():
var_file = var_evaluations['var_file']
@@ -101,10 +118,19 @@ def __str__(self):
f'in expression: {colored(definition_obj["definition_name"] + " = ", "yellow")}{colored(definition_obj["definition_expression"], "yellow")}\n',
'white')
status_message = colored("\t{} for resource: {}\n".format(status, self.resource), status_color)
- if self.check_result['result'] == CheckResult.FAILED and code_lines:
- return check_message + status_message + file_details + guideline_message + code_lines + evaluation_message
+ if self.check_result['result'] == CheckResult.FAILED and code_lines and not compact:
+ return check_message + status_message + file_details + caller_file_details + guideline_message + code_lines + evaluation_message
if self.check_result['result'] == CheckResult.SKIPPED:
- return check_message + status_message + suppress_comment + file_details + guideline_message
+ return check_message + status_message + suppress_comment + file_details + caller_file_details + guideline_message
else:
- return check_message + status_message + file_details + evaluation_message + guideline_message
+ return check_message + status_message + file_details + caller_file_details + evaluation_message + guideline_message
+
+ def __str__(self):
+ return self.to_string()
+
+ def get_output_id(self, use_bc_ids: bool) -> str:
+ return self.bc_check_id if self.bc_check_id and use_bc_ids else self.check_id
+
+ def get_unique_string(self):
+ return f"{self.check_id}.{self.check_result}.{self.file_abs_path}.{self.file_line_range}.{self.resource}"
diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py
index 29eba19036..1cb7a340be 100644
--- a/checkov/common/output/report.py
+++ b/checkov/common/output/report.py
@@ -1,137 +1,381 @@
import json
from collections import defaultdict
-
+from typing import List, Dict, Union, Any, Optional
from colorama import init
-from junit_xml import TestCase, TestSuite
+from junit_xml import TestCase, TestSuite, to_xml_report_string
+from tabulate import tabulate
from termcolor import colored
from checkov.common.models.enums import CheckResult
+from checkov.common.output.record import Record
+from checkov.common.util.type_forcers import convert_csv_string_arg_to_list
from checkov.version import version
-from tabulate import tabulate
init(autoreset=True)
class Report:
+ def __init__(self, check_type: str):
+ self.check_type: str = check_type
+ self.passed_checks: List[Record] = []
+ self.failed_checks: List[Record] = []
+ self.skipped_checks: List[Record] = []
+ self.parsing_errors: List[str] = []
- def __init__(self, check_type):
- self.check_type = check_type
- self.passed_checks = []
- self.failed_checks = []
- self.skipped_checks = []
- self.parsing_errors = []
-
- def add_parsing_errors(self, errors):
+ def add_parsing_errors(self, errors: List[str]) -> None:
for file in errors:
self.add_parsing_error(file)
- def add_parsing_error(self, file):
+ def add_parsing_error(self, file: str) -> None:
if file:
self.parsing_errors.append(file)
- def add_record(self, record):
- if record.check_result['result'] == CheckResult.PASSED:
+ def add_record(self, record: Record) -> None:
+ if record.check_result["result"] == CheckResult.PASSED:
self.passed_checks.append(record)
- if record.check_result['result'] == CheckResult.FAILED:
+ if record.check_result["result"] == CheckResult.FAILED:
self.failed_checks.append(record)
- if record.check_result['result'] == CheckResult.SKIPPED:
+ if record.check_result["result"] == CheckResult.SKIPPED:
self.skipped_checks.append(record)
- def get_summary(self):
+ def get_summary(self) -> Dict[str, Union[int, str]]:
return {
"passed": len(self.passed_checks),
"failed": len(self.failed_checks),
"skipped": len(self.skipped_checks),
"parsing_errors": len(self.parsing_errors),
- "checkov_version": version
+ "resource_count": self._count_resources(),
+ "checkov_version": version,
}
- def get_json(self):
+ def get_json(self) -> str:
return json.dumps(self.get_dict(), indent=4)
- def get_dict(self):
- return {
- "check_type": self.check_type,
- "results": {
- "passed_checks": [check.__dict__ for check in self.passed_checks],
- "failed_checks": [check.__dict__ for check in self.failed_checks],
- "skipped_checks": [check.__dict__ for check in self.skipped_checks],
- "parsing_errors": list(self.parsing_errors)
- },
- "summary": self.get_summary()
- }
+ def get_dict(self, is_quiet=False) -> dict:
+ if is_quiet:
+ return {
+ "check_type": self.check_type,
+ "results": {
+ "failed_checks": [check.__dict__ for check in self.failed_checks]
+ },
+ "summary": self.get_summary(),
+ }
+ else:
+ return {
+ "check_type": self.check_type,
+ "results": {
+ "passed_checks": [check.__dict__ for check in self.passed_checks],
+ "failed_checks": [check.__dict__ for check in self.failed_checks],
+ "skipped_checks": [check.__dict__ for check in self.skipped_checks],
+ "parsing_errors": list(self.parsing_errors),
+ },
+ "summary": self.get_summary(),
+ }
- def get_exit_code(self, soft_fail):
+ def get_exit_code(
+ self,
+ soft_fail: bool,
+ soft_fail_on: Optional[list] = None,
+ hard_fail_on: Optional[list] = None,
+ ) -> int:
+ """
+ Returns the appropriate exit code depending on the flags that are passed in.
+
+ :param soft_fail: If true, exit code is always 0. (default is false)
+ :param soft_fail_on: A list of checks that will return exit code 0 if they fail. Other failing checks will
+ result exit code 1.
+ :param hard_fail_on: A list of checks that will return exit code 1 if they fail. Other failing checks will
+ result exit code 0.
+ :return: Exit code 0 or 1.
+ """
+ if soft_fail_on:
+ soft_fail_on = convert_csv_string_arg_to_list(soft_fail_on)
+ if all(
+ (check_id in soft_fail_on or bc_check_id in soft_fail_on)
+ for (check_id, bc_check_id) in (
+ (failed_check.check_id, failed_check.bc_check_id)
+ for failed_check in self.failed_checks
+ )
+ ):
+ # List of "failed checks" is a subset of the "soft fail on" list.
+ return 0
+ else:
+ return 1
+ if hard_fail_on:
+ hard_fail_on = convert_csv_string_arg_to_list(hard_fail_on)
+ if any(
+ (check_id in hard_fail_on or bc_check_id in hard_fail_on)
+ for (check_id, bc_check_id) in (
+ (failed_check.check_id, failed_check.bc_check_id)
+ for failed_check in self.failed_checks
+ )
+ ):
+ # Any check from the list of "failed checks" is in the list of "hard fail on checks".
+ return 1
+ else:
+ return 0
if soft_fail:
return 0
elif len(self.failed_checks) > 0:
return 1
return 0
- def is_empty(self):
- return len(self.passed_checks) + len(self.failed_checks) + len(self.skipped_checks) + len(self.parsing_errors) == 0
+ def is_empty(self) -> bool:
+ return (
+ len(self.passed_checks + self.failed_checks + self.skipped_checks)
+ + len(self.parsing_errors)
+ == 0
+ )
- def print_console(self, is_quiet=False):
+ def print_console(
+ self,
+ is_quiet=False,
+ is_compact=False,
+ created_baseline_path=None,
+ baseline=None,
+ use_bc_ids=False,
+ ) -> None:
summary = self.get_summary()
print(colored(f"{self.check_type} scan results:", "blue"))
if self.parsing_errors:
message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Parsing errors: {}\n".format(
- summary["passed"], summary["failed"], summary["skipped"], summary["parsing_errors"])
+ summary["passed"],
+ summary["failed"],
+ summary["skipped"],
+ summary["parsing_errors"],
+ )
else:
- message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}\n".format(
- summary["passed"], summary["failed"], summary["skipped"])
+ message = (
+ "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}\n".format(
+ summary["passed"], summary["failed"], summary["skipped"]
+ )
+ )
print(colored(message, "cyan"))
if not is_quiet:
for record in self.passed_checks:
- print(record)
+ print(record.to_string(compact=is_compact, use_bc_ids=use_bc_ids))
for record in self.failed_checks:
- print(record)
+ print(record.to_string(compact=is_compact, use_bc_ids=use_bc_ids))
if not is_quiet:
for record in self.skipped_checks:
- print(record)
+ print(record.to_string(compact=is_compact, use_bc_ids=use_bc_ids))
if not is_quiet:
for file in self.parsing_errors:
Report._print_parsing_error_console(file)
+ if created_baseline_path:
+ print(
+ colored(
+ f"Created a checkov baseline file at {created_baseline_path}",
+ "blue",
+ )
+ )
+
+ if baseline:
+ print(
+ colored(
+ f"Baseline analysis report using {baseline.path} - only new failed checks with respect to the baseline are reported",
+ "blue",
+ )
+ )
+
@staticmethod
- def _print_parsing_error_console(file):
- print(colored(f'Error parsing file {file}', 'red'))
+ def _print_parsing_error_console(file: str) -> None:
+ print(colored(f"Error parsing file {file}", "red"))
+
+ def print_junit_xml(self, use_bc_ids: bool = False) -> None:
+ ts = self.get_test_suites(use_bc_ids)
+ xml_string = self.get_junit_xml_string(ts)
+ print(xml_string)
+
+ def get_sarif_json(self) -> Dict[str, Any]:
+ runs = []
+ rules = []
+ results = []
+ for idx, record in enumerate(self.failed_checks):
+ rule = {
+ "id": record.check_id,
+ "name": record.check_name,
+ "shortDescription": {"text": record.check_name},
+ "fullDescription": {"text": record.check_name},
+ "help": {
+ "text": f'"{record.check_name}\nResource: {record.resource}\nGuideline: {record.guideline}"',
+ },
+ "defaultConfiguration": {"level": "error"},
+ }
+ if record.file_line_range[0] == 0:
+ record.file_line_range[0] = 1
+ if record.file_line_range[1] == 0:
+ record.file_line_range[1] = 1
+ result = {
+ "ruleId": record.check_id,
+ "ruleIndex": idx,
+ "level": "error",
+ "message": {"text": record.check_name},
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {"uri": record.file_path},
+ "region": {
+ "startLine": int(record.file_line_range[0]),
+ "endLine": int(record.file_line_range[1]),
+ },
+ }
+ }
+ ],
+ }
+ rules.append(rule)
+ results.append(result)
+
+ runs.append({
+ "tool": {
+ "driver": {
+ "name": "checkov",
+ "version": version,
+ "informationUri": "https://github.com/bridgecrewio/checkov/",
+ "rules": rules,
+ "organization": "bridgecrew",
+ }
+ },
+ "results": results,
+ })
+ sarif_template_report = {
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": runs,
+ }
+ return sarif_template_report
+
+ def print_sarif_report(self) -> None:
+ print(json.dumps(self.get_sarif_json()))
- def print_junit_xml(self):
- ts = self.get_test_suites()
- print(TestSuite.to_xml_string(ts))
+ @staticmethod
+ def get_junit_xml_string(ts: List[TestSuite]) -> str:
+ return to_xml_report_string(ts)
- def print_failed_github_md(self):
+ def print_failed_github_md(self, use_bc_ids=False) -> None:
result = []
for record in self.failed_checks:
- result.append([record.check_id, record.file_path ,record.resource, record.check_name, record.guideline])
- print(tabulate(result, headers=["check_id", "file" ,"resource", "check_name", "guideline"], tablefmt="github", showindex=True))
+ result.append(
+ [
+ record.get_output_id(use_bc_ids),
+ record.file_path,
+ record.resource,
+ record.check_name,
+ record.guideline,
+ ]
+ )
+ print(
+ tabulate(
+ result,
+ headers=["check_id", "file", "resource", "check_name", "guideline"],
+ tablefmt="github",
+ showindex=True,
+ )
+ )
print("\n\n---\n\n")
- def get_test_suites(self):
+ def get_test_suites(self, use_bc_ids=False) -> List[TestSuite]:
test_cases = defaultdict(list)
test_suites = []
records = self.passed_checks + self.failed_checks + self.skipped_checks
for record in records:
- check_name = record.check_name
-
- test_name = "{} {} {}".format(self.check_type, check_name, record.resource)
- test_case = TestCase(name=test_name, file=record.file_path, classname=record.check_class)
- if record.check_result['result'] == CheckResult.FAILED:
- test_case.add_failure_info(
- "Resource \"{}\" failed in check \"{}\"".format(record.resource, check_name))
- if record.check_result['result'] == CheckResult.SKIPPED:
+ check_name = f"{record.get_output_id(use_bc_ids)}/{record.check_name}"
+
+ test_name = f"{self.check_type} {check_name} {record.resource}"
+ test_case = TestCase(
+ name=test_name, file=record.file_path, classname=record.check_class
+ )
+ if record.check_result["result"] == CheckResult.FAILED:
+ if record.file_path and record.file_line_range:
+ test_case.add_failure_info(
+ f"Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range} - Guideline: {record.guideline}"
+ )
+ else:
+ test_case.add_failure_info(
+ f"Resource {record.resource} failed in check {check_name}"
+ )
+ if record.check_result["result"] == CheckResult.SKIPPED:
test_case.add_skipped_info(
- "Resource \"{}\" skipped in check \"{}\"\n Suppress comment: {}".format(record.resource, check_name,
- record.check_result[
- 'suppress_comment']))
+ f'Resource {record.resource} skipped in check {check_name} \n Suppress comment: {record.check_result["suppress_comment"]} - Guideline: {record.guideline}'
+ )
+
test_cases[check_name].append(test_case)
for key in test_cases.keys():
test_suites.append(
- TestSuite(name=key, test_cases=test_cases[key], package=test_cases[key][0].classname))
+ TestSuite(
+ name=key,
+ test_cases=test_cases[key],
+ package=test_cases[key][0].classname,
+ )
+ )
return test_suites
- def print_json(self):
+ def print_json(self) -> None:
print(self.get_json())
+ def _count_resources(self) -> int:
+ unique_resources = set()
+ for record in self.passed_checks + self.failed_checks:
+ unique_resources.add(record.file_path + "." + record.resource)
+ return len(unique_resources)
+
+ @staticmethod
+ def enrich_plan_report(
+ report: "Report", enriched_resources: Dict[str, Dict[str, Any]]
+ ) -> "Report":
+ # This enriches reports with the appropriate filepath, line numbers, and codeblock
+ for record in report.failed_checks:
+ enriched_resource = enriched_resources.get(record.resource)
+ if enriched_resource:
+ record.file_path = enriched_resource["scanned_file"]
+ record.file_line_range = enriched_resource["entity_lines_range"]
+ record.code_block = enriched_resource["entity_code_lines"]
+ return report
+
+ @staticmethod
+ def handle_skipped_checks(
+ report: "Report", enriched_resources: Dict[str, Dict[str, Any]]
+ ) -> "Report":
+ skip_records = []
+ for record in report.failed_checks:
+ resource_skips = enriched_resources.get(record.resource, {}).get(
+ "skipped_checks", []
+ )
+ for skip in resource_skips:
+ if record.check_id in skip["id"]:
+ # Mark for removal and add it as a skipped record. It is not safe to remove
+ # the record from failed_checks immediately because we're iterating over it
+ skip_records.append(record)
+ record.check_result["result"] = CheckResult.SKIPPED
+ record.check_result["suppress_comment"] = skip["suppress_comment"]
+ report.add_record(record)
+
+ for record in skip_records:
+ if record in report.failed_checks:
+ report.failed_checks.remove(record)
+ return report
+
+
+def merge_reports(base_report, report_to_merge):
+ base_report.passed_checks.extend(report_to_merge.passed_checks)
+ base_report.failed_checks.extend(report_to_merge.failed_checks)
+ base_report.skipped_checks.extend(report_to_merge.skipped_checks)
+ base_report.parsing_errors.extend(report_to_merge.parsing_errors)
+
+
+def remove_duplicate_results(report):
+ def dedupe_records(origin_records):
+ record_cache = []
+ new_records = []
+ for record in origin_records:
+ record_hash = record.get_unique_string()
+ if record_hash not in record_cache:
+ new_records.append(record)
+ record_cache.append(record_hash)
+ return new_records
+
+ report.passed_checks = dedupe_records(report.passed_checks)
+ report.failed_checks = dedupe_records(report.failed_checks)
+ return report
diff --git a/checkov/common/runners/base_runner.py b/checkov/common/runners/base_runner.py
index 536d86a553..509d652261 100644
--- a/checkov/common/runners/base_runner.py
+++ b/checkov/common/runners/base_runner.py
@@ -1,20 +1,92 @@
+import itertools
import os
+import re
from abc import ABC, abstractmethod
+from typing import List, Dict, Optional, Any
+from checkov.common.graph.checks_infra.base_check import BaseGraphCheck
+from checkov.common.output.report import Report
from checkov.runner_filter import RunnerFilter
-IGNORED_DIRECTORIES_ENV = os.getenv('CKV_IGNORED_DIRECTORIES', "node_modules,.terraform,.serverless")
+IGNORED_DIRECTORIES_ENV = os.getenv("CKV_IGNORED_DIRECTORIES", "node_modules,.terraform,.serverless")
ignored_directories = IGNORED_DIRECTORIES_ENV.split(",")
class BaseRunner(ABC):
check_type = ""
+ definitions = None
+ context = None
+ breadcrumbs = None
+ external_registries = None
+ graph_manager = None
+ graph_registry = None
@abstractmethod
- def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True):
+ def run(
+ self,
+ root_folder: str,
+ external_checks_dir: Optional[List[str]] = None,
+ files: Optional[List[str]] = None,
+ runner_filter: RunnerFilter = RunnerFilter(),
+ collect_skip_comments: bool = True,
+ ) -> Report:
pass
+ def set_external_data(
+ self,
+ definitions: Optional[Dict[str, Dict[str, Any]]],
+ context: Optional[Dict[str, Dict[str, Any]]],
+ breadcrumbs: Optional[Dict],
+ ):
+ self.definitions = definitions
+ self.context = context
+ self.breadcrumbs = breadcrumbs
-def filter_ignored_directories(d_names):
- [d_names.remove(d) for d in list(d_names) if d in ignored_directories or d.startswith(".")]
+ def load_external_checks(self, external_checks_dir: List[str]):
+ pass
+
+ def get_graph_checks_report(self, root_folder: str, runner_filter: RunnerFilter):
+ pass
+
+ def run_graph_checks_results(self, runner_filter: RunnerFilter) -> Dict[BaseGraphCheck, List[Dict[str, Any]]]:
+ checks_results: Dict[BaseGraphCheck, List[Dict[str, Any]]] = {}
+ for r in itertools.chain(self.external_registries or [], [self.graph_registry]):
+ r.load_checks()
+ registry_results = r.run_checks(self.graph_manager.get_reader_endpoint(), runner_filter)
+ checks_results = {**checks_results, **registry_results}
+ return checks_results
+
+
+def filter_ignored_paths(root_dir: str, names: List[str], excluded_paths: Optional[List[str]]) -> None:
+ # we need to handle legacy logic, where directories to skip could be specified using the env var (default value above)
+ # or a directory starting with '.'; these look only at directory basenames, not relative paths.
+ #
+ # But then any other excluded paths (specified via --skip-path or via the platform repo settings) should look at
+ # the path name relative to the root folder. These can be files or directories.
+ # Example: take the following dir tree:
+ # .
+ # ./dir1
+ # ./dir1/dir33
+ # ./dir1/.terraform
+ # ./dir2
+ # ./dir2/dir33
+ # /.dir2/hello.yaml
+ #
+ # if excluded_paths = ['dir1/dir33', 'dir2/hello.yaml'], then we would scan dir1, but we would skip its subdirectories. We would scan
+ # dir2 and its subdirectory, but we'd skip hello.yaml.
+
+ # first handle the legacy logic - this will also remove files starting with '.' but that's probably fine
+ # mostly this will just remove those problematic directories hardcoded above.
+ for path in list(names):
+ if path in ignored_directories or path.startswith("."):
+ names.remove(path)
+
+ # now apply the new logic
+ # TODO this is not going to work well on Windows, because paths specified in the platform will use /, and
+ # paths specified via the CLI argument will presumably use \\
+ if excluded_paths:
+ compiled = [re.compile(p.replace(".terraform", "\.terraform")) for p in excluded_paths]
+ for path in list(names):
+ if any(pattern.search(os.path.join(root_dir, path)) for pattern in compiled):
+ names.remove(path)
diff --git a/checkov/common/runners/runner_registry.py b/checkov/common/runners/runner_registry.py
index 5f2b732407..77d5e35b41 100644
--- a/checkov/common/runners/runner_registry.py
+++ b/checkov/common/runners/runner_registry.py
@@ -1,12 +1,21 @@
import json
import logging
+import os
from abc import abstractmethod
+from typing import List
+from checkov.common.bridgecrew.integration_features.integration_feature_registry import (
+ integration_feature_registry,
+)
from checkov.common.output.report import Report
+from checkov.common.util import data_structures_utils
+from checkov.terraform.context_parsers.registry import parser_registry
+from checkov.terraform.runner import Runner as tf_runner
+from checkov.terraform.parser import Parser
-OUTPUT_CHOICES = ['cli', 'json', 'junitxml', 'github_failed_only']
-from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
+CHECK_BLOCK_TYPES = frozenset(["resource", "data", "provider", "module"])
+OUTPUT_CHOICES = ["cli", "json", "junitxml", "github_failed_only", "sarif"]
class RunnerRegistry(object):
@@ -17,76 +26,175 @@ class RunnerRegistry(object):
def __init__(self, banner, runner_filter, *runners):
self.logger = logging.getLogger(__name__)
self.runner_filter = runner_filter
- self.runners = runners
+ self.runners = list(runners)
self.banner = banner
self.scan_reports = []
self.filter_runner_framework()
- self.bc_platform = BcPlatformIntegration()
@abstractmethod
def extract_entity_details(self, entity):
raise NotImplementedError()
- def run(self, root_folder=None, external_checks_dir=None, files=None, guidelines=None, collect_skip_comments=True):
+ def run(
+ self,
+ root_folder=None,
+ external_checks_dir=None,
+ files=None,
+ guidelines=None,
+ collect_skip_comments=True,
+ repo_root_for_plan_enrichment=None,
+ ) -> List[Report]:
for runner in self.runners:
- scan_report = runner.run(root_folder, external_checks_dir=external_checks_dir, files=files,
- runner_filter=self.runner_filter, collect_skip_comments=collect_skip_comments)
+ integration_feature_registry.run_pre_runner()
+ scan_report = runner.run(
+ root_folder,
+ external_checks_dir=external_checks_dir,
+ files=files,
+ runner_filter=self.runner_filter,
+ collect_skip_comments=collect_skip_comments,
+ )
+ integration_feature_registry.run_post_runner(scan_report)
if guidelines:
RunnerRegistry.enrich_report_with_guidelines(scan_report, guidelines)
+ if repo_root_for_plan_enrichment:
+ enriched_resources = RunnerRegistry.get_enriched_resources(
+ repo_root_for_plan_enrichment
+ )
+ scan_report = Report("terraform_plan").enrich_plan_report(
+ scan_report, enriched_resources
+ )
+ scan_report = Report("terraform_plan").handle_skipped_checks(
+ scan_report, enriched_resources
+ )
self.scan_reports.append(scan_report)
return self.scan_reports
- def print_reports(self, scan_reports, args):
- if args.output == 'cli':
+ def print_reports(
+ self, scan_reports, config, url=None, created_baseline_path=None, baseline=None
+ ):
+ if config.output == "cli":
print(f"{self.banner}\n")
exit_codes = []
report_jsons = []
+ sarif_reports = []
junit_reports = []
for report in scan_reports:
if not report.is_empty():
- if args.output == "json":
- report_jsons.append(report.get_dict())
- elif args.output == "junitxml":
+ if config.output == "json":
+ report_jsons.append(report.get_dict(is_quiet=config.quiet))
+ elif config.output == "junitxml":
junit_reports.append(report)
# report.print_junit_xml()
- elif args.output == 'github_failed_only':
- report.print_failed_github_md()
+ elif config.output == "github_failed_only":
+ report.print_failed_github_md(use_bc_ids=config.output_bc_ids)
+ elif config.output == "sarif":
+ sarif_reports.append(report)
else:
- report.print_console(is_quiet=args.quiet)
- exit_codes.append(report.get_exit_code(args.soft_fail))
- if args.output == "junitxml":
+ report.print_console(
+ is_quiet=config.quiet,
+ is_compact=config.compact,
+ created_baseline_path=created_baseline_path,
+ baseline=baseline,
+ use_bc_ids=config.output_bc_ids,
+ )
+ if url:
+ print("More details: {}".format(url))
+ exit_codes.append(
+ report.get_exit_code(
+ config.soft_fail, config.soft_fail_on, config.hard_fail_on
+ )
+ )
+ if config.output == "junitxml":
if len(junit_reports) == 1:
- junit_reports[0].print_junit_xml()
+ junit_reports[0].print_junit_xml(use_bc_ids=config.output_bc_ids)
else:
master_report = Report(None)
for report in junit_reports:
master_report.skipped_checks += report.skipped_checks
master_report.passed_checks += report.passed_checks
master_report.failed_checks += report.failed_checks
- master_report.print_junit_xml()
- if args.output == "json":
+ master_report.print_junit_xml(use_bc_ids=config.output_bc_ids)
+ if config.output == "sarif":
+ master_report = Report(None)
+ for report in sarif_reports:
+ master_report.failed_checks += report.failed_checks
+ master_report.print_sarif_report()
+ if config.output == "json":
if len(report_jsons) == 1:
print(json.dumps(report_jsons[0], indent=4))
else:
print(json.dumps(report_jsons, indent=4))
- if args.output == "cli":
- self.bc_platform.get_report_to_platform(args,scan_reports)
+ # if config.output == "cli":
+ # bc_integration.get_report_to_platform(config,scan_reports)
exit_code = 1 if 1 in exit_codes else 0
- exit(exit_code)
+ return exit_code
def filter_runner_framework(self):
if not self.runner_filter:
return
- if self.runner_filter.framework == 'all':
+ if self.runner_filter.framework is None:
return
+ if self.runner_filter.framework == "all":
+ return
+ filtered_runners = []
for runner in self.runners:
- if runner.check_type == self.runner_filter.framework:
- self.runners = [runner]
- return
+ if runner.check_type in self.runner_filter.framework:
+ filtered_runners.append(runner)
+ self.runners = filtered_runners
+ return
+
+ def remove_runner(self, runner):
+ if runner in self.runners:
+ self.runners.remove(runner)
@staticmethod
def enrich_report_with_guidelines(scan_report, guidelines):
- for record in scan_report.failed_checks + scan_report.passed_checks + scan_report.skipped_checks:
+ for record in (
+ scan_report.failed_checks
+ + scan_report.passed_checks
+ + scan_report.skipped_checks
+ ):
if record.check_id in guidelines:
record.set_guideline(guidelines[record.check_id])
+
+ @staticmethod
+ def get_enriched_resources(repo_root):
+ tf_definitions = {}
+ parsing_errors = {}
+ Parser().parse_directory(
+ directory=repo_root, # assume plan file is in the repo-root
+ out_definitions=tf_definitions,
+ out_parsing_errors=parsing_errors,
+ )
+
+ enriched_resources = {}
+ for full_file_path, definition in tf_definitions.items():
+ definitions_context = parser_registry.enrich_definitions_context(
+ (full_file_path, definition)
+ )
+ abs_scanned_file, _ = tf_runner._strip_module_referrer(full_file_path)
+ scanned_file = os.path.relpath(abs_scanned_file, repo_root)
+ for block_type, block_value in definition.items():
+ if block_type in CHECK_BLOCK_TYPES:
+ for entity in block_value:
+ context_parser = parser_registry.context_parsers[block_type]
+ definition_path = context_parser.get_entity_context_path(entity)
+ entity_id = ".".join(definition_path)
+ entity_context_path = [block_type] + definition_path
+ entity_context = data_structures_utils.get_inner_dict(
+ definitions_context[full_file_path], entity_context_path
+ )
+ entity_lines_range = [
+ entity_context.get("start_line"),
+ entity_context.get("end_line"),
+ ]
+ entity_code_lines = entity_context.get("code_lines")
+ skipped_checks = entity_context.get("skipped_checks")
+ enriched_resources[entity_id] = {
+ "entity_code_lines": entity_code_lines,
+ "entity_lines_range": entity_lines_range,
+ "scanned_file": scanned_file,
+ "skipped_checks": skipped_checks,
+ }
+ return enriched_resources
diff --git a/checkov/common/typing.py b/checkov/common/typing.py
new file mode 100644
index 0000000000..86f00b1ee9
--- /dev/null
+++ b/checkov/common/typing.py
@@ -0,0 +1,9 @@
+from typing import Optional
+
+from typing_extensions import TypedDict
+
+
+class _SkippedCheck(TypedDict, total=False):
+ bc_id: Optional[str]
+ id: str
+ suppress_comment: str
diff --git a/checkov/common/util/banner.py b/checkov/common/util/banner.py
index 9a3f7db919..a4638c40db 100644
--- a/checkov/common/util/banner.py
+++ b/checkov/common/util/banner.py
@@ -14,5 +14,5 @@
new_version = check_for_update("checkov", version)
if new_version:
- banner = "\n" + banner + "\nUpdate available " + colored(version,"grey",attrs=["blink"]) + " → " + colored(new_version, "green") + "\nRun " + colored(
+ banner = "\n" + banner + "\nUpdate available " + colored(version,"grey") + " -> " + colored(new_version, "green") + "\nRun " + colored(
"pip3 install -U checkov", "magenta") + " to update \n"
diff --git a/checkov/common/util/config_utils.py b/checkov/common/util/config_utils.py
new file mode 100644
index 0000000000..83d6fd617f
--- /dev/null
+++ b/checkov/common/util/config_utils.py
@@ -0,0 +1,24 @@
+import os
+
+from pathlib import Path
+
+
+def config_file_paths(dir_path):
+ return [os.path.join(dir_path, '.checkov.yaml'), os.path.join(dir_path, '.checkov.yml')]
+
+
+def get_default_config_paths(argv):
+ """
+ Checkov looks for .checkov.yml or .checkov.yaml file in the directory (--directory) against which it is run.
+ If that does not have the config file, the current working directory is checked followed by checking the user's
+ home directory is searched.
+ :param argv: List of CLI args from sys.argv.
+ :return: List of default config file paths.
+ """
+ home_paths = config_file_paths(Path.home())
+ cwd_path = config_file_paths(Path.cwd())
+ dir_paths = []
+ for i, v in enumerate(argv):
+ if v in ['-d', '--directory']:
+ dir_paths += config_file_paths(argv[i + 1])
+ return dir_paths + cwd_path + home_paths
diff --git a/checkov/common/util/consts.py b/checkov/common/util/consts.py
index c6a50f0ddd..bb855d9ab1 100644
--- a/checkov/common/util/consts.py
+++ b/checkov/common/util/consts.py
@@ -1,2 +1,11 @@
DEFAULT_EXTERNAL_MODULES_DIR = ".external_modules"
RESOLVED_MODULE_ENTRY_NAME = "__resolved__"
+
+DEV_API_GET_HEADERS = {
+ 'Accept': 'application/json'
+}
+
+DEV_API_POST_HEADERS = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+}
diff --git a/checkov/common/util/data_structures_utils.py b/checkov/common/util/data_structures_utils.py
new file mode 100644
index 0000000000..d8fba32585
--- /dev/null
+++ b/checkov/common/util/data_structures_utils.py
@@ -0,0 +1,31 @@
+from typing import Generator, Any, Union
+
+
+def get_inner_dict(source_dict, path_as_list):
+ result = source_dict
+ for index in path_as_list:
+ result = result[index]
+ return result
+
+
+def merge_dicts(*dicts):
+ """
+ Merges two or more dicts. If there are duplicate keys, later dict arguments take precedence.
+
+ Null, empty, or non-dict arguments are qiuetly skipped.
+ :param dicts:
+ :return:
+ """
+ res = {}
+ for d in dicts:
+ if not d or type(d) != dict:
+ continue
+ res = {**res, **d}
+ return res
+
+
+def generator_reader_wrapper(g: Generator) -> Union[None, Any]:
+ try:
+ return next(g)
+ except StopIteration:
+ return
diff --git a/checkov/common/util/dict_utils.py b/checkov/common/util/dict_utils.py
deleted file mode 100644
index 049d0948f8..0000000000
--- a/checkov/common/util/dict_utils.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def getInnerDict(source_dict, path_as_list):
- result = source_dict
- for index in path_as_list:
- result = result[index]
- return result
\ No newline at end of file
diff --git a/checkov/common/util/docs_generator.py b/checkov/common/util/docs_generator.py
index 7e3397c88a..3cc375ccba 100644
--- a/checkov/common/util/docs_generator.py
+++ b/checkov/common/util/docs_generator.py
@@ -1,62 +1,79 @@
#!/usr/bin/env python
import re
-
from tabulate import tabulate
-from checkov.arm.registry import arm_registry
+from checkov.arm.registry import arm_resource_registry, arm_parameter_registry
from checkov.cloudformation.checks.resource.registry import cfn_registry as cfn_registry
from checkov.common.checks.base_check_registry import BaseCheckRegistry
+from checkov.common.checks_infra.registry import BaseRegistry as BaseGraphRegistry, get_graph_checks_registry
+from checkov.dockerfile.registry import registry as dockerfile_registry
from checkov.kubernetes.registry import registry as k8_registry
+from checkov.secrets.runner import CHECK_ID_TO_SECRET_TYPE
from checkov.serverless.registry import sls_registry
from checkov.terraform.checks.data.registry import data_registry
from checkov.terraform.checks.module.registry import module_registry
from checkov.terraform.checks.provider.registry import provider_registry
from checkov.terraform.checks.resource.registry import resource_registry
-ID_PARTS_PATTERN = re.compile(r'(\D*)(\d*)')
+ID_PARTS_PATTERN = re.compile(r'([^_]*)_([^_]*)_(\d+)')
def get_compare_key(c):
res = []
for match in ID_PARTS_PATTERN.finditer(c[0]):
- text, number = match.groups()
+ ckv, framework, number = match.groups()
numeric_value = int(number) if number else 0
# count number of leading zeros
same_number_ordering = len(number) - len(number.lstrip('0'))
- res.append((text, numeric_value, same_number_ordering))
+ res.append((framework, ckv, numeric_value, same_number_ordering))
return res
-def print_checks(framework="all"):
- printable_checks_list = get_checks(framework)
+def print_checks(framework="all", use_bc_ids=False):
+ printable_checks_list = get_checks(framework, use_bc_ids=use_bc_ids)
print(
tabulate(printable_checks_list, headers=["Id", "Type", "Entity", "Policy", "IaC"], tablefmt="github",
showindex=True))
print("\n\n---\n\n")
-def get_checks(framework="all"):
+def get_checks(framework="all", use_bc_ids=False):
printable_checks_list = []
- def add_from_repository(registry: BaseCheckRegistry, checked_type: str, iac: str):
+ def add_from_repository(registry, checked_type: str, iac: str):
nonlocal printable_checks_list
- for entity, check in registry.all_checks():
- printable_checks_list.append([check.id, checked_type, entity, check.name, iac])
+ if isinstance(registry, BaseCheckRegistry):
+ for entity, check in registry.all_checks():
+ printable_checks_list.append([check.get_output_id(use_bc_ids), checked_type, entity, check.name, iac])
+ elif isinstance(registry, BaseGraphRegistry):
+ for check in registry.checks:
+ for rt in check.resource_types:
+ printable_checks_list.append([check.get_output_id(use_bc_ids), checked_type, rt, check.name, iac])
if framework == "terraform" or framework == "all":
add_from_repository(resource_registry, "resource", "Terraform")
add_from_repository(data_registry, "data", "Terraform")
add_from_repository(provider_registry, "provider", "Terraform")
add_from_repository(module_registry, "module", "Terraform")
+
+ graph_registry = get_graph_checks_registry("terraform")
+ graph_registry.load_checks()
+ add_from_repository(graph_registry, "resource", "Terraform")
if framework == "cloudformation" or framework == "all":
add_from_repository(cfn_registry, "resource", "Cloudformation")
if framework == "kubernetes" or framework == "all":
- add_from_repository(k8_registry, "PodSecurityPolicy", "Kubernetes")
+ add_from_repository(k8_registry, "resource", "Kubernetes")
if framework == "serverless" or framework == "all":
add_from_repository(sls_registry, "resource", "serverless")
+ if framework == "dockerfile" or framework == "all":
+ add_from_repository(dockerfile_registry, "dockerfile", "dockerfile")
if framework == "arm" or framework == "all":
- add_from_repository(arm_registry, "resource", "arm")
+ add_from_repository(arm_resource_registry, "resource", "arm")
+ add_from_repository(arm_parameter_registry, "parameter", "arm")
+ if framework == "secrets" or framework == "all":
+ for check_id, check_type in CHECK_ID_TO_SECRET_TYPE.items():
+ printable_checks_list.append((check_id, check_type, "secrets", check_type, check_type, "secrets"))
return sorted(printable_checks_list, key=get_compare_key)
diff --git a/checkov/common/util/ext_argument_parser.py b/checkov/common/util/ext_argument_parser.py
new file mode 100644
index 0000000000..70f0837975
--- /dev/null
+++ b/checkov/common/util/ext_argument_parser.py
@@ -0,0 +1,51 @@
+import configargparse
+
+from checkov.common.util.type_forcers import convert_str_to_bool
+
+
+class ExtArgumentParser(configargparse.ArgumentParser):
+
+ def write_config_file(self, parsed_namespace, output_file_paths, exit_after=False):
+ """
+ Write the given settings to output files. Overrides write_config_file from the class ArgumentParser for
+ correcting types of some attributes (example: check, skip_check)
+
+ :param parsed_namespace: namespace object created within parse_known_args()
+ :param output_file_paths: any number of file paths to write the config to
+ :param exit_after: whether to exit the program after writing the config files
+ """
+ for output_file_path in output_file_paths:
+ # validate the output file path
+ try:
+ with self._config_file_open_func(output_file_path, "w") as output_file:
+ pass
+ except IOError as e:
+ raise ValueError("Couldn't open {} for writing: {}".format(
+ output_file_path, e))
+ if output_file_paths:
+ # generate the config file contents
+ config_items = self.get_items_for_config_file_output(
+ self._source_to_settings, parsed_namespace)
+ # convert check, skip_check, soft_fail_on and hard_fail_on to list
+ if 'check' in config_items.keys():
+ config_items['check'] = config_items['check'][0].split(",")
+ if 'skip-check' in config_items.keys():
+ config_items['skip-check'] = config_items['skip-check'][0].split(",")
+ if 'soft-fail-on' in config_items.keys():
+ config_items['soft-fail-on'] = config_items['soft-fail-on'][0].split(",")
+ if 'hard-fail-on' in config_items.keys():
+ config_items['hard-fail-on'] = config_items['hard-fail-on'][0].split(",")
+ # convert strings to booleans
+ for k in config_items.keys():
+ config_items[k] = convert_str_to_bool(config_items[k])
+
+ file_contents = self._config_file_parser.serialize(config_items)
+ for output_file_path in output_file_paths:
+ with self._config_file_open_func(output_file_path, "w") as output_file:
+ output_file.write(file_contents)
+ message = "Wrote config file to " + ", ".join(output_file_paths)
+ if exit_after:
+ self.exit(0, message)
+ else:
+ print(message)
+
diff --git a/checkov/common/util/http_utils.py b/checkov/common/util/http_utils.py
new file mode 100644
index 0000000000..8782361bac
--- /dev/null
+++ b/checkov/common/util/http_utils.py
@@ -0,0 +1,44 @@
+import json
+import requests
+import logging
+
+from checkov.common.bridgecrew.bc_source import SourceType
+from checkov.common.util.consts import DEV_API_GET_HEADERS, DEV_API_POST_HEADERS
+from checkov.common.util.data_structures_utils import merge_dicts
+from checkov.version import version as checkov_version
+
+logger = logging.getLogger(__name__)
+
+
+def extract_error_message(response: requests.Response):
+ if response.content:
+ try:
+ content = json.loads(response.content)
+ if 'message' in content:
+ return content['message']
+ except:
+ logging.debug(f'Failed to parse the response content: {response.content}')
+
+ return response.reason
+
+
+def get_auth_header(token):
+ return {
+ 'Authorization': token
+ }
+
+
+def get_version_headers(client, client_version):
+ return {
+ 'x-api-client': client,
+ 'x-api-version': client_version,
+ 'x-api-checkov-version': checkov_version
+ }
+
+
+def get_default_get_headers(client: SourceType, client_version: str):
+ return merge_dicts(DEV_API_GET_HEADERS, get_version_headers(client.name, client_version))
+
+
+def get_default_post_headers(client: SourceType, client_version: str):
+ return merge_dicts(DEV_API_POST_HEADERS, get_version_headers(client.name, client_version))
\ No newline at end of file
diff --git a/checkov/common/util/json_utils.py b/checkov/common/util/json_utils.py
new file mode 100644
index 0000000000..453319a6d4
--- /dev/null
+++ b/checkov/common/util/json_utils.py
@@ -0,0 +1,9 @@
+import json
+
+
+class CustomJSONEncoder(json.JSONEncoder):
+ def default(self, o):
+ if isinstance(o, set):
+ return list(o)
+ else:
+ return json.JSONEncoder.default(self, o)
diff --git a/checkov/common/util/runner_dependency_handler.py b/checkov/common/util/runner_dependency_handler.py
new file mode 100644
index 0000000000..6e28cb1d1e
--- /dev/null
+++ b/checkov/common/util/runner_dependency_handler.py
@@ -0,0 +1,46 @@
+import logging
+
+from checkov.common.runners.runner_registry import RunnerRegistry
+
+logger = logging.getLogger(__name__)
+
+
+class RunnerDependencyHandler():
+ """
+ Scan runners for system dependencies, disable runners with failed deps
+ """
+ def __init__(self, runner_registry: RunnerRegistry):
+ """
+ RunnerDependencyHandler
+ :param runner_registry: a populated runner registry
+ """
+ self.runner_registry = runner_registry
+
+ def validate_runner_deps(self):
+ """
+ Checks if each runner declares any system dependancies by calling each runner's system_deps() function.
+ This function can safley not exist, but if returns true, call check_system_deps() on the same function.
+ The function would impliment it's own dependancy checks (see helm/runner.py for example).
+ Sucessful check_system_deps() should return None, otherwise self.check_type to indicate a runner has failed deps.
+
+ THen removes any runners with missing dependencies from runner_registry.
+ """
+ runners_with_unmatched_deps = []
+ runner_names = []
+ for runner in self.runner_registry.runners:
+ try:
+ runner.system_deps
+ except:
+ logging.debug(f"{runner.check_type}_runner declares no system dependency checks required.")
+ continue
+
+ if runner.system_deps:
+ result = runner.check_system_deps()
+ if result is not None:
+ runner_names.append(result)
+ runners_with_unmatched_deps.append(runner)
+
+ if runners_with_unmatched_deps:
+ logging.info(f"The following frameworks will automatically be disabled due to missing system dependencies: {','.join(runner_names)}")
+ for runner in runners_with_unmatched_deps:
+ self.runner_registry.remove_runner(runner)
diff --git a/checkov/common/util/secrets.py b/checkov/common/util/secrets.py
index af177902e6..5f02c4a494 100644
--- a/checkov/common/util/secrets.py
+++ b/checkov/common/util/secrets.py
@@ -15,9 +15,8 @@
# https://github.com/msalemcode/git-secrets#options-for-register-azure
_secrets_regexes = {
'azure': [
- r'''("|')?[A-Z0-9a-z!"\#$%&'()*+,\-./:;<=>?@\[\\\]^_‘{|}~]{32}("|')?$''',
- r'''("|')?[A-Z0-9a-z!"\#$%&'()*+,\-./:;<=>?@\[\\\]^_‘{|}~]{88}("|')?$''',
- "(\"|')?[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}(\"|')?",
+ "(\"|')?([0-9A-Fa-f]{4}-){4}[0-9A-Fa-f]{12}(\"|')?", # client_secret
+ "(\"|')?[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}(\"|')?", # client_id and many other forms of IDs
"(\"|')?.*[0-9a-zA-Z]{2,256}[.][o|O][n|N][m|M][i|I][c|C][r|R][o|O][s|S][o|O][f|F][t|T][.][c|C][o|O][m|M](\"|')?",
"(\"|')?.*[0-9a-zA-Z]{2,256}[.][b|B][l|L][o|O][b|B][.][c|C][o|O][r|R][e|E][.][w|W][i|I][n|N][d|D][o|O][w|W][s|S][.][n|N][e|E][t|T](\"|')?",
"(\"|')?.*[0-9a-zA-Z]{2,256}[.][q|Q][u|U][e|E][u|U][e|E][.][c|C][o|O][r|R][e|E][.][w|W][i|I][n|N][d|D][o|O][w|W][s|S][.][n|N][e|E][t|T](\"|')?",
diff --git a/checkov/common/util/type_forcers.py b/checkov/common/util/type_forcers.py
index e861073d85..efc9e98fac 100644
--- a/checkov/common/util/type_forcers.py
+++ b/checkov/common/util/type_forcers.py
@@ -1,22 +1,116 @@
-def force_list (var):
+import json
+import logging
+from json import JSONDecodeError
+from typing import TypeVar, List, overload, Union, Optional, Any, Dict
+
+import yaml
+
+T = TypeVar("T")
+
+
+@overload
+def force_list(var: List[T]) -> List[T]:
+ ...
+
+
+@overload
+def force_list(var: T) -> List[T]:
+ ...
+
+
+def force_list(var: Union[T, List[T]]) -> List[T]:
if not isinstance(var, list):
- var = [var]
+ return [var]
return var
-def force_int (var):
+def force_int(var: Any) -> Optional[int]:
try:
if not isinstance(var, int):
- var = int(var)
+ return int(var)
return var
- except:
+ except Exception:
return None
-def convert_str_to_bool(bool_str):
- if bool_str in ['true', '"true"', 'True', '"True"']:
+def force_float(var: Any) -> Optional[float]:
+ try:
+ if not isinstance(var, float):
+ return float(var)
+ return var
+ except Exception:
+ return None
+
+
+def convert_str_to_bool(bool_str: Union[bool, str]) -> Union[bool, str]:
+ if bool_str in ["true", '"true"', "True", '"True"']:
return True
- elif bool_str in ['false', '"false"', 'False', '"False"']:
+ elif bool_str in ["false", '"false"', "False", '"False"']:
return False
else:
return bool_str
+
+
+def force_dict(obj: Any) -> Optional[Dict[str, Any]]:
+ """
+ If the specified object is a dict, returns the object. If the object is a list of length 1 or more, and the first
+ element is a dict, returns the first element. Else returns None.
+ :param obj:
+ :return:
+ """
+ if isinstance(obj, dict):
+ return obj
+ if isinstance(obj, list) and len(obj) > 0 and isinstance(obj[0], dict):
+ return obj[0]
+ return None
+
+
+def is_json(data: str) -> bool:
+ try:
+ parsed = json.loads(data)
+ return isinstance(parsed, dict)
+ except (TypeError, ValueError):
+ logging.debug(f"could not parse json data: {data}")
+ return False
+
+
+def is_yaml(data: str) -> bool:
+ try:
+ parsed = yaml.safe_load(data)
+ return isinstance(parsed, dict)
+ except yaml.YAMLError:
+ logging.debug(f"could not parse yaml data: {data}")
+ return False
+
+
+def extract_policy_dict(policy: Union[dict, str]) -> Optional[dict]:
+ if isinstance(policy, dict):
+ return policy
+ if isinstance(policy, str):
+ try:
+ policy_dict = json.loads(policy)
+ return policy_dict
+ except JSONDecodeError:
+ return None
+
+ return None
+
+
+def convert_csv_string_arg_to_list(csv_string_arg: Union[List[str], str, None]) -> list:
+ """
+ Converts list type arguments that also support comma delimited strings into a list.
+ For instance the --check flag in the CLI:
+ checkov -c CKV_1,CKV2
+ will translate to ['CKV_1', 'CKV_2']
+ :param csv_string_arg: Comma delimited string
+ :return: List of strings or empty list
+ """
+ if csv_string_arg is None:
+ return []
+ if isinstance(csv_string_arg, str):
+ return csv_string_arg.split(',')
+ elif isinstance(csv_string_arg, list) and len(csv_string_arg) == 1:
+ return csv_string_arg[0].split(',')
+ else:
+ return csv_string_arg
+
diff --git a/checkov/dockerfile/__init__.py b/checkov/dockerfile/__init__.py
new file mode 100644
index 0000000000..370f9cd890
--- /dev/null
+++ b/checkov/dockerfile/__init__.py
@@ -0,0 +1 @@
+from checkov.dockerfile.checks import *
\ No newline at end of file
diff --git a/checkov/dockerfile/base_dockerfile_check.py b/checkov/dockerfile/base_dockerfile_check.py
new file mode 100644
index 0000000000..a116468c3d
--- /dev/null
+++ b/checkov/dockerfile/base_dockerfile_check.py
@@ -0,0 +1,11 @@
+from checkov.common.checks.base_check import BaseCheck
+
+from checkov.dockerfile.registry import registry
+
+class BaseDockerfileCheck(BaseCheck):
+ def __init__(self, name, id, categories, supported_instructions):
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_instructions,
+ block_type="dockerfile")
+ self.supported_instructions = supported_instructions
+ registry.register(self)
+
diff --git a/checkov/dockerfile/base_registry.py b/checkov/dockerfile/base_registry.py
new file mode 100644
index 0000000000..f592adff1f
--- /dev/null
+++ b/checkov/dockerfile/base_registry.py
@@ -0,0 +1,49 @@
+from checkov.common.checks.base_check_registry import BaseCheckRegistry
+from checkov.common.models.enums import CheckResult
+
+
+class Registry(BaseCheckRegistry):
+ def scan(self, scanned_file, entity, skipped_checks, runner_filter):
+
+ results = {}
+ if not entity:
+ return results
+ for instruction, checks in self.checks.items():
+ skip_info = {}
+ if instruction in entity:
+
+ for check in checks:
+ if check.id in [x['id'] for x in skipped_checks]:
+ skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
+
+ if runner_filter.should_run_check(check.id, check.bc_id):
+ entity_name = instruction
+ entity_type = instruction
+ entity_configuration = entity[instruction]
+ self.update_result(check, entity_configuration, entity_name, entity_type, results, scanned_file,
+ skip_info)
+
+ for check in self.wildcard_checks["*"]:
+ if skipped_checks:
+ if check.id in [x['id'] for x in skipped_checks]:
+ skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
+
+ if runner_filter.should_run_check(check.id, check.bc_id):
+ entity_name = scanned_file
+ entity_type = "*"
+ entity_configuration = entity
+ self.update_result(check, entity_configuration, entity_name, entity_type, results, scanned_file,
+ skip_info)
+ return results
+
+ def update_result(self, check, entity_configuration, entity_name, entity_type, results, scanned_file, skip_info):
+ result = self.run_check(check, entity_configuration, entity_name, entity_type, scanned_file,
+ skip_info)
+ results[check] = {}
+ if result['result'] == CheckResult.SKIPPED:
+ results[check]['result'] = result['result']
+ results[check]['suppress_comment'] = result['suppress_comment']
+ results[check]['results_configuration'] = None
+ else:
+ results[check]['result'] = result['result'][0]
+ results[check]['results_configuration'] = result['result'][1]
diff --git a/checkov/dockerfile/checks/AddExists.py b/checkov/dockerfile/checks/AddExists.py
new file mode 100644
index 0000000000..53d668387e
--- /dev/null
+++ b/checkov/dockerfile/checks/AddExists.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class AddExists(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure that COPY is used instead of ADD in Dockerfiles"
+ id = "CKV_DOCKER_4"
+ supported_instructions = ["ADD"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ i=0
+ for instruction in conf:
+ if instruction['instruction'] == "ADD":
+ return CheckResult.FAILED, conf[i]
+ return CheckResult.PASSED,None
+
+
+check = AddExists()
diff --git a/checkov/dockerfile/checks/ExposePort22.py b/checkov/dockerfile/checks/ExposePort22.py
new file mode 100644
index 0000000000..07825db1e2
--- /dev/null
+++ b/checkov/dockerfile/checks/ExposePort22.py
@@ -0,0 +1,22 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class ExposePort22(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure port 22 is not exposed"
+ id = "CKV_DOCKER_1"
+ supported_instructions = ['EXPOSE']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ i=0
+ for expose_term in conf:
+ if "22" in expose_term['value'].split(' '):
+ return CheckResult.FAILED, conf[i]
+ i=i+1
+ return CheckResult.PASSED , None
+
+
+check = ExposePort22()
diff --git a/checkov/dockerfile/checks/HealthcheckExists.py b/checkov/dockerfile/checks/HealthcheckExists.py
new file mode 100644
index 0000000000..0798ea9cd3
--- /dev/null
+++ b/checkov/dockerfile/checks/HealthcheckExists.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class HealthcheckExists(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure that HEALTHCHECK instructions have been added to container images "
+ id = "CKV_DOCKER_2"
+ supported_instructions = ["*"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ for instruction, content in conf.items():
+ if instruction == "HEALTHCHECK":
+ return CheckResult.PASSED, conf[instruction][0]
+ return CheckResult.FAILED, None
+
+
+check = HealthcheckExists()
diff --git a/checkov/dockerfile/checks/MaintainerExists.py b/checkov/dockerfile/checks/MaintainerExists.py
new file mode 100644
index 0000000000..4114ea210b
--- /dev/null
+++ b/checkov/dockerfile/checks/MaintainerExists.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class MaintainerExists(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure that LABEL maintainer is used instead of MAINTAINER (deprecated)"
+ id = "CKV_DOCKER_6"
+ supported_instructions = ["MAINTAINER"]
+ categories = [CheckCategories.CONVENTION]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ return CheckResult.FAILED, conf[0]
+
+
+check = MaintainerExists()
diff --git a/checkov/dockerfile/checks/ReferenceLatestTag.py b/checkov/dockerfile/checks/ReferenceLatestTag.py
new file mode 100644
index 0000000000..6db347b368
--- /dev/null
+++ b/checkov/dockerfile/checks/ReferenceLatestTag.py
@@ -0,0 +1,34 @@
+import re
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+MULTI_STAGE_PATTERN = re.compile(r"(\S+)\s+as\s+(\S+)")
+
+
+class ReferenceLatestTag(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure the base image uses a non latest version tag"
+ id = "CKV_DOCKER_7"
+ supported_instructions = ["FROM"]
+ categories = [CheckCategories.CONVENTION]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ stages = []
+
+ for content in conf:
+ base_image = content["value"]
+ multi_stage = re.match(MULTI_STAGE_PATTERN, base_image)
+ if multi_stage:
+ base_image = multi_stage[1]
+ stages.append(multi_stage[2])
+
+ if ":" not in base_image and base_image not in stages:
+ return CheckResult.FAILED, content
+ elif base_image.endswith(":latest"):
+ return CheckResult.FAILED, content
+ return CheckResult.PASSED, None
+
+
+check = ReferenceLatestTag()
diff --git a/checkov/dockerfile/checks/RootUser.py b/checkov/dockerfile/checks/RootUser.py
new file mode 100644
index 0000000000..8989d2c4ec
--- /dev/null
+++ b/checkov/dockerfile/checks/RootUser.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class RootUser(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure the last USER is not root"
+ id = "CKV_DOCKER_8"
+ supported_instructions = ["USER"]
+ categories = [CheckCategories.APPLICATION_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ last_user = conf[-1]
+ if last_user["value"] == "root":
+ return CheckResult.FAILED, last_user
+
+ return CheckResult.PASSED, last_user
+
+
+check = RootUser()
diff --git a/checkov/dockerfile/checks/UpdateNotAlone.py b/checkov/dockerfile/checks/UpdateNotAlone.py
new file mode 100644
index 0000000000..769bd807e2
--- /dev/null
+++ b/checkov/dockerfile/checks/UpdateNotAlone.py
@@ -0,0 +1,46 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+install_commands = [
+ "install",
+ "source-install",
+ "reinstall",
+ "groupinstall",
+ "localinstall",
+ "add",
+]
+update_commands = [
+ "update",
+ "--update"
+]
+
+
+class UpdateNotAlone(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure update instructions are not use alone in the Dockerfile"
+ id = "CKV_DOCKER_5"
+ supported_instructions = ["RUN"]
+ categories = [CheckCategories.APPLICATION_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ update_instruction = 0
+ update_cnt = 0
+ i = 0
+ for instruction in conf:
+ content = instruction['content']
+ if instruction['instruction'] in self.supported_instructions:
+
+ if any(x in content for x in update_commands):
+ update_cnt = update_cnt + 1
+ update_instruction = i
+ if any(x in content for x in install_commands):
+ update_cnt = update_cnt - 1
+ i = i + 1
+
+ if update_cnt <= 0:
+ return CheckResult.PASSED, None
+ return CheckResult.FAILED, conf[update_instruction]
+
+
+check = UpdateNotAlone()
diff --git a/checkov/dockerfile/checks/UserExists.py b/checkov/dockerfile/checks/UserExists.py
new file mode 100644
index 0000000000..11d3404e67
--- /dev/null
+++ b/checkov/dockerfile/checks/UserExists.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
+
+
+class UserExists(BaseDockerfileCheck):
+ def __init__(self):
+ name = "Ensure that a user for the container has been created"
+ id = "CKV_DOCKER_3"
+ supported_instructions = ["*"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_instructions=supported_instructions)
+
+ def scan_entity_conf(self, conf):
+ for instruction, content in conf.items():
+ if instruction == "USER":
+ return CheckResult.PASSED, conf[instruction][0]
+ return CheckResult.FAILED, None
+
+
+check = UserExists()
diff --git a/checkov/dockerfile/checks/__init__.py b/checkov/dockerfile/checks/__init__.py
new file mode 100644
index 0000000000..93c4882cb6
--- /dev/null
+++ b/checkov/dockerfile/checks/__init__.py
@@ -0,0 +1,5 @@
+from os.path import dirname, basename, isfile, join
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+
diff --git a/checkov/dockerfile/parser.py b/checkov/dockerfile/parser.py
new file mode 100644
index 0000000000..4e9e30d4e6
--- /dev/null
+++ b/checkov/dockerfile/parser.py
@@ -0,0 +1,48 @@
+from collections import OrderedDict
+
+import re
+from dockerfile_parse import DockerfileParser
+from dockerfile_parse.constants import COMMENT_INSTRUCTION
+
+# class CheckovDockerFileParser(DockerfileParser)
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.comment.enum import COMMENT_REGEX
+
+
+def parse(filename):
+ dfp = DockerfileParser(path=filename)
+ return dfp_group_by_instructions(dfp)
+
+
+def dfp_group_by_instructions(dfp):
+ result = OrderedDict()
+ for instruction in dfp.structure:
+ instruction_literal = instruction["instruction"]
+ if instruction_literal not in result:
+ result[instruction_literal] = []
+ result[instruction_literal].append(instruction)
+ return result, dfp.lines
+
+
+def collect_skipped_checks(parse_result):
+ skipped_checks = []
+ bc_id_mapping = bc_integration.get_id_mapping()
+ ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
+ if COMMENT_INSTRUCTION in parse_result:
+ for comment in parse_result[COMMENT_INSTRUCTION]:
+ skip_search = re.search(COMMENT_REGEX, comment["value"])
+ if skip_search:
+ skipped_check = {
+ 'id': skip_search.group(2),
+ 'suppress_comment': skip_search.group(3)[1:] if skip_search.group(
+ 3) else "No comment provided"
+ }
+ # No matter which ID was used to skip, save the pair of IDs in the appropriate fields
+ if bc_id_mapping and skipped_check["id"] in bc_id_mapping:
+ skipped_check["bc_id"] = skipped_check["id"]
+ skipped_check["id"] = bc_id_mapping[skipped_check["id"]]
+ elif ckv_to_bc_id_mapping:
+ skipped_check["bc_id"] = ckv_to_bc_id_mapping.get(skipped_check["id"])
+ skipped_checks.append(skipped_check)
+ return skipped_checks
+
diff --git a/checkov/dockerfile/registry.py b/checkov/dockerfile/registry.py
new file mode 100644
index 0000000000..4a37b91f8d
--- /dev/null
+++ b/checkov/dockerfile/registry.py
@@ -0,0 +1,3 @@
+from checkov.dockerfile.base_registry import Registry
+
+registry = Registry()
diff --git a/checkov/dockerfile/runner.py b/checkov/dockerfile/runner.py
new file mode 100644
index 0000000000..988bb82393
--- /dev/null
+++ b/checkov/dockerfile/runner.py
@@ -0,0 +1,94 @@
+import logging
+import os
+from dockerfile_parse.constants import DOCKERFILE_FILENAME
+
+from checkov.common.output.record import Record
+from checkov.common.output.report import Report
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
+from checkov.dockerfile.parser import parse, collect_skipped_checks
+from checkov.dockerfile.registry import registry
+from checkov.runner_filter import RunnerFilter
+
+DOCKER_FILE_MASK = [DOCKERFILE_FILENAME]
+
+
+class Runner(BaseRunner):
+ check_type = "dockerfile"
+
+ def run(self, root_folder=None, external_checks_dir=None, files=None, runner_filter=RunnerFilter(),
+ collect_skip_comments=True):
+ report = Report(self.check_type)
+ definitions = {}
+ definitions_raw = {}
+ parsing_errors = {}
+ files_list = []
+ if external_checks_dir:
+ for directory in external_checks_dir:
+ registry.load_external_checks(directory)
+
+ if files:
+ for file in files:
+ if os.path.basename(file) in DOCKER_FILE_MASK:
+ (definitions[file], definitions_raw[file]) = parse(file)
+
+ if root_folder:
+ for root, d_names, f_names in os.walk(root_folder):
+ filter_ignored_paths(root, d_names, runner_filter.excluded_paths)
+ filter_ignored_paths(root, f_names, runner_filter.excluded_paths)
+ for file in f_names:
+ if file in DOCKER_FILE_MASK:
+ files_list.append(os.path.join(root, file))
+
+ for file in files_list:
+ relative_file_path = f'/{os.path.relpath(file, os.path.commonprefix((root_folder, file)))}'
+ try:
+ (definitions[relative_file_path], definitions_raw[relative_file_path]) = parse(file)
+ except TypeError:
+ logging.info(f'Dockerfile skipping {file} as it is not a valid dockerfile template')
+
+ for docker_file_path in definitions.keys():
+
+ # There are a few cases here. If -f was used, there could be a leading / because it's an absolute path,
+ # or there will be no leading slash; root_folder will always be none.
+ # If -d is used, root_folder will be the value given, and -f will start with a / (hardcoded above).
+ # The goal here is simply to get a valid path to the file (which docker_file_path does not always give).
+ if docker_file_path[0] == '/':
+ path_to_convert = (root_folder + docker_file_path) if root_folder else docker_file_path
+ else:
+ path_to_convert = (os.path.join(root_folder, docker_file_path)) if root_folder else docker_file_path
+
+ file_abs_path = os.path.abspath(path_to_convert)
+ skipped_checks = collect_skipped_checks(definitions[docker_file_path])
+ instructions = definitions[docker_file_path]
+
+ results = registry.scan(docker_file_path, instructions, skipped_checks,
+ runner_filter)
+ for check, check_result in results.items():
+ result_configuration = check_result['results_configuration']
+ startline = 1
+ endline = 1
+ result_instruction = ""
+ if result_configuration:
+ startline = result_configuration['startline']
+ endline = result_configuration['endline']
+ result_instruction = result_configuration["instruction"]
+
+ codeblock = []
+ self.calc_record_codeblock(codeblock, definitions_raw, docker_file_path, endline, startline)
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
+ code_block=codeblock,
+ file_path=docker_file_path,
+ file_line_range=[startline,
+ endline],
+ resource="{}.{}".format(docker_file_path,
+ result_instruction,
+ startline),
+ evaluations=None, check_class=check.__class__.__module__,
+ file_abs_path=file_abs_path, entity_tags=None)
+ report.add_record(record=record)
+
+ return report
+
+ def calc_record_codeblock(self, codeblock, definitions_raw, docker_file_path, endline, startline):
+ for line in range(startline, endline + 1):
+ codeblock.append((line, definitions_raw[docker_file_path][line - 1]))
diff --git a/checkov/helm/__init__.py b/checkov/helm/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/helm/base_registry.py b/checkov/helm/base_registry.py
new file mode 100644
index 0000000000..e5f29f1371
--- /dev/null
+++ b/checkov/helm/base_registry.py
@@ -0,0 +1,83 @@
+from checkov.common.checks.base_check_registry import BaseCheckRegistry
+from checkov.runner_filter import RunnerFilter
+
+
+class Registry(BaseCheckRegistry):
+
+ def extract_entity_details(self, entity):
+ kind = entity["kind"]
+ conf = entity
+ return kind, conf
+
+ def scan(self, scanned_file, entity, skipped_checks, runner_filter):
+ (entity_type, entity_configuration) = self.extract_entity_details(entity)
+ results = {}
+ checks = self.get_checks(entity_type)
+ for check in checks:
+ skip_info = {}
+ if skipped_checks:
+ if check.id in [x['id'] for x in skipped_checks]:
+ skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
+
+ if self._should_run_scan(check.id, entity_configuration, runner_filter):
+ self.logger.debug("Running check: {} on file {}".format(check.name, scanned_file))
+
+ result = check.run(scanned_file=scanned_file, entity_configuration=entity_configuration,
+ entity_name=entity_type, entity_type=entity_type, skip_info=skip_info)
+ results[check] = result
+ return results
+
+ @staticmethod
+ def _should_run_scan(check_id, entity_configuration, runner_filter):
+ check_id_allowlist = runner_filter.checks
+ check_id_denylist = runner_filter.skip_checks
+ if check_id_allowlist:
+ # Allow list provides namespace-only allows, check-only allows, or both
+ # If namespaces not specified, all namespaces are scanned
+ # If checks not specified, all checks are scanned
+ run_check = False
+ allowed_namespaces = [string for string in check_id_allowlist if "CKV_" not in string]
+ if not any("CKV_" in check for check in check_id_allowlist):
+ if "metadata" in entity_configuration and "namespace" in entity_configuration["metadata"]:
+ if entity_configuration["metadata"]["namespace"] in allowed_namespaces:
+ run_check = True
+ elif "parent_metadata" in entity_configuration and "namespace" in entity_configuration["parent_metadata"]:
+ if entity_configuration["parent_metadata"]["namespace"] in allowed_namespaces:
+ run_check = True
+ else:
+ if "default" in allowed_namespaces:
+ run_check = True
+ else:
+ if check_id in check_id_allowlist or RunnerFilter.is_external_check(check_id):
+ if allowed_namespaces:
+ # Check if namespace in allowed namespaces
+ if "metadata" in entity_configuration and "namespace" in entity_configuration["metadata"]:
+ if entity_configuration["metadata"]["namespace"] in allowed_namespaces:
+ run_check = True
+ elif "parent_metadata" in entity_configuration and "namespace" in entity_configuration["parent_metadata"]:
+ if entity_configuration["parent_metadata"]["namespace"] in allowed_namespaces:
+ run_check = True
+ else:
+ if "default" in allowed_namespaces:
+ run_check = True
+ else:
+ # No namespaces to filter
+ run_check = True
+ if run_check:
+ return True
+ elif check_id_denylist:
+ namespace_skip = False
+ if "metadata" in entity_configuration and "namespace" in entity_configuration["metadata"]:
+ if entity_configuration["metadata"]["namespace"] in check_id_denylist:
+ namespace_skip = True
+ elif "parent_metadata" in entity_configuration and "namespace" in entity_configuration["parent_metadata"]:
+ if entity_configuration["parent_metadata"]["namespace"] in check_id_denylist:
+ namespace_skip = True
+ else:
+ if "default" in check_id_denylist:
+ namespace_skip = True
+ if check_id not in check_id_denylist and namespace_skip == False:
+ return True
+ else:
+ return True
+ return False
diff --git a/checkov/helm/registry.py b/checkov/helm/registry.py
new file mode 100644
index 0000000000..7967eb9ab2
--- /dev/null
+++ b/checkov/helm/registry.py
@@ -0,0 +1,3 @@
+from checkov.helm.base_registry import Registry
+
+registry = Registry()
diff --git a/checkov/helm/runner.py b/checkov/helm/runner.py
new file mode 100644
index 0000000000..6827c720c2
--- /dev/null
+++ b/checkov/helm/runner.py
@@ -0,0 +1,272 @@
+import io
+import logging
+import operator
+import os
+import subprocess #nosec
+import tempfile
+from functools import reduce
+import shutil
+import json
+
+from checkov.common.output.record import Record
+from checkov.common.output.report import Report
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
+from checkov.kubernetes.runner import Runner as k8_runner
+from checkov.helm.registry import registry
+from checkov.runner_filter import RunnerFilter
+
+import yaml
+import asyncio
+
+K8_POSSIBLE_ENDINGS = [".yaml", ".yml", ".json"]
+
+
+class Runner(BaseRunner):
+ check_type = "helm"
+ helm_command = 'helm'
+ system_deps = True
+
+ @staticmethod
+ def find_chart_directories(root_folder, files, excluded_paths):
+ chart_directories = []
+ if not excluded_paths:
+ excluded_paths = []
+ if files:
+ logging.info('Running with --file argument; checking for Helm Chart.yaml files')
+ for file in files:
+ if os.path.basename(file) == 'Chart.yaml':
+ chart_directories.append(os.path.dirname(file))
+
+ if root_folder:
+ for root, d_names, f_names in os.walk(root_folder):
+ filter_ignored_paths(root, d_names, excluded_paths)
+ filter_ignored_paths(root, f_names, excluded_paths)
+ if 'Chart.yaml' in f_names:
+ chart_directories.append(root)
+
+ return chart_directories
+
+ @staticmethod
+ def parse_helm_dependency_output(o):
+ output = o.decode('utf-8')
+ chart_dependencies={}
+ if "WARNING" in output:
+ #Helm output showing no deps, example: 'WARNING: no dependencies at helm-charts/charts/prometheus-kafka-exporter/charts\n'
+ pass
+ else:
+ lines = output.split('\n')
+ for line in lines:
+ if line != "":
+ if not "NAME" in line:
+ chart_name, chart_version, chart_repo, chart_status = line.split("\t")
+ chart_dependencies.update({chart_name.rstrip():{'chart_name': chart_name.rstrip(), 'chart_version': chart_version.rstrip(), 'chart_repo': chart_repo.rstrip(), 'chart_status': chart_status.rstrip()}})
+ return chart_dependencies
+
+ @staticmethod
+ def parse_helm_chart_details(chart_path):
+ with open(f"{chart_path}/Chart.yaml", 'r') as chartyaml:
+ try:
+ chart_meta = yaml.safe_load(chartyaml)
+ except yaml.YAMLError as exc:
+ logging.info(f"Failed to load chart metadata from {chart_path}/Chart.yaml. details: {exc}")
+ return chart_meta
+
+ def check_system_deps(self):
+ # Ensure local system dependancies are available and of the correct version.
+ # Returns framework names to skip if deps fail.
+ logging.info(f"Checking necessary system dependancies for {self.check_type} checks.")
+ try:
+ proc = subprocess.Popen([self.helm_command, 'version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) #nosec
+ o, e = proc.communicate()
+ oString = str(o, 'utf-8')
+ if "Version:" in oString:
+ helmVersionOutput = oString[oString.find(':')+2 : oString.find(',')-1]
+ if "v3" in helmVersionOutput:
+ logging.info(f"Found working version of {self.check_type} dependancies: {helmVersionOutput}")
+ return None
+ else:
+ return self.check_type
+ except Exception:
+ logging.info(f"Error running necessary tools to process {self.check_type} checks.")
+ return self.check_type
+
+ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True):
+
+ definitions = {}
+ definitions_raw = {}
+ parsing_errors = {}
+ files_list = []
+ if external_checks_dir:
+ for directory in external_checks_dir:
+ registry.load_external_checks(directory)
+
+ chart_directories = self.find_chart_directories(root_folder, files, runner_filter.excluded_paths)
+
+ report = Report(self.check_type)
+
+ for chart_dir in chart_directories:
+ #chart_name = os.path.basename(chart_dir)
+ chart_meta = self.parse_helm_chart_details(chart_dir)
+ logging.info(f"Processing chart found at: {chart_dir}, name: {chart_meta['name']}, version: {chart_meta['version']}")
+ with tempfile.TemporaryDirectory() as target_dir:
+ #dependency list is nicer to parse than dependency update.
+ proc = subprocess.Popen([self.helm_command, 'dependency', 'list' , chart_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE) #nosec
+ o, e = proc.communicate()
+ if e:
+ if "Warning: Dependencies" in str(e, 'utf-8'):
+ logging.info(f"V1 API chart without Chart.yaml dependancies. Skipping chart dependancy list for {chart_meta['name']} at dir: {chart_dir}. Working dir: {target_dir}. Error details: {str(e, 'utf-8')}")
+ else:
+ logging.info(f"Error processing helm dependancies for {chart_meta['name']} at source dir: {chart_dir}. Working dir: {target_dir}. Error details: {str(e, 'utf-8')}")
+
+ self.parse_helm_dependency_output(o)
+
+ try:
+ #--dependency-update needed to pull in deps before templating.
+ proc = subprocess.Popen([self.helm_command, 'template', '--dependency-update', chart_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE) #nosec
+ o, e = proc.communicate()
+ logging.debug(f"Ran helm command to template chart output. Chart: {chart_meta['name']}. dir: {target_dir}. Output: {str(o, 'utf-8')}")
+
+ except Exception:
+ logging.info(f"Error processing helm chart {chart_meta['name']} at dir: {chart_dir}. Working dir: {target_dir}. Error details: {str(e, 'utf-8')}")
+
+ output = str(o, 'utf-8')
+ reader = io.StringIO(output)
+ cur_source_file = None
+ cur_writer = None
+ last_line_dashes = False
+ line_num = 1
+ for s in reader:
+ s = s.rstrip()
+ if s == '---':
+ last_line_dashes = True
+ continue
+
+ if last_line_dashes:
+ # The next line should contain a "Source" comment saying the name of the file it came from
+ # So we will close the old file, open a new file, and write the dashes from last iteration plus this line
+
+ if not s.startswith('# Source: '):
+ raise Exception(f'Line {line_num}: Expected line to start with # Source: {s}')
+ source = s[10:]
+ if source != cur_source_file:
+ if cur_writer:
+ cur_writer.close()
+ file_path = os.path.join(target_dir, source)
+ parent = os.path.dirname(file_path)
+ os.makedirs(parent, exist_ok=True)
+ cur_source_file = source
+ cur_writer = open(os.path.join(target_dir, source), 'a')
+ cur_writer.write('---' + os.linesep)
+ cur_writer.write(s + os.linesep)
+
+ last_line_dashes = False
+ else:
+ if s.startswith('# Source: '):
+ raise Exception(f'Line {line_num}: Unexpected line starting with # Source: {s}')
+
+ if not cur_writer:
+ continue
+ else:
+ cur_writer.write(s + os.linesep)
+
+ line_num += 1
+
+ if cur_writer:
+ cur_writer.close()
+
+ try:
+ k8s_runner = k8_runner()
+ chart_results = k8s_runner.run(target_dir, external_checks_dir=external_checks_dir, runner_filter=runner_filter, helmChart=chart_meta['name'])
+ logging.debug(f"Sucessfully ran k8s scan on {chart_meta['name']}. Scan dir : {target_dir}")
+ report.failed_checks += chart_results.failed_checks
+ report.passed_checks += chart_results.passed_checks
+ report.parsing_errors += chart_results.parsing_errors
+ report.skipped_checks += chart_results.skipped_checks
+
+ except:
+ with tempfile.TemporaryDirectory() as save_error_dir:
+ logging.debug(f"Error running k8s scan on {chart_meta['name']}. Scan dir: {target_dir}. Saved context dir: {save_error_dir}")
+ shutil.move(target_dir, save_error_dir)
+
+
+ ## TODO: Export helm dependancies for the chart we've extracted in chart_dependencies
+ return report
+
+
+ def _search_deep_keys(self, search_text, k8n_dict, path):
+ """Search deep for keys and get their values"""
+ keys = []
+ if isinstance(k8n_dict, dict):
+ for key in k8n_dict:
+ pathprop = path[:]
+ pathprop.append(key)
+ if key == search_text:
+ pathprop.append(k8n_dict[key])
+ keys.append(pathprop)
+ # pop the last element off for nesting of found elements for
+ # dict and list checks
+ pathprop = pathprop[:-1]
+ if isinstance(k8n_dict[key], dict):
+ keys.extend(self._search_deep_keys(search_text, k8n_dict[key], pathprop))
+ elif isinstance(k8n_dict[key], list):
+ for index, item in enumerate(k8n_dict[key]):
+ pathproparr = pathprop[:]
+ pathproparr.append(index)
+ keys.extend(self._search_deep_keys(search_text, item, pathproparr))
+ elif isinstance(k8n_dict, list):
+ for index, item in enumerate(k8n_dict):
+ pathprop = path[:]
+ pathprop.append(index)
+ keys.extend(self._search_deep_keys(search_text, item, pathprop))
+
+ return keys
+
+def get_skipped_checks(entity_conf):
+ skipped = []
+ metadata = {}
+ if not isinstance(entity_conf,dict):
+ return skipped
+ if entity_conf["kind"] == "containers" or entity_conf["kind"] == "initContainers":
+ metadata = entity_conf["parent_metadata"]
+ else:
+ if "metadata" in entity_conf.keys():
+ metadata = entity_conf["metadata"]
+ if "annotations" in metadata.keys() and metadata["annotations"] is not None:
+ for key in metadata["annotations"].keys():
+ skipped_item = {}
+ if "checkov.io/skip" in key or "bridgecrew.io/skip" in key:
+ if "CKV_K8S" in metadata["annotations"][key]:
+ if "=" in metadata["annotations"][key]:
+ (skipped_item["id"], skipped_item["suppress_comment"]) = metadata["annotations"][key].split("=")
+ else:
+ skipped_item["id"] = metadata["annotations"][key]
+ skipped_item["suppress_comment"] = "No comment provided"
+ skipped.append(skipped_item)
+ else:
+ logging.info("Parse of Annotation Failed for {}: {}".format(metadata["annotations"][key], entity_conf, indent=2))
+ continue
+ return skipped
+
+def _get_from_dict(data_dict, map_list):
+ return reduce(operator.getitem, map_list, data_dict)
+
+
+def _set_in_dict(data_dict, map_list, value):
+ _get_from_dict(data_dict, map_list[:-1])[map_list[-1]] = value
+
+
+def find_lines(node, kv):
+ if isinstance(node, str):
+ return node
+ if isinstance(node, list):
+ for i in node:
+ for x in find_lines(i, kv):
+ yield x
+ elif isinstance(node, dict):
+ if kv in node:
+ yield node[kv]
+ for j in node.values():
+ for x in find_lines(j, kv):
+ yield x
+
+
diff --git a/checkov/kubernetes/base_registry.py b/checkov/kubernetes/base_registry.py
index 92e2a1649e..448080bf38 100644
--- a/checkov/kubernetes/base_registry.py
+++ b/checkov/kubernetes/base_registry.py
@@ -4,9 +4,6 @@
class Registry(BaseCheckRegistry):
- def __init__(self):
- super().__init__()
-
def extract_entity_details(self, entity):
kind = entity["kind"]
conf = entity
@@ -22,7 +19,7 @@ def scan(self, scanned_file, entity, skipped_checks, runner_filter):
if check.id in [x['id'] for x in skipped_checks]:
skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
- if self._should_run_scan(check.id, entity_configuration, runner_filter):
+ if self._should_run_scan(check.id, entity_configuration, runner_filter, check.bc_id):
self.logger.debug("Running check: {} on file {}".format(check.name, scanned_file))
result = check.run(scanned_file=scanned_file, entity_configuration=entity_configuration,
@@ -31,7 +28,7 @@ def scan(self, scanned_file, entity, skipped_checks, runner_filter):
return results
@staticmethod
- def _should_run_scan(check_id, entity_configuration, runner_filter):
+ def _should_run_scan(check_id, entity_configuration, runner_filter, bc_id=None):
check_id_allowlist = runner_filter.checks
check_id_denylist = runner_filter.skip_checks
if check_id_allowlist:
@@ -39,8 +36,8 @@ def _should_run_scan(check_id, entity_configuration, runner_filter):
# If namespaces not specified, all namespaces are scanned
# If checks not specified, all checks are scanned
run_check = False
- allowed_namespaces = [string for string in check_id_allowlist if "CKV_" not in string]
- if not any("CKV_" in check for check in check_id_allowlist):
+ allowed_namespaces = [string for string in check_id_allowlist if ("CKV_" not in string and "BC_" not in string)]
+ if not any(("CKV_" in check or "BC_" in check) for check in check_id_allowlist):
if "metadata" in entity_configuration and "namespace" in entity_configuration["metadata"]:
if entity_configuration["metadata"]["namespace"] in allowed_namespaces:
run_check = True
@@ -51,7 +48,7 @@ def _should_run_scan(check_id, entity_configuration, runner_filter):
if "default" in allowed_namespaces:
run_check = True
else:
- if check_id in check_id_allowlist or RunnerFilter.is_external_check(check_id):
+ if runner_filter.should_run_check(check_id, bc_id):
if allowed_namespaces:
# Check if namespace in allowed namespaces
if "metadata" in entity_configuration and "namespace" in entity_configuration["metadata"]:
@@ -79,7 +76,7 @@ def _should_run_scan(check_id, entity_configuration, runner_filter):
else:
if "default" in check_id_denylist:
namespace_skip = True
- if check_id not in check_id_denylist and namespace_skip == False:
+ if runner_filter.should_run_check(check_id, bc_id) and not namespace_skip:
return True
else:
return True
diff --git a/checkov/kubernetes/base_spec_check.py b/checkov/kubernetes/base_spec_check.py
index 677d36b5c0..6a32a7f1d0 100644
--- a/checkov/kubernetes/base_spec_check.py
+++ b/checkov/kubernetes/base_spec_check.py
@@ -32,3 +32,10 @@ def wrapper(self, conf, entity_type=None):
return wrapped(self, conf)
return wrapper
+
+ @staticmethod
+ def get_inner_entry(conf, entry_name):
+ spec = {}
+ if conf.get("spec") and conf.get("spec").get("template"):
+ spec = conf.get("spec").get("template").get(entry_name, {})
+ return spec
diff --git a/checkov/kubernetes/checks/AllowPrivilegeEscalation.py b/checkov/kubernetes/checks/AllowPrivilegeEscalation.py
index 546fbbe25a..e8955ae1ea 100644
--- a/checkov/kubernetes/checks/AllowPrivilegeEscalation.py
+++ b/checkov/kubernetes/checks/AllowPrivilegeEscalation.py
@@ -19,7 +19,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "securityContext" in conf:
diff --git a/checkov/kubernetes/checks/AllowedCapabilities.py b/checkov/kubernetes/checks/AllowedCapabilities.py
index f11e26f1a9..dcc31f98fe 100644
--- a/checkov/kubernetes/checks/AllowedCapabilities.py
+++ b/checkov/kubernetes/checks/AllowedCapabilities.py
@@ -16,12 +16,12 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
- if "securityContext" in conf:
- if "capabilities" in conf["securityContext"]:
- if "add" in conf["securityContext"]["capabilities"]:
+ if conf.get("securityContext"):
+ if conf["securityContext"].get("capabilities"):
+ if conf["securityContext"]["capabilities"].get("add"):
if conf["securityContext"]["capabilities"]["add"]:
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/kubernetes/checks/AllowedCapabilitiesSysAdmin.py b/checkov/kubernetes/checks/AllowedCapabilitiesSysAdmin.py
index 330d3d9884..f69073ebe3 100644
--- a/checkov/kubernetes/checks/AllowedCapabilitiesSysAdmin.py
+++ b/checkov/kubernetes/checks/AllowedCapabilitiesSysAdmin.py
@@ -15,12 +15,12 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
- if "securityContext" in conf:
- if "capabilities" in conf["securityContext"]:
- if "add" in conf["securityContext"]["capabilities"]:
+ if conf.get("securityContext"):
+ if conf["securityContext"].get("capabilities"):
+ if conf["securityContext"]["capabilities"].get("add"):
if "SYS_ADMIN" in conf["securityContext"]["capabilities"]["add"]:
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/kubernetes/checks/ApiServerAdmissionControlAlwaysAdmit.py b/checkov/kubernetes/checks/ApiServerAdmissionControlAlwaysAdmit.py
new file mode 100644
index 0000000000..735ff0d43d
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAdmissionControlAlwaysAdmit.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAdmissionControlAlwaysAdmit(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_79"
+ name = "Ensure that the admission control plugin AlwaysAdmit is not set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command"):
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "AlwaysAdmit" == value:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAdmissionControlAlwaysAdmit()
diff --git a/checkov/kubernetes/checks/ApiServerAdmissionControlEventRateLimit.py b/checkov/kubernetes/checks/ApiServerAdmissionControlEventRateLimit.py
new file mode 100644
index 0000000000..742484e18a
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAdmissionControlEventRateLimit.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAdmissionControlEventRateLimit(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_78"
+ name = "Ensure that the admission control plugin EventRateLimit is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['AdmissionConfiguration']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ if "metadata" in conf:
+ if "name" in conf["metadata"]:
+ return 'AdmissionConfiguration.{}'.format(conf["metadata"]["name"])
+ return 'AdmissionConfiguration.spec.allowedCapabilities'
+
+ def scan_spec_conf(self, conf):
+ if "plugins" not in conf:
+ return CheckResult.FAILED
+ plugins = conf["plugins"]
+ for plugin in plugins:
+ if plugin["name"] == "EventRateLimit":
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+check = ApiServerAdmissionControlEventRateLimit()
diff --git a/checkov/kubernetes/checks/ApiServerAlwaysPullImagesPlugin.py b/checkov/kubernetes/checks/ApiServerAlwaysPullImagesPlugin.py
new file mode 100644
index 0000000000..39c3038018
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAlwaysPullImagesPlugin.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAlwaysPullImagesPlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_80"
+ name = "Ensure that the admission control plugin AlwaysPullImages is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "AlwaysPullImages" not in value:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAlwaysPullImagesPlugin()
diff --git a/checkov/kubernetes/checks/ApiServerAnonymousAuth.py b/checkov/kubernetes/checks/ApiServerAnonymousAuth.py
new file mode 100644
index 0000000000..ba8434183b
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAnonymousAuth.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ApiServerAnonymousAuth(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_68"
+ name = "Ensure that the --anonymous-auth argument is set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ if "--anonymous-auth=true" in conf["command"] or "--anonymous-auth=false" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerAnonymousAuth()
diff --git a/checkov/kubernetes/checks/ApiServerAuditLog.py b/checkov/kubernetes/checks/ApiServerAuditLog.py
new file mode 100644
index 0000000000..2e15e73528
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuditLog.py
@@ -0,0 +1,26 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuditLog(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_91"
+ name = "Ensure that the --audit-log-path argument is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasAuditLog = False
+ for command in conf["command"]:
+ if command.startswith("--audit-log-path"):
+ hasAuditLog = True
+ return CheckResult.PASSED if hasAuditLog else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuditLog()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerAuditLogMaxAge.py b/checkov/kubernetes/checks/ApiServerAuditLogMaxAge.py
new file mode 100644
index 0000000000..ddeb50e9ae
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuditLogMaxAge.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuditLogMaxAge(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_92"
+ name = "Ensure that the --audit-log-maxage argument is set to 30 or as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasAuditLogMaxAge = False
+ for command in conf["command"]:
+ if command.startswith("--audit-log-maxage"):
+ value = command.split("=")[1]
+ hasAuditLogMaxAge = int(value) >= 30
+ break
+ return CheckResult.PASSED if hasAuditLogMaxAge else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuditLogMaxAge()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerAuditLogMaxBackup.py b/checkov/kubernetes/checks/ApiServerAuditLogMaxBackup.py
new file mode 100644
index 0000000000..8235c2865c
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuditLogMaxBackup.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuditLogMaxBackup(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_93"
+ name = "Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasAuditLogMaxBackup = False
+ for command in conf["command"]:
+ if command.startswith("--audit-log-maxbackup"):
+ value = command.split("=")[1]
+ hasAuditLogMaxBackup = int(value) >= 10
+ break
+ return CheckResult.PASSED if hasAuditLogMaxBackup else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuditLogMaxBackup()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerAuditLogMaxSize.py b/checkov/kubernetes/checks/ApiServerAuditLogMaxSize.py
new file mode 100644
index 0000000000..61c92c4202
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuditLogMaxSize.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuditLogMaxSize(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_94"
+ name = "Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasAuditLogMaxSize = False
+ for command in conf["command"]:
+ if command.startswith("--audit-log-maxsize"):
+ value = command.split("=")[1]
+ hasAuditLogMaxSize = int(value) >= 100
+ break
+ return CheckResult.PASSED if hasAuditLogMaxSize else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuditLogMaxSize()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerAuthorizationModeNode.py b/checkov/kubernetes/checks/ApiServerAuthorizationModeNode.py
new file mode 100644
index 0000000000..9c6b519973
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuthorizationModeNode.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuthorizationModeNode(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_75"
+ name = "Ensure that the --authorization-mode argument includes Node"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasNodeAuthorizationMode = False
+ for command in conf["command"]:
+ if command.startswith("--authorization-mode"):
+ modes = command.split("=")[1]
+ if "Node" in modes.split(","):
+ hasNodeAuthorizationMode = True
+ return CheckResult.PASSED if hasNodeAuthorizationMode else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuthorizationModeNode()
diff --git a/checkov/kubernetes/checks/ApiServerAuthorizationModeNotAlwaysAllow.py b/checkov/kubernetes/checks/ApiServerAuthorizationModeNotAlwaysAllow.py
new file mode 100644
index 0000000000..2aa2cc4ef5
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuthorizationModeNotAlwaysAllow.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuthorizationModeNotAlwaysAllow(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_74"
+ name = "Ensure that the --authorization-mode argument is not set to AlwaysAllow"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith("--authorization-mode"):
+ modes = command.split("=")[1]
+ if "AlwaysAllow" in modes.split(","):
+ return CheckResult.FAILED
+ break
+
+ return CheckResult.PASSED
+
+check = ApiServerAuthorizationModeNotAlwaysAllow()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerAuthorizationModeRBAC.py b/checkov/kubernetes/checks/ApiServerAuthorizationModeRBAC.py
new file mode 100644
index 0000000000..26c9751119
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerAuthorizationModeRBAC.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerAuthorizationModeRBAC(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_77"
+ name = "Ensure that the --authorization-mode argument includes RBAC"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasRBACAuthorizationMode = False
+ for command in conf["command"]:
+ if command.startswith("--authorization-mode"):
+ modes = command.split("=")[1]
+ if "RBAC" in modes.split(","):
+ hasRBACAuthorizationMode = True
+ return CheckResult.PASSED if hasRBACAuthorizationMode else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerAuthorizationModeRBAC()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerBasicAuthFile.py b/checkov/kubernetes/checks/ApiServerBasicAuthFile.py
new file mode 100644
index 0000000000..79fb215ef6
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerBasicAuthFile.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ApiServerBasicAuthFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_69"
+ name = "Ensure that the --basic-auth-file argument is not set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ if any(x.startswith('--basic-auth-file') for x in conf["command"]):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerBasicAuthFile()
diff --git a/checkov/kubernetes/checks/ApiServerEncryptionProviders.py b/checkov/kubernetes/checks/ApiServerEncryptionProviders.py
new file mode 100644
index 0000000000..f58ddc8719
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerEncryptionProviders.py
@@ -0,0 +1,29 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.kubernetes.checks.k8s_check_utils import extract_commands
+
+
+class ApiServerEncryptionProviders(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_104"
+ name = "Ensure that encryption providers are appropriately configured"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ keys, values = extract_commands(conf)
+
+ if "kube-apiserver" in keys:
+ if "--encryption-provider-config" not in keys:
+ return CheckResult.FAILED
+
+
+ return CheckResult.PASSED
+
+
+check = ApiServerEncryptionProviders()
diff --git a/checkov/kubernetes/checks/ApiServerEtcdCaFile.py b/checkov/kubernetes/checks/ApiServerEtcdCaFile.py
new file mode 100644
index 0000000000..c8a7c87120
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerEtcdCaFile.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.kubernetes.checks.k8s_check_utils import extract_commands
+
+
+class ApiServerEtcdCaFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_102"
+ name = "Ensure that the --etcd-ca-file argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ keys, values = extract_commands(conf)
+
+ if "kube-apiserver" in keys:
+ if '--etcd-ca-file' not in keys:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerEtcdCaFile()
diff --git a/checkov/kubernetes/checks/ApiServerEtcdCertAndKey.py b/checkov/kubernetes/checks/ApiServerEtcdCertAndKey.py
new file mode 100644
index 0000000000..45f2103b43
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerEtcdCertAndKey.py
@@ -0,0 +1,31 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ApiServerEtcdCertAndKey(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_99"
+ name = "Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasCertCommand = False
+ hasKeyCommand = False
+ for command in conf["command"]:
+ if command.startswith("--etcd-certfile"):
+ hasCertCommand = True
+ elif command.startswith("--etcd-keyfile"):
+ hasKeyCommand = True
+ return CheckResult.PASSED if hasCertCommand and hasKeyCommand else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerEtcdCertAndKey()
diff --git a/checkov/kubernetes/checks/ApiServerInsecureBindAddress.py b/checkov/kubernetes/checks/ApiServerInsecureBindAddress.py
new file mode 100644
index 0000000000..0e19e6bc3d
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerInsecureBindAddress.py
@@ -0,0 +1,24 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerInsecureBindAddress(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_86"
+ name = "Ensure that the --insecure-bind-address argument is not set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ strippedArgs = [arg.split("=")[0] for arg in conf["command"]]
+ if "--insecure-bind-address" in strippedArgs:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerInsecureBindAddress()
diff --git a/checkov/kubernetes/checks/ApiServerInsecurePort.py b/checkov/kubernetes/checks/ApiServerInsecurePort.py
new file mode 100644
index 0000000000..f1bb634b29
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerInsecurePort.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerInsecurePort(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_88"
+ name = "Ensure that the --insecure-port argument is set to 0"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ if "--insecure-port=0" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerInsecurePort()
diff --git a/checkov/kubernetes/checks/ApiServerKubeletClientCertAndKey.py b/checkov/kubernetes/checks/ApiServerKubeletClientCertAndKey.py
new file mode 100644
index 0000000000..f1cb47cabb
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerKubeletClientCertAndKey.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerKubeletClientCertAndKey(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_72"
+ name = "Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasCertCommand = False
+ hasKeyCommand = False
+ for command in conf["command"]:
+ if command.startswith("--kubelet-client-certificate"):
+ hasCertCommand = True
+ elif command.startswith("--kubelet-client-key"):
+ hasKeyCommand = True
+ return CheckResult.PASSED if hasCertCommand and hasKeyCommand else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerKubeletClientCertAndKey()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerKubeletHttps.py b/checkov/kubernetes/checks/ApiServerKubeletHttps.py
new file mode 100644
index 0000000000..23cb3d4302
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerKubeletHttps.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerKubeletHttps(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_71"
+ name = "Ensure that the --kubelet-https argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ if "--kubelet-https=false" in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerKubeletHttps()
diff --git a/checkov/kubernetes/checks/ApiServerNamespaceLifecyclePlugin.py b/checkov/kubernetes/checks/ApiServerNamespaceLifecyclePlugin.py
new file mode 100644
index 0000000000..4d4e884b3b
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerNamespaceLifecyclePlugin.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerNamespaceLifecyclePlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_83"
+ name = "Ensure that the admission control plugin NamespaceLifecycle is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "NamespaceLifecycle" not in value:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerNamespaceLifecyclePlugin()
diff --git a/checkov/kubernetes/checks/ApiServerNodeRestrictionPlugin.py b/checkov/kubernetes/checks/ApiServerNodeRestrictionPlugin.py
new file mode 100644
index 0000000000..d2569ea4cf
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerNodeRestrictionPlugin.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerNodeRestrictionPlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_85"
+ name = "Ensure that the admission control plugin NodeRestriction is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "NodeRestriction" not in value:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerNodeRestrictionPlugin()
diff --git a/checkov/kubernetes/checks/ApiServerPodSecurityPolicyPlugin.py b/checkov/kubernetes/checks/ApiServerPodSecurityPolicyPlugin.py
new file mode 100644
index 0000000000..46926e711f
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerPodSecurityPolicyPlugin.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerPodSecurityPolicyPlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_84"
+ name = "Ensure that the admission control plugin PodSecurityPolicy is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "PodSecurityPolicy" not in value:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerPodSecurityPolicyPlugin()
diff --git a/checkov/kubernetes/checks/ApiServerProfiling.py b/checkov/kubernetes/checks/ApiServerProfiling.py
new file mode 100644
index 0000000000..dc31784905
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerProfiling.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerProfiling(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_90"
+ name = "Ensure that the --profiling argument is set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ if "--profiling=true" in conf["command"] or "--profiling=false" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerProfiling()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerRequestTimeout.py b/checkov/kubernetes/checks/ApiServerRequestTimeout.py
new file mode 100644
index 0000000000..39864bf1a7
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerRequestTimeout.py
@@ -0,0 +1,32 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+import re
+
+
+class ApiServerRequestTimeout(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_95"
+ name = "Ensure that the --request-timeout argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--request-timeout":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--request-timeout":
+ regex = r"^(\d{1,2}[h])(\d{1,2}[m])?(\d{1,2}[s])?$|^(\d{1,2}[m])?(\d{1,2}[s])?$|^(\d{1,2}[s])$"
+ matches = re.match(regex, value)
+ if not matches:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerRequestTimeout()
diff --git a/checkov/kubernetes/checks/ApiServerSecurePort.py b/checkov/kubernetes/checks/ApiServerSecurePort.py
new file mode 100644
index 0000000000..41017c6452
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerSecurePort.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerSecurePort(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_89"
+ name = "Ensure that the --secure-port argument is not set to 0"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ if "--secure-port=0" in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerSecurePort()
diff --git a/checkov/kubernetes/checks/ApiServerSecurityContextDenyPlugin.py b/checkov/kubernetes/checks/ApiServerSecurityContextDenyPlugin.py
new file mode 100644
index 0000000000..204da6f7f7
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerSecurityContextDenyPlugin.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerSecurityContextDenyPlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_81"
+ name = "Ensure that the admission control plugin SecurityContextDeny is set if PodSecurityPolicy is not used"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "PodSecurityPolicy" not in value and "SecurityContextDeny" not in value:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = ApiServerSecurityContextDenyPlugin()
diff --git a/checkov/kubernetes/checks/ApiServerServiceAccountKeyFile.py b/checkov/kubernetes/checks/ApiServerServiceAccountKeyFile.py
new file mode 100644
index 0000000000..5e598b9a92
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerServiceAccountKeyFile.py
@@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+import re
+
+
+class ApiServerServiceAccountKeyFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_97"
+ name = "Ensure that the --service-account-key-file argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--service-account-key-file":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--service-account-key-file":
+ # should be a valid path and to end with .pem
+ regex = r"^([\/|\.\/]?[a-z_\-\s0-9\.]+)+\.(pem)$"
+ matches = re.match(regex, value)
+ if not matches:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerServiceAccountKeyFile()
diff --git a/checkov/kubernetes/checks/ApiServerServiceAccountLookup.py b/checkov/kubernetes/checks/ApiServerServiceAccountLookup.py
new file mode 100644
index 0000000000..3f85768460
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerServiceAccountLookup.py
@@ -0,0 +1,24 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerServiceAccountLookup(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_96"
+ name = "Ensure that the --service-account-lookup argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ if "--service-account-lookup=false" in conf["command"] or "--service-account-lookup=true" not in conf["command"]:
+ return CheckResult.FAILED
+
+
+ return CheckResult.PASSED
+
+check = ApiServerServiceAccountLookup()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/ApiServerServiceAccountPlugin.py b/checkov/kubernetes/checks/ApiServerServiceAccountPlugin.py
new file mode 100644
index 0000000000..0485ea9008
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerServiceAccountPlugin.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class ApiServerServiceAccountPlugin(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_82"
+ name = "Ensure that the admission control plugin ServiceAccount is set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd == "--enable-admission-plugins":
+ return CheckResult.FAILED
+ if "=" in cmd:
+ [field,value,*_] = cmd.split("=")
+ if field == "--enable-admission-plugins":
+ if "ServiceAccount" not in value:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+check = ApiServerServiceAccountPlugin()
diff --git a/checkov/kubernetes/checks/ApiServerStrongCryptographicCiphers.py b/checkov/kubernetes/checks/ApiServerStrongCryptographicCiphers.py
new file mode 100644
index 0000000000..ade0cafa3f
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerStrongCryptographicCiphers.py
@@ -0,0 +1,33 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+strongCiphers = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256"]
+
+
+class ApiServerStrongCryptographicCiphers(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_105"
+ name = "Ensure that the API Server only makes use of Strong Cryptographic Ciphers"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-apiserver" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith("--tls-cipher-suites"):
+ value = command.split("=")[1]
+ ciphers = value.split(",")
+ for cipher in ciphers:
+ if cipher not in strongCiphers:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerStrongCryptographicCiphers()
diff --git a/checkov/kubernetes/checks/ApiServerTlsCertAndKey.py b/checkov/kubernetes/checks/ApiServerTlsCertAndKey.py
new file mode 100644
index 0000000000..e39aefa21d
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerTlsCertAndKey.py
@@ -0,0 +1,31 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ApiServerTlsCertAndKey(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_100"
+ name = "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf and conf["command"] is not None:
+ if "kube-apiserver" in conf["command"]:
+ hasCertCommand = False
+ hasKeyCommand = False
+ for command in conf["command"]:
+ if command.startswith("--tls-cert-file"):
+ hasCertCommand = True
+ elif command.startswith("--tls-private-key-file"):
+ hasKeyCommand = True
+ return CheckResult.PASSED if hasCertCommand and hasKeyCommand else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerTlsCertAndKey()
diff --git a/checkov/kubernetes/checks/ApiServerTokenAuthFile.py b/checkov/kubernetes/checks/ApiServerTokenAuthFile.py
new file mode 100644
index 0000000000..fe7653880c
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerTokenAuthFile.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ApiServerTokenAuthFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_70"
+ name = "Ensure that the --token-auth-file argument is not set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-apiserver" in conf["command"]:
+ if any(x.startswith('--token-auth-file') for x in conf["command"]):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerTokenAuthFile()
diff --git a/checkov/kubernetes/checks/ApiServerkubeletCertificateAuthority.py b/checkov/kubernetes/checks/ApiServerkubeletCertificateAuthority.py
new file mode 100644
index 0000000000..76fa6d265b
--- /dev/null
+++ b/checkov/kubernetes/checks/ApiServerkubeletCertificateAuthority.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.kubernetes.checks.k8s_check_utils import extract_commands
+
+
+class ApiServerkubeletCertificateAuthority(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_73"
+ name = "Ensure that the --kubelet-certificate-authority argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ keys, values = extract_commands(conf)
+
+ if "kube-apiserver" in keys and '--kubelet-certificate-authority' not in keys:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ApiServerkubeletCertificateAuthority()
diff --git a/checkov/kubernetes/checks/CPULimits.py b/checkov/kubernetes/checks/CPULimits.py
index 123a1d978f..c716e58e9f 100644
--- a/checkov/kubernetes/checks/CPULimits.py
+++ b/checkov/kubernetes/checks/CPULimits.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if conf.get("resources"):
diff --git a/checkov/kubernetes/checks/CPURequests.py b/checkov/kubernetes/checks/CPURequests.py
index 2a486dc6e3..2e8ce5eddb 100644
--- a/checkov/kubernetes/checks/CPURequests.py
+++ b/checkov/kubernetes/checks/CPURequests.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if conf.get("resources"):
diff --git a/checkov/kubernetes/checks/ContainerSecurityContext.py b/checkov/kubernetes/checks/ContainerSecurityContext.py
index 4bcf76c291..14b22861c5 100644
--- a/checkov/kubernetes/checks/ContainerSecurityContext.py
+++ b/checkov/kubernetes/checks/ContainerSecurityContext.py
@@ -16,7 +16,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "securityContext" in conf:
diff --git a/checkov/kubernetes/checks/ControllerManagerBindAddress.py b/checkov/kubernetes/checks/ControllerManagerBindAddress.py
new file mode 100644
index 0000000000..0e061909bb
--- /dev/null
+++ b/checkov/kubernetes/checks/ControllerManagerBindAddress.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class ControllerManagerBindAddress(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_113"
+ name = "Ensure that the --bind-address argument is set to 127.0.0.1"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-controller-manager" in conf["command"]:
+ for cmd in conf["command"]:
+ if "=" in cmd:
+ [key, value, *_] = cmd.split("=")
+ if key == "--bind-address" and value == "127.0.0.1":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = ControllerManagerBindAddress()
diff --git a/checkov/kubernetes/checks/DockerSocketVolume.py b/checkov/kubernetes/checks/DockerSocketVolume.py
index c42d3f2715..f81b25d6c9 100644
--- a/checkov/kubernetes/checks/DockerSocketVolume.py
+++ b/checkov/kubernetes/checks/DockerSocketVolume.py
@@ -39,16 +39,14 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
# Evaluate volumes
if spec:
if "volumes" in spec and spec.get("volumes"):
for v in spec["volumes"]:
- if "hostPath" in v:
+ if v.get("hostPath"):
if "path" in v["hostPath"]:
if v["hostPath"]["path"] == "/var/run/docker.sock":
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/DropCapabilities.py b/checkov/kubernetes/checks/DropCapabilities.py
index 1213358003..e3f53cb33d 100644
--- a/checkov/kubernetes/checks/DropCapabilities.py
+++ b/checkov/kubernetes/checks/DropCapabilities.py
@@ -16,14 +16,14 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
- if "securityContext" in conf:
- if "capabilities" in conf["securityContext"]:
- if "drop" in conf["securityContext"]["capabilities"]:
+ if conf.get("securityContext"):
+ if conf["securityContext"].get("capabilities"):
+ if conf["securityContext"]["capabilities"].get("drop"):
for d in conf["securityContext"]["capabilities"]["drop"]:
- if "ALL" in d or "NET_RAW" in d:
+ if any(cap in d for cap in ("ALL", "all", "NET_RAW")):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/EtcdAutoTls.py b/checkov/kubernetes/checks/EtcdAutoTls.py
new file mode 100644
index 0000000000..9bcbefe17d
--- /dev/null
+++ b/checkov/kubernetes/checks/EtcdAutoTls.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class EtcdAutoTls(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 2.3
+ id = "CKV_K8S_118"
+ name = "Ensure that the --auto-tls argument is not set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "etcd" in conf.get("command", []) and "--auto-tls=true" in conf.get("command", []):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = EtcdAutoTls()
diff --git a/checkov/kubernetes/checks/EtcdCertAndKey.py b/checkov/kubernetes/checks/EtcdCertAndKey.py
new file mode 100644
index 0000000000..9ee4ee3ccd
--- /dev/null
+++ b/checkov/kubernetes/checks/EtcdCertAndKey.py
@@ -0,0 +1,32 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class EtcdCertAndKey(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 2.1
+ id = "CKV_K8S_116"
+ name = "Ensure that the --cert-file and --key-file arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "etcd" in conf["command"]:
+ hasCertCommand = False
+ hasKeyCommand = False
+ for command in conf["command"]:
+ if command.startswith("--cert-file"):
+ hasCertCommand = True
+ elif command.startswith("--key-file"):
+ hasKeyCommand = True
+ if hasCertCommand and hasKeyCommand:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = EtcdCertAndKey()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/EtcdClientCertAuth.py b/checkov/kubernetes/checks/EtcdClientCertAuth.py
new file mode 100644
index 0000000000..3a688a820c
--- /dev/null
+++ b/checkov/kubernetes/checks/EtcdClientCertAuth.py
@@ -0,0 +1,22 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class EtcdClientCertAuth(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 2.2
+ id = "CKV_K8S_117"
+ name = "Ensure that the --client-cert-auth argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "etcd" in conf.get("command", []) and "--client-cert-auth=true" not in conf.get("command", []):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = EtcdClientCertAuth()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/EtcdPeerFiles.py b/checkov/kubernetes/checks/EtcdPeerFiles.py
new file mode 100644
index 0000000000..496a02a580
--- /dev/null
+++ b/checkov/kubernetes/checks/EtcdPeerFiles.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.kubernetes.checks.k8s_check_utils import extract_commands
+
+
+class EtcdPeerFiles(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6
+ id = "CKV_K8S_119"
+ name = "Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ keys, values = extract_commands(conf)
+
+ if "etcd" in keys:
+ if '--peer-cert-file' in keys and '--peer-key-file' in keys:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = EtcdPeerFiles()
diff --git a/checkov/kubernetes/checks/HostPort.py b/checkov/kubernetes/checks/HostPort.py
index 06711a8c5f..4759f9a001 100644
--- a/checkov/kubernetes/checks/HostPort.py
+++ b/checkov/kubernetes/checks/HostPort.py
@@ -21,7 +21,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "ports" in conf:
diff --git a/checkov/kubernetes/checks/ImageDigest.py b/checkov/kubernetes/checks/ImageDigest.py
index 2870debac9..779b7d841e 100644
--- a/checkov/kubernetes/checks/ImageDigest.py
+++ b/checkov/kubernetes/checks/ImageDigest.py
@@ -20,7 +20,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "image" in conf:
diff --git a/checkov/kubernetes/checks/ImagePullPolicyAlways.py b/checkov/kubernetes/checks/ImagePullPolicyAlways.py
index 65039a6a0d..f4750f429a 100644
--- a/checkov/kubernetes/checks/ImagePullPolicyAlways.py
+++ b/checkov/kubernetes/checks/ImagePullPolicyAlways.py
@@ -24,7 +24,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "image" in conf:
diff --git a/checkov/kubernetes/checks/ImageTagFixed.py b/checkov/kubernetes/checks/ImageTagFixed.py
index 3c80f942cb..f6b9afb343 100644
--- a/checkov/kubernetes/checks/ImageTagFixed.py
+++ b/checkov/kubernetes/checks/ImageTagFixed.py
@@ -21,7 +21,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "image" in conf:
diff --git a/checkov/kubernetes/checks/KubeControllerManagerBlockProfiles.py b/checkov/kubernetes/checks/KubeControllerManagerBlockProfiles.py
new file mode 100644
index 0000000000..fc51b1d8ad
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerBlockProfiles.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerBlockProfiles(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_107"
+ name = "Ensure that the --profiling argument is set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-controller-manager" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--profiling'):
+ value = command.split("=")[1]
+ if value == 'false':
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerBlockProfiles()
diff --git a/checkov/kubernetes/checks/KubeControllerManagerRootCAFile.py b/checkov/kubernetes/checks/KubeControllerManagerRootCAFile.py
new file mode 100644
index 0000000000..46477786eb
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerRootCAFile.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerRootCAFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_111"
+ name = "Ensure that the --root-ca-file argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-controller-manager" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--root-ca-file'):
+ file_name = command.split("=")[1]
+ extension = file_name.split(".")[1]
+ if extension == 'pem':
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerRootCAFile()
diff --git a/checkov/kubernetes/checks/KubeControllerManagerRotateKubeletServerCertificate.py b/checkov/kubernetes/checks/KubeControllerManagerRotateKubeletServerCertificate.py
new file mode 100644
index 0000000000..f07a80be1b
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerRotateKubeletServerCertificate.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerRotateKubeletServerCertificate(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.12
+ id = "CKV_K8S_112"
+ name = "Ensure that the RotateKubeletServerCertificate argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-controller-manager" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd.startswith("--feature-gates"):
+ value = cmd[cmd.index("=")+1:]
+ if 'RotateKubeletServerCertificate=false' in value:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerRotateKubeletServerCertificate()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/KubeControllerManagerServiceAccountCredentials.py b/checkov/kubernetes/checks/KubeControllerManagerServiceAccountCredentials.py
new file mode 100644
index 0000000000..b78d241c4d
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerServiceAccountCredentials.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerServiceAccountCredentials(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_108"
+ name = "Ensure that the --use-service-account-credentials argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-controller-manager" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--use-service-account-credentials'):
+ value = command.split("=")[1]
+ if value == 'true':
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.UNKNOWN
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerServiceAccountCredentials()
diff --git a/checkov/kubernetes/checks/KubeControllerManagerServiceAccountPrivateKeyFile.py b/checkov/kubernetes/checks/KubeControllerManagerServiceAccountPrivateKeyFile.py
new file mode 100644
index 0000000000..e12e96d8b1
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerServiceAccountPrivateKeyFile.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerServiceAccountPrivateKeyFile(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_110"
+ name = "Ensure that the --service-account-private-key-file argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-controller-manager" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--service-account-private-key-file'):
+ file_name = command.split("=")[1]
+ extension = file_name.split(".")[1]
+ if extension == 'pem':
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerServiceAccountPrivateKeyFile()
diff --git a/checkov/kubernetes/checks/KubeControllerManagerTerminatedPods.py b/checkov/kubernetes/checks/KubeControllerManagerTerminatedPods.py
new file mode 100644
index 0000000000..4b9f13344b
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeControllerManagerTerminatedPods.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeControllerManagerTerminatedPods(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_106"
+ name = "Ensure that the --terminated-pod-gc-threshold argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kube-controller-manager" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--terminated-pod-gc-threshold'):
+ threshold = command.split("=")[1]
+ if int(threshold) > 0:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = KubeControllerManagerTerminatedPods()
diff --git a/checkov/kubernetes/checks/KubeletAnonymousAuth.py b/checkov/kubernetes/checks/KubeletAnonymousAuth.py
new file mode 100644
index 0000000000..fe3e835da6
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletAnonymousAuth.py
@@ -0,0 +1,26 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletAnonymousAuth(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.1
+ id = "CKV_K8S_138"
+ name = "Ensure that the --anonymous-auth argument is set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--anonymous-auth=true" in conf["command"] or "--anonymous-auth=false" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletAnonymousAuth()
diff --git a/checkov/kubernetes/checks/KubeletAuthorizationModeNotAlwaysAllow.py b/checkov/kubernetes/checks/KubeletAuthorizationModeNotAlwaysAllow.py
new file mode 100644
index 0000000000..76dbd14869
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletAuthorizationModeNotAlwaysAllow.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class KubeletAuthorizationModeNotAlwaysAllow(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.
+ id = "CKV_K8S_139"
+ name = "Ensure that the --authorization-mode argument is not set to AlwaysAllow"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kubelet" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith("--authorization-mode"):
+ modes = command.split("=")[1]
+ if "AlwaysAllow" in modes.split(","):
+ return CheckResult.FAILED
+ break
+
+ return CheckResult.PASSED
+
+check = KubeletAuthorizationModeNotAlwaysAllow()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/KubeletClientCa.py b/checkov/kubernetes/checks/KubeletClientCa.py
new file mode 100644
index 0000000000..9892293381
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletClientCa.py
@@ -0,0 +1,33 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletClientCa(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.3
+ id = "CKV_K8S_140"
+ name = "Ensure that the --client-ca-file argument is set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith('--root-ca-file'):
+ file_name = command.split("=")[1]
+ extension = file_name.split(".")[1]
+ if extension == 'pem':
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletClientCa()
diff --git a/checkov/kubernetes/checks/KubeletCryptographicCiphers.py b/checkov/kubernetes/checks/KubeletCryptographicCiphers.py
new file mode 100644
index 0000000000..a30cd7f666
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletCryptographicCiphers.py
@@ -0,0 +1,32 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+strongCiphers = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256"]
+
+class KubeletCryptographicCiphers(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.13
+ id = "CKV_K8S_151"
+ name = "Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ for command in conf["command"]:
+ if command.startswith("--tls-cipher-suites"):
+ value = command.split("=")[1]
+ ciphers = value.split(",")
+ for cipher in ciphers:
+ if cipher not in strongCiphers:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletCryptographicCiphers()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/KubeletHostnameOverride.py b/checkov/kubernetes/checks/KubeletHostnameOverride.py
new file mode 100644
index 0000000000..c507579e19
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletHostnameOverride.py
@@ -0,0 +1,26 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletHostnameOverride(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.8
+ id = "CKV_K8S_146"
+ name = "Ensure that the --hostname-override argument is not set"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--hostname-override" in [arg.split("=")[0] for arg in conf["command"]]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletHostnameOverride()
diff --git a/checkov/kubernetes/checks/KubeletKeyFilesSetAppropriate.py b/checkov/kubernetes/checks/KubeletKeyFilesSetAppropriate.py
new file mode 100644
index 0000000000..f6ea7e31f7
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletKeyFilesSetAppropriate.py
@@ -0,0 +1,33 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletKeyFilesSetAppropriate(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.10
+ id = "CKV_K8S_148"
+ name = "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if conf.get("command") is not None:
+ if "kubelet" in conf["command"]:
+ hasTLSCert = False
+ hasTLSKey = False
+ for command in conf["command"]:
+ if command.startswith("--tls-cert-file"):
+ hasTLSCert = True
+ elif command.startswith("--tls-private-key-file"):
+ hasTLSKey = True
+ return CheckResult.PASSED if hasTLSCert and hasTLSKey else CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletKeyFilesSetAppropriate()
diff --git a/checkov/kubernetes/checks/KubeletMakeIptablesUtilChains.py b/checkov/kubernetes/checks/KubeletMakeIptablesUtilChains.py
new file mode 100644
index 0000000000..8879fa9584
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletMakeIptablesUtilChains.py
@@ -0,0 +1,27 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletMakeIptablesUtilChains(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.7
+ id = "CKV_K8S_145"
+ name = "Ensure that the --make-iptables-util-chains argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--make-iptables-util-chains=true" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletMakeIptablesUtilChains()
diff --git a/checkov/kubernetes/checks/KubeletProtectKernelDefaults.py b/checkov/kubernetes/checks/KubeletProtectKernelDefaults.py
new file mode 100644
index 0000000000..7d9f098ef7
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletProtectKernelDefaults.py
@@ -0,0 +1,27 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletProtectKernelDefaults(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.6
+ id = "CKV_K8S_144"
+ name = "Ensure that the --protect-kernel-defaults argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--protect-kernel-defaults=true" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletProtectKernelDefaults()
diff --git a/checkov/kubernetes/checks/KubeletReadOnlyPort.py b/checkov/kubernetes/checks/KubeletReadOnlyPort.py
new file mode 100644
index 0000000000..974ea36e8d
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletReadOnlyPort.py
@@ -0,0 +1,31 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.kubernetes.checks.k8s_check_utils import extract_commands
+
+
+class KubeletReadOnlyPort(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.4
+ id = "CKV_K8S_141"
+ name = "Ensure that the --read-only-port argument is set to 0"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ keys, values = extract_commands(conf)
+
+ if "kubelet" in keys:
+ if '--read-only-port' in keys and values[keys.index('--read-only-port')] == "0":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletReadOnlyPort()
diff --git a/checkov/kubernetes/checks/KubeletStreamingConnectionIdleTimeout.py b/checkov/kubernetes/checks/KubeletStreamingConnectionIdleTimeout.py
new file mode 100644
index 0000000000..9f9236412b
--- /dev/null
+++ b/checkov/kubernetes/checks/KubeletStreamingConnectionIdleTimeout.py
@@ -0,0 +1,27 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubeletStreamingConnectionIdleTimeout(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.5
+ id = "CKV_K8S_143"
+ name = "Ensure that the --streaming-connection-idle-timeout argument is not set to 0"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--streaming-connection-idle-timeout=0" in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubeletStreamingConnectionIdleTimeout()
diff --git a/checkov/kubernetes/checks/KubernetesDashboard.py b/checkov/kubernetes/checks/KubernetesDashboard.py
index 92a0bf48cb..177a81880f 100644
--- a/checkov/kubernetes/checks/KubernetesDashboard.py
+++ b/checkov/kubernetes/checks/KubernetesDashboard.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "image" in conf:
@@ -25,7 +25,7 @@ def scan_spec_conf(self, conf):
else:
return CheckResult.FAILED
if "parent_metadata" in conf:
- if "labels" in conf["parent_metadata"]:
+ if conf["parent_metadata"].get("labels"):
if "app" in conf["parent_metadata"]["labels"]:
if conf["parent_metadata"]["labels"]["app"] == "kubernetes-dashboard":
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/KubletEventCapture.py b/checkov/kubernetes/checks/KubletEventCapture.py
new file mode 100644
index 0000000000..fea2f29002
--- /dev/null
+++ b/checkov/kubernetes/checks/KubletEventCapture.py
@@ -0,0 +1,32 @@
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubletEventCapture(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.9
+ id = "CKV_K8S_147"
+ name = "Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ for cmd in conf["command"]:
+ if "=" in cmd:
+ [key, value, *_] = cmd.split("=")
+ if key == "--event-qps":
+ if int(value) > 5:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubletEventCapture()
diff --git a/checkov/kubernetes/checks/KubletRotateCertificates.py b/checkov/kubernetes/checks/KubletRotateCertificates.py
new file mode 100644
index 0000000000..9735f74a9a
--- /dev/null
+++ b/checkov/kubernetes/checks/KubletRotateCertificates.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubletRotateCertificates(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.11
+ id = "CKV_K8S_149"
+ name = "Ensure that the --rotate-certificates argument is not set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ if "--rotate-certificates=false" in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubletRotateCertificates()
diff --git a/checkov/kubernetes/checks/KubletRotateKubeletServerCertificate.py b/checkov/kubernetes/checks/KubletRotateKubeletServerCertificate.py
new file mode 100644
index 0000000000..d2e9895fe8
--- /dev/null
+++ b/checkov/kubernetes/checks/KubletRotateKubeletServerCertificate.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class KubletRotateKubeletServerCertificate(BaseK8Check):
+ def __init__(self):
+ # CIS-1.6 4.2.12
+ id = "CKV_K8S_150"
+ name = "Ensure that the RotateKubeletServerCertificate argument is set to true"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kubelet" in conf["command"]:
+ for cmd in conf["command"]:
+ if cmd.startswith("--feature-gates"):
+ value = cmd[cmd.index("=")+1:]
+ if 'RotateKubeletServerCertificate=false' in value:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = KubletRotateKubeletServerCertificate()
\ No newline at end of file
diff --git a/checkov/kubernetes/checks/LivenessProbe.py b/checkov/kubernetes/checks/LivenessProbe.py
index 14ee30ab68..d7616d49f1 100644
--- a/checkov/kubernetes/checks/LivenessProbe.py
+++ b/checkov/kubernetes/checks/LivenessProbe.py
@@ -14,7 +14,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
# Don't check Job/CronJob
diff --git a/checkov/kubernetes/checks/MemoryLimits.py b/checkov/kubernetes/checks/MemoryLimits.py
index bb661f91ec..54805c1327 100644
--- a/checkov/kubernetes/checks/MemoryLimits.py
+++ b/checkov/kubernetes/checks/MemoryLimits.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if conf.get("resources"):
diff --git a/checkov/kubernetes/checks/MemoryRequests.py b/checkov/kubernetes/checks/MemoryRequests.py
index ae807631e6..f0582bc3db 100644
--- a/checkov/kubernetes/checks/MemoryRequests.py
+++ b/checkov/kubernetes/checks/MemoryRequests.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if conf.get("resources"):
diff --git a/checkov/kubernetes/checks/MinimizeCapabilities.py b/checkov/kubernetes/checks/MinimizeCapabilities.py
index e0bad67cc7..56c054db68 100644
--- a/checkov/kubernetes/checks/MinimizeCapabilities.py
+++ b/checkov/kubernetes/checks/MinimizeCapabilities.py
@@ -14,14 +14,14 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
- if "securityContext" in conf:
- if "capabilities" in conf["securityContext"]:
- if "drop" in conf["securityContext"]["capabilities"]:
+ if conf.get("securityContext"):
+ if conf["securityContext"].get("capabilities"):
+ if conf["securityContext"]["capabilities"].get("drop"):
for d in conf["securityContext"]["capabilities"]["drop"]:
- if "ALL" in d:
+ if any(cap in d for cap in ("ALL", "all")):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/PeerClientCertAuthTrue.py b/checkov/kubernetes/checks/PeerClientCertAuthTrue.py
new file mode 100644
index 0000000000..a7b4b89314
--- /dev/null
+++ b/checkov/kubernetes/checks/PeerClientCertAuthTrue.py
@@ -0,0 +1,31 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class PeerClientCertAuthTrue(BaseK8Check):
+
+ def __init__(self):
+ name = "Ensure that the --peer-client-cert-auth argument is set to true"
+ id = "CKV_K8S_121"
+ supported_kind = ['Pod']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ if "namespace" in conf["metadata"]:
+ return "{}.{}.{}".format(conf["kind"], conf["metadata"]["name"], conf["metadata"]["namespace"])
+ else:
+ return "{}.{}.default".format(conf["kind"], conf["metadata"]["name"])
+
+ def scan_spec_conf(self, conf, entity_type=None):
+ if conf.get("metadata")['name'] == 'etcd':
+ containers = conf.get('spec')['containers']
+ for container in containers:
+ if container.get("args") is not None:
+ if '--peer-client-cert-auth=true' not in container['args']:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+ return CheckResult.UNKNOWN
+
+
+check = PeerClientCertAuthTrue()
diff --git a/checkov/kubernetes/checks/PodSecurityContext.py b/checkov/kubernetes/checks/PodSecurityContext.py
index 3bda3d25fc..15aba4d1a6 100644
--- a/checkov/kubernetes/checks/PodSecurityContext.py
+++ b/checkov/kubernetes/checks/PodSecurityContext.py
@@ -37,10 +37,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
if spec:
if "securityContext" in spec:
diff --git a/checkov/kubernetes/checks/PrivilegedContainers.py b/checkov/kubernetes/checks/PrivilegedContainers.py
index a21b710340..048073f526 100644
--- a/checkov/kubernetes/checks/PrivilegedContainers.py
+++ b/checkov/kubernetes/checks/PrivilegedContainers.py
@@ -15,7 +15,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "securityContext" in conf:
diff --git a/checkov/kubernetes/checks/ReadOnlyFilesystem.py b/checkov/kubernetes/checks/ReadOnlyFilesystem.py
index 2ba9c2d581..22421bcfc6 100644
--- a/checkov/kubernetes/checks/ReadOnlyFilesystem.py
+++ b/checkov/kubernetes/checks/ReadOnlyFilesystem.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "securityContext" in conf:
diff --git a/checkov/kubernetes/checks/ReadinessProbe.py b/checkov/kubernetes/checks/ReadinessProbe.py
index 9e05a9133e..8e3b75c0b4 100644
--- a/checkov/kubernetes/checks/ReadinessProbe.py
+++ b/checkov/kubernetes/checks/ReadinessProbe.py
@@ -14,7 +14,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
# Don't check Job/CronJob (or Pods in runtime derived from Job/CronJob)
diff --git a/checkov/kubernetes/checks/RootContainers.py b/checkov/kubernetes/checks/RootContainers.py
index 45cec034ed..3019e38811 100644
--- a/checkov/kubernetes/checks/RootContainers.py
+++ b/checkov/kubernetes/checks/RootContainers.py
@@ -40,10 +40,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
# Collect results
if spec:
@@ -53,7 +51,7 @@ def scan_spec_conf(self, conf):
results["pod"]["runAsNonRoot"] = check_runAsNonRoot(spec)
results["pod"]["runAsUser"] = check_runAsUser(spec)
- if "containers" in spec:
+ if spec.get("containers"):
for c in spec["containers"]:
cresults = {}
cresults["runAsNonRoot"] = check_runAsNonRoot(c)
diff --git a/checkov/kubernetes/checks/RootContainersHighUID.py b/checkov/kubernetes/checks/RootContainersHighUID.py
index ac4eb04afe..02f0189eee 100644
--- a/checkov/kubernetes/checks/RootContainersHighUID.py
+++ b/checkov/kubernetes/checks/RootContainersHighUID.py
@@ -38,10 +38,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
# Collect results
if spec:
@@ -50,7 +48,7 @@ def scan_spec_conf(self, conf):
results["container"] = []
results["pod"]["runAsUser"] = check_runAsUser(spec)
- if "containers" in spec:
+ if spec.get("containers"):
for c in spec["containers"]:
cresults = {}
cresults["runAsUser"] = check_runAsUser(c)
diff --git a/checkov/kubernetes/checks/SchedulerBindAddress.py b/checkov/kubernetes/checks/SchedulerBindAddress.py
new file mode 100644
index 0000000000..de7c41aaa9
--- /dev/null
+++ b/checkov/kubernetes/checks/SchedulerBindAddress.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class SchedulerBindAddress(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_115"
+ name = "Ensure that the --bind-address argument is set to 127.0.0.1"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-scheduler" in conf["command"]:
+ for cmd in conf["command"]:
+ if "=" in cmd:
+ [key, value, *_] = cmd.split("=")
+ if key == "--bind-address" and value == "127.0.0.1":
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = SchedulerBindAddress()
diff --git a/checkov/kubernetes/checks/SchedulerProfiling.py b/checkov/kubernetes/checks/SchedulerProfiling.py
new file mode 100644
index 0000000000..9c31e56d8e
--- /dev/null
+++ b/checkov/kubernetes/checks/SchedulerProfiling.py
@@ -0,0 +1,26 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+
+class SchedulerProfiling(BaseK8Check):
+ def __init__(self):
+ id = "CKV_K8S_114"
+ name = "Ensure that the --profiling argument is set to false"
+ categories = [CheckCategories.KUBERNETES]
+ supported_entities = ['containers']
+ super().__init__(name=name, id=id, categories=categories,
+ supported_entities=supported_entities)
+
+ def get_resource_id(self, conf):
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
+
+ def scan_spec_conf(self, conf):
+ if "command" in conf:
+ if "kube-scheduler" in conf["command"]:
+ if "--profiling=false" not in conf["command"]:
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+
+check = SchedulerProfiling()
diff --git a/checkov/kubernetes/checks/Seccomp.py b/checkov/kubernetes/checks/Seccomp.py
index fdd9392b4e..0385e8c190 100644
--- a/checkov/kubernetes/checks/Seccomp.py
+++ b/checkov/kubernetes/checks/Seccomp.py
@@ -2,6 +2,7 @@
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.kubernetes.base_spec_check import BaseK8Check
+from checkov.common.util.type_forcers import force_list
class Seccomp(BaseK8Check):
@@ -34,6 +35,20 @@ def scan_spec_conf(self, conf):
return CheckResult.PASSED if security_profile == 'RuntimeDefault' else CheckResult.FAILED
if "metadata" in conf:
metadata = conf["metadata"]
+ if conf['kind'] == 'Deployment':
+ security_profile = dpath.search(conf, 'spec/template/spec/securityContext/seccompProfile/type')
+ if security_profile:
+ security_profile = dpath.get(conf, 'spec/template/spec/securityContext/seccompProfile/type')
+ return CheckResult.PASSED if security_profile == 'RuntimeDefault' else CheckResult.FAILED
+ if "metadata" in conf:
+ metadata = conf["metadata"]
+ if conf['kind'] == 'StatefulSet':
+ security_profile = dpath.search(conf, 'spec/template/spec/securityContext/seccompProfile/type')
+ if security_profile:
+ security_profile = dpath.get(conf, 'spec/template/spec/securityContext/seccompProfile/type')
+ return CheckResult.PASSED if security_profile == 'RuntimeDefault' else CheckResult.FAILED
+ if "metadata" in conf:
+ metadata = conf["metadata"]
elif conf['kind'] == 'CronJob':
if "spec" in conf:
if "jobTemplate" in conf["spec"]:
@@ -42,17 +57,16 @@ def scan_spec_conf(self, conf):
if "metadata" in conf["spec"]["jobTemplate"]["spec"]["template"]:
metadata = conf["spec"]["jobTemplate"]["spec"]["template"]["metadata"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "metadata" in conf["spec"]["template"]:
- metadata = conf["spec"]["template"]["metadata"]
+ inner_metadata = self.get_inner_entry(conf, "metadata")
+ metadata = inner_metadata if inner_metadata else metadata
if metadata:
- if "annotations" in metadata and isinstance(metadata['annotations'], dict):
- if "seccomp.security.alpha.kubernetes.io/pod" in metadata["annotations"]:
- if ("docker/default" in metadata["annotations"]["seccomp.security.alpha.kubernetes.io/pod"] or
- "runtime/default" in metadata["annotations"]["seccomp.security.alpha.kubernetes.io/pod"]):
- return CheckResult.PASSED
+ if metadata.get('annotations'):
+ for annotation in force_list(metadata["annotations"]):
+ for key in annotation:
+ if "seccomp.security.alpha.kubernetes.io/pod" in key:
+ if "docker/default" in annotation[key] or "runtime/default" in annotation[key]:
+ return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/SeccompPSP.py b/checkov/kubernetes/checks/SeccompPSP.py
index 92f527d3aa..ce3e98557e 100644
--- a/checkov/kubernetes/checks/SeccompPSP.py
+++ b/checkov/kubernetes/checks/SeccompPSP.py
@@ -21,11 +21,12 @@ def get_resource_id(self, conf):
def scan_spec_conf(self, conf):
if "metadata" in conf:
- if "annotations" in conf["metadata"]:
- if "seccomp.security.alpha.kubernetes.io/defaultProfileName" in conf["metadata"]["annotations"]:
- if ("docker/default" in conf["metadata"]["annotations"]["seccomp.security.alpha.kubernetes.io/defaultProfileName"] or
- "runtime/default" in conf["metadata"]["annotations"]["seccomp.security.alpha.kubernetes.io/defaultProfileName"]):
- return CheckResult.PASSED
+ if "annotations" in conf["metadata"] and conf["metadata"].get("annotations"):
+ for annotation in conf["metadata"]["annotations"]:
+ for key in annotation:
+ if "seccomp.security.alpha.kubernetes.io/defaultProfileName" in key:
+ if "docker/default" in annotation[key] or "runtime/default" in annotation[key]:
+ return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/kubernetes/checks/Secrets.py b/checkov/kubernetes/checks/Secrets.py
index 174fbaaed6..251dfe38ce 100644
--- a/checkov/kubernetes/checks/Secrets.py
+++ b/checkov/kubernetes/checks/Secrets.py
@@ -14,7 +14,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
if "env" in conf:
diff --git a/checkov/kubernetes/checks/ServiceAccountTokens.py b/checkov/kubernetes/checks/ServiceAccountTokens.py
index bd36f1d9cb..fe78ad9bdd 100644
--- a/checkov/kubernetes/checks/ServiceAccountTokens.py
+++ b/checkov/kubernetes/checks/ServiceAccountTokens.py
@@ -37,10 +37,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
# Collect results
if spec:
diff --git a/checkov/kubernetes/checks/ShareHostIPC.py b/checkov/kubernetes/checks/ShareHostIPC.py
index 6bd8956b25..4fcc2b2e28 100644
--- a/checkov/kubernetes/checks/ShareHostIPC.py
+++ b/checkov/kubernetes/checks/ShareHostIPC.py
@@ -36,10 +36,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
if spec:
if "hostIPC" in spec:
if spec["hostIPC"]:
diff --git a/checkov/kubernetes/checks/ShareHostPID.py b/checkov/kubernetes/checks/ShareHostPID.py
index 49ae56bfc0..8d76e91ba3 100644
--- a/checkov/kubernetes/checks/ShareHostPID.py
+++ b/checkov/kubernetes/checks/ShareHostPID.py
@@ -36,10 +36,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
if spec:
if "hostPID" in spec:
if spec["hostPID"]:
diff --git a/checkov/kubernetes/checks/SharedHostNetworkNamespace.py b/checkov/kubernetes/checks/SharedHostNetworkNamespace.py
index 40216eb633..90c1261e84 100644
--- a/checkov/kubernetes/checks/SharedHostNetworkNamespace.py
+++ b/checkov/kubernetes/checks/SharedHostNetworkNamespace.py
@@ -36,10 +36,8 @@ def scan_spec_conf(self, conf):
if "spec" in conf["spec"]["jobTemplate"]["spec"]["template"]:
spec = conf["spec"]["jobTemplate"]["spec"]["template"]["spec"]
else:
- if "spec" in conf:
- if "template" in conf["spec"]:
- if "spec" in conf["spec"]["template"]:
- spec = conf["spec"]["template"]["spec"]
+ inner_spec = self.get_inner_entry(conf, "spec")
+ spec = inner_spec if inner_spec else spec
if spec:
if "hostNetwork" in spec:
if spec["hostNetwork"]:
diff --git a/checkov/kubernetes/checks/Tiller.py b/checkov/kubernetes/checks/Tiller.py
index 5da69de14c..b29bd905ad 100644
--- a/checkov/kubernetes/checks/Tiller.py
+++ b/checkov/kubernetes/checks/Tiller.py
@@ -13,7 +13,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
return CheckResult.FAILED if self.is_tiller(conf) else CheckResult.PASSED
@@ -25,12 +25,12 @@ def is_tiller(conf):
if isinstance(conf_image,str) and "tiller" in conf_image:
return True
- if "parent_metadata" in conf:
- if "labels" in conf["parent_metadata"]:
- if "app" in conf["parent_metadata"]["labels"]:
+ if conf["parent_metadata"]:
+ if conf["parent_metadata"].get("labels"):
+ if conf["parent_metadata"]["labels"].get("app"):
if conf["parent_metadata"]["labels"]["app"] == "helm":
return True
- elif "name" in conf["parent_metadata"]["labels"]:
+ elif conf["parent_metadata"]["labels"].get("name"):
if conf["parent_metadata"]["labels"]["name"] == "tiller":
return True
diff --git a/checkov/kubernetes/checks/TillerDeploymentListener.py b/checkov/kubernetes/checks/TillerDeploymentListener.py
index 19e9a5a607..278b2c6f45 100644
--- a/checkov/kubernetes/checks/TillerDeploymentListener.py
+++ b/checkov/kubernetes/checks/TillerDeploymentListener.py
@@ -14,7 +14,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
def get_resource_id(self, conf):
- return f'{conf["parent"]} - {conf["name"]}'
+ return f'{conf["parent"]} - {conf["name"]}' if conf.get('name') else conf["parent"]
def scan_spec_conf(self, conf):
diff --git a/checkov/kubernetes/checks/WildcardRoles.py b/checkov/kubernetes/checks/WildcardRoles.py
new file mode 100644
index 0000000000..4aa87d4184
--- /dev/null
+++ b/checkov/kubernetes/checks/WildcardRoles.py
@@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.kubernetes.base_spec_check import BaseK8Check
+
+class WildcardRoles(BaseK8Check):
+ # CIS-1.6 5.1.3
+ def __init__(self):
+ name = "Minimize wildcard use in Roles and ClusterRoles"
+ id = "CKV_K8S_49"
+ categories = [CheckCategories.KUBERNETES]
+ supported_kind = ['Role', 'ClusterRole']
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_kind)
+
+ def get_resource_id(self, conf):
+ if "namespace" in conf["metadata"]:
+ return "{}.{}.{}".format(conf["kind"], conf["metadata"]["name"], conf["metadata"]["namespace"])
+ else:
+ return "{}.{}.default".format(conf["kind"], conf["metadata"]["name"])
+
+ def scan_spec_conf(self, conf):
+ if isinstance(conf.get("rules"), list) and len(conf.get("rules")) > 0:
+ if "apiGroups" in conf["rules"][0]:
+ if any("*" in s for s in conf["rules"][0]["apiGroups"]):
+ return CheckResult.FAILED
+ if "resources" in conf["rules"][0]:
+ if any("*" in s for s in conf["rules"][0]["resources"]):
+ return CheckResult.FAILED
+ if "verbs" in conf["rules"][0]:
+ if any("*" in s for s in conf["rules"][0]["verbs"]):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+check = WildcardRoles()
diff --git a/checkov/kubernetes/checks/k8s_check_utils.py b/checkov/kubernetes/checks/k8s_check_utils.py
new file mode 100644
index 0000000000..8ed1b6598f
--- /dev/null
+++ b/checkov/kubernetes/checks/k8s_check_utils.py
@@ -0,0 +1,21 @@
+from typing import List
+
+
+def extract_commands(conf: dict) -> (List[str], List[str]):
+ commands: List[str] = conf.get("command")
+ if not commands:
+ return [], []
+ values = []
+ keys = []
+ for cmd in commands:
+ if cmd is None:
+ continue
+ if "=" in cmd:
+ firstEqual = cmd.index("=")
+ [key, value] = [cmd[:firstEqual], cmd[firstEqual + 1:]]
+ keys.append(key)
+ values.append(value)
+ else:
+ keys.append(cmd)
+ values.append(None)
+ return keys, values
diff --git a/checkov/kubernetes/parser/parser.py b/checkov/kubernetes/parser/parser.py
index b5052632d1..b6210469a8 100644
--- a/checkov/kubernetes/parser/parser.py
+++ b/checkov/kubernetes/parser/parser.py
@@ -14,6 +14,7 @@
def parse(filename):
template = None
template_lines = None
+ valid_templates = []
try:
if filename.endswith(".yaml") or filename.endswith(".yml"):
(template, template_lines) = k8_yaml.load(filename)
@@ -21,12 +22,9 @@ def parse(filename):
(template, template_lines) = k8_json.load(filename)
if template:
if isinstance(template, list):
- for i in range(len(template)):
- if isinstance(template[i], dict):
- if not ('apiVersion' in template[i].keys() and 'kind' in template[i].keys()):
- return
- else:
- return
+ for t in template:
+ if t and isinstance(t, dict) and 'apiVersion' in t.keys() and 'kind' in t.keys():
+ valid_templates.append(t)
else:
return
else:
@@ -51,4 +49,4 @@ def parse(filename):
logger.debug('Cannot read file contents: %s - is it a yaml?', filename)
return
- return template, template_lines
+ return valid_templates, template_lines
diff --git a/checkov/kubernetes/runner.py b/checkov/kubernetes/runner.py
index 49516ed5a9..51334e18c2 100644
--- a/checkov/kubernetes/runner.py
+++ b/checkov/kubernetes/runner.py
@@ -3,9 +3,11 @@
import os
from functools import reduce
+from checkov.common.bridgecrew.platform_integration import bc_integration
+from checkov.common.util.type_forcers import force_list
from checkov.common.output.record import Record
from checkov.common.output.report import Report
-from checkov.common.runners.base_runner import BaseRunner, filter_ignored_directories
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
from checkov.kubernetes.parser.parser import parse
from checkov.kubernetes.registry import registry
from checkov.runner_filter import RunnerFilter
@@ -16,7 +18,7 @@
class Runner(BaseRunner):
check_type = "kubernetes"
- def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True):
+ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True, helmChart=None):
report = Report(self.check_type)
definitions = {}
definitions_raw = {}
@@ -24,7 +26,7 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
files_list = []
if external_checks_dir:
for directory in external_checks_dir:
- registry.load_external_checks(directory, runner_filter)
+ registry.load_external_checks(directory)
if files:
for file in files:
@@ -34,7 +36,8 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if root_folder:
for root, d_names, f_names in os.walk(root_folder):
- filter_ignored_directories(d_names)
+ filter_ignored_paths(root, d_names, runner_filter.excluded_paths)
+ filter_ignored_paths(root, f_names, runner_filter.excluded_paths)
for file in f_names:
file_ending = os.path.splitext(file)[1]
@@ -46,9 +49,13 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
for file in files_list:
relative_file_path = f'/{os.path.relpath(file, os.path.commonprefix((root_folder, file)))}'
- parse_result = parse(file)
- if parse_result:
- (definitions[relative_file_path], definitions_raw[relative_file_path]) = parse_result
+ try:
+ parse_result = parse(file)
+ if parse_result:
+ (definitions[relative_file_path], definitions_raw[relative_file_path]) = parse_result
+ except (TypeError, ValueError) as e:
+ logging.warning(f"Kubernetes skipping {file} as it is not a valid Kubernetes template\n{e}")
+ continue
for k8_file in definitions.keys():
@@ -70,25 +77,27 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
logging.debug("Template Dump for {}: {}".format(k8_file, definitions[k8_file][i], indent=2))
entity_conf = definitions[k8_file][i]
+ if entity_conf is None:
+ continue
# Split out resources if entity kind is List
- if entity_conf["kind"] == "List":
- for item in entity_conf["items"]:
+ if isinstance(entity_conf, dict) and entity_conf["kind"] == "List":
+ for item in entity_conf.get("items", []):
definitions[k8_file].append(item)
for i in range(len(definitions[k8_file])):
- if (not 'apiVersion' in definitions[k8_file][i].keys()) and (not 'kind' in definitions[k8_file][i].keys()):
+ if _is_invalid_k8_definition(definitions[k8_file][i]):
continue
logging.debug("Template Dump for {}: {}".format(k8_file, definitions[k8_file][i], indent=2))
entity_conf = definitions[k8_file][i]
- if entity_conf["kind"] == "List":
+ if isinstance(entity_conf, dict) and entity_conf.get("kind") == "List":
continue
# Skip entity without metadata["name"]
- if "metadata" in entity_conf:
- if isinstance(entity_conf["metadata"], int) or not "name" in entity_conf["metadata"]:
+ if isinstance(entity_conf, dict) and entity_conf.get("metadata"):
+ if isinstance(entity_conf["metadata"], int) or "name" not in entity_conf["metadata"]:
continue
else:
continue
@@ -128,20 +137,21 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
# Run for each definition included added container definitions
for i in range(len(definitions[k8_file])):
- if (not 'apiVersion' in definitions[k8_file][i].keys()) and (not 'kind' in definitions[k8_file][i].keys()):
+ if _is_invalid_k8_definition(definitions[k8_file][i]):
continue
logging.debug("Template Dump for {}: {}".format(k8_file, definitions[k8_file][i], indent=2))
entity_conf = definitions[k8_file][i]
-
- if entity_conf["kind"] == "List":
+ if entity_conf is None:
+ continue
+ if isinstance(entity_conf, dict) and (entity_conf["kind"] == "List" or not entity_conf.get("kind")):
continue
- if isinstance(entity_conf["kind"], int):
+ if isinstance(entity_conf, dict) and isinstance(entity_conf.get("kind"), int):
continue
# Skip entity without metadata["name"] or parent_metadata["name"]
if not any(x in entity_conf["kind"] for x in ["containers", "initContainers"]):
- if "metadata" in entity_conf:
+ if entity_conf.get("metadata"):
if isinstance(entity_conf["metadata"], int) or not "name" in entity_conf["metadata"]:
continue
else:
@@ -178,7 +188,7 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
variable_evaluations = {}
for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=k8_file,
file_line_range=entity_lines_range,
resource=check.get_resource_id(entity_conf), evaluations=variable_evaluations,
@@ -220,6 +230,8 @@ def _search_deep_keys(self, search_text, k8n_dict, path):
def get_skipped_checks(entity_conf):
skipped = []
metadata = {}
+ bc_id_mapping = bc_integration.get_id_mapping()
+ ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
if not isinstance(entity_conf,dict):
return skipped
if entity_conf["kind"] == "containers" or entity_conf["kind"] == "initContainers":
@@ -228,19 +240,32 @@ def get_skipped_checks(entity_conf):
if "metadata" in entity_conf.keys():
metadata = entity_conf["metadata"]
if "annotations" in metadata.keys() and metadata["annotations"] is not None:
- for key in metadata["annotations"].keys():
- skipped_item = {}
- if "checkov.io/skip" in key or "bridgecrew.io/skip" in key:
- if "CKV_K8S" in metadata["annotations"][key]:
- if "=" in metadata["annotations"][key]:
- (skipped_item["id"], skipped_item["suppress_comment"]) = metadata["annotations"][key].split("=")
+ if isinstance(metadata["annotations"], dict):
+ metadata["annotations"] = force_list(metadata["annotations"])
+ for annotation in metadata["annotations"]:
+ if not isinstance(annotation, dict):
+ logging.debug( f"Parse of Annotation Failed for {annotation}: {entity_conf}")
+ continue
+ for key in annotation:
+ skipped_item = {}
+ if "checkov.io/skip" in key or "bridgecrew.io/skip" in key:
+ if "CKV_K8S" in annotation[key] or "BC_K8S" in annotation[key]:
+ if "=" in annotation[key]:
+ (skipped_item["id"], skipped_item["suppress_comment"]) = annotation[key].split("=")
+ else:
+ skipped_item["id"] = annotation[key]
+ skipped_item["suppress_comment"] = "No comment provided"
+
+ # No matter which ID was used to skip, save the pair of IDs in the appropriate fields
+ if bc_id_mapping and skipped_item["id"] in bc_id_mapping:
+ skipped_item["bc_id"] = skipped_item["id"]
+ skipped_item["id"] = bc_id_mapping[skipped_item["id"]]
+ elif ckv_to_bc_id_mapping:
+ skipped_item["bc_id"] = ckv_to_bc_id_mapping.get(skipped_item["id"])
+ skipped.append(skipped_item)
else:
- skipped_item["id"] = metadata["annotations"][key]
- skipped_item["suppress_comment"] = "No comment provided"
- skipped.append(skipped_item)
- else:
- logging.debug("Parse of Annotation Failed for {}: {}".format(metadata["annotations"][key], entity_conf, indent=2))
- continue
+ logging.debug("Parse of Annotation Failed for {}: {}".format(metadata["annotations"][key], entity_conf, indent=2))
+ continue
return skipped
def _get_from_dict(data_dict, map_list):
@@ -266,3 +291,6 @@ def find_lines(node, kv):
yield x
+def _is_invalid_k8_definition(definition: dict) -> bool:
+ return isinstance(definition, dict) and 'apiVersion' not in definition.keys() and 'kind' not in \
+ definition.keys()
diff --git a/checkov/logging_init.py b/checkov/logging_init.py
index 5008639b21..43fbf3a242 100644
--- a/checkov/logging_init.py
+++ b/checkov/logging_init.py
@@ -7,12 +7,12 @@
def init():
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'WARNING').upper()
logging.basicConfig(level=LOG_LEVEL)
- logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
- rootLogger = logging.getLogger()
- consoleHandler = logging.StreamHandler(sys.stdout)
- consoleHandler.setFormatter(logFormatter)
- consoleHandler.setLevel(LOG_LEVEL)
- rootLogger.addHandler(consoleHandler)
+ log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
+ root_logger = logging.getLogger()
+ stream_handler = root_logger.handlers[0]
+ stream_handler.setFormatter(log_formatter)
+ root_logger.setLevel(LOG_LEVEL)
+ logging.getLogger().setLevel(LOG_LEVEL)
logging.getLogger("urllib3").setLevel(logging.ERROR)
logging.getLogger("urllib3.connectionpool").setLevel(logging.ERROR)
logging.getLogger("urllib3.connectionpool").propagate = False
diff --git a/checkov/main.py b/checkov/main.py
index 8ec98b2666..2b664e1483 100644
--- a/checkov/main.py
+++ b/checkov/main.py
@@ -1,90 +1,221 @@
#!/usr/bin/env python
import atexit
-
-import argparse
+import json
+import logging
import os
import shutil
import sys
from pathlib import Path
+import configargparse
+
from checkov.arm.runner import Runner as arm_runner
from checkov.cloudformation.runner import Runner as cfn_runner
+from checkov.common.bridgecrew.bc_source import SourceTypes, BCSourceType, get_source_type
+from checkov.common.bridgecrew.image_scanning.image_scanner import image_scanner
+from checkov.common.bridgecrew.integration_features.integration_feature_registry import integration_feature_registry
from checkov.common.bridgecrew.platform_integration import bc_integration
from checkov.common.goget.github.get_git import GitGetter
+from checkov.common.output.baseline import Baseline
from checkov.common.runners.runner_registry import RunnerRegistry, OUTPUT_CHOICES
+from checkov.common.checks.base_check_registry import BaseCheckRegistry
from checkov.common.util.banner import banner as checkov_banner
+from checkov.common.util.config_utils import get_default_config_paths
from checkov.common.util.consts import DEFAULT_EXTERNAL_MODULES_DIR
from checkov.common.util.docs_generator import print_checks
+from checkov.common.util.ext_argument_parser import ExtArgumentParser
+from checkov.common.util.runner_dependency_handler import RunnerDependencyHandler
from checkov.common.util.type_forcers import convert_str_to_bool
+from checkov.dockerfile.runner import Runner as dockerfile_runner
+from checkov.helm.runner import Runner as helm_runner
from checkov.kubernetes.runner import Runner as k8_runner
from checkov.logging_init import init as logging_init
from checkov.runner_filter import RunnerFilter
+from checkov.secrets.runner import Runner as secrets_runner
from checkov.serverless.runner import Runner as sls_runner
from checkov.terraform.plan_runner import Runner as tf_plan_runner
-from checkov.terraform.runner import Runner as tf_runner
+from checkov.terraform.runner import Runner as tf_graph_runner
from checkov.version import version
outer_registry = None
logging_init()
+logger = logging.getLogger(__name__)
+checkov_runners = ['cloudformation', 'terraform', 'kubernetes', 'serverless', 'arm', 'terraform_plan', 'helm',
+ 'dockerfile', 'secrets']
+
+DEFAULT_RUNNERS = (tf_graph_runner(), cfn_runner(), k8_runner(),
+ sls_runner(), arm_runner(), tf_plan_runner(), helm_runner(),
+ dockerfile_runner(), secrets_runner())
def run(banner=checkov_banner, argv=sys.argv[1:]):
- parser = argparse.ArgumentParser(description='Infrastructure as code static analysis')
+ default_config_paths = get_default_config_paths(sys.argv[1:])
+ parser = ExtArgumentParser(description='Infrastructure as code static analysis',
+ default_config_files=default_config_paths,
+ config_file_parser_class=configargparse.YAMLConfigFileParser,
+ add_env_var_help=True)
add_parser_args(parser)
- args = parser.parse_args(argv)
- runner_filter = RunnerFilter(framework=args.framework, checks=args.check, skip_checks=args.skip_check,
- download_external_modules=convert_str_to_bool(args.download_external_modules),
- external_modules_download_path=args.external_modules_download_path,
- evaluate_variables=convert_str_to_bool(args.evaluate_variables))
+ config = parser.parse_args(argv)
+ # bridgecrew uses both the urllib3 and requests libraries, while checkov uses the requests library.
+ # Allow the user to specify a CA bundle to be used by both libraries.
+ bc_integration.setup_http_manager(config.ca_certificate)
+
+ # if a repo is passed in it'll save it. Otherwise a default will be created based on the file or dir
+ config.repo_id = bc_integration.persist_repo_id(config)
+ # if a bc_api_key is passed it'll save it. Otherwise it will check ~/.bridgecrew/credentials
+ config.bc_api_key = bc_integration.persist_bc_api_key(config)
+
+ excluded_paths = config.skip_path or []
+
+ runner_filter = RunnerFilter(framework=config.framework, skip_framework=config.skip_framework, checks=config.check,
+ skip_checks=config.skip_check,
+ download_external_modules=convert_str_to_bool(config.download_external_modules),
+ external_modules_download_path=config.external_modules_download_path,
+ evaluate_variables=convert_str_to_bool(config.evaluate_variables),
+ runners=checkov_runners, excluded_paths=excluded_paths,
+ all_external=config.run_all_external_checks)
if outer_registry:
runner_registry = outer_registry
runner_registry.runner_filter = runner_filter
else:
- runner_registry = RunnerRegistry(banner, runner_filter, tf_runner(), cfn_runner(), k8_runner(), sls_runner(),
- arm_runner(), tf_plan_runner())
- if args.version:
- print(version)
+ runner_registry = RunnerRegistry(banner, runner_filter, *DEFAULT_RUNNERS)
+
+ runnerDependencyHandler = RunnerDependencyHandler(runner_registry)
+ runnerDependencyHandler.validate_runner_deps()
+
+ if config.show_config:
+ print(parser.format_values())
return
- if args.bc_api_key:
- if args.repo_id is None:
+
+ if config.bc_api_key == '':
+ parser.error('The --bc-api-key flag was specified but the value was blank. If this value was passed as a '
+ 'secret, you may need to double check the mapping.')
+ elif config.bc_api_key:
+ logger.debug(f'Using API key ending with {config.bc_api_key[-8:]}')
+
+ if config.repo_id is None and not config.list:
+ # if you are only listing policies, then the API key will be used to fetch policies, but that's it,
+ # so the repo is not required
parser.error("--repo-id argument is required when using --bc-api-key")
- if len(args.repo_id.split('/')) != 2:
+ elif config.repo_id and len(config.repo_id.split('/')) != 2:
parser.error("--repo-id argument format should be 'organization/repository_name' E.g "
"bridgecrewio/checkov")
- bc_integration.setup_bridgecrew_credentials(bc_api_key=args.bc_api_key, repo_id=args.repo_id)
+
+ source_env_val = os.getenv('BC_SOURCE', 'cli')
+ source = get_source_type(source_env_val)
+ if source == SourceTypes[BCSourceType.DISABLED]:
+ logger.warning(f'Received unexpected value for BC_SOURCE: {source_env_val}; setting source to DISABLED')
+ source_version = os.getenv('BC_SOURCE_VERSION', version)
+ logger.debug(f'BC_SOURCE = {source.name}, version = {source_version}')
+
+ if config.list:
+ # This speeds up execution by not setting up upload credentials (since we won't upload anything anyways)
+ logger.debug('Using --list; setting source to DISABLED')
+ source = SourceTypes[BCSourceType.DISABLED]
+
+ try:
+ bc_integration.setup_bridgecrew_credentials(bc_api_key=config.bc_api_key, repo_id=config.repo_id,
+ skip_fixes=config.skip_fixes,
+ skip_suppressions=config.skip_suppressions,
+ skip_policy_download=config.skip_policy_download,
+ source=source, source_version=source_version, repo_branch=config.branch)
+ platform_excluded_paths = bc_integration.get_excluded_paths() or []
+ runner_filter.excluded_paths = runner_filter.excluded_paths + platform_excluded_paths
+ except Exception as e:
+ logger.error('An error occurred setting up the Bridgecrew platform integration. Please check your API token'
+ ' and try again.', exc_info=True)
+ return
+ else:
+ logger.debug('No API key found. Scanning locally only.')
+
+ if config.check and config.skip_check:
+ if any(item in runner_filter.checks for item in runner_filter.skip_checks):
+ parser.error("The check ids specified for '--check' and '--skip-check' must be mutually exclusive.")
+ return
+
+ integration_feature_registry.run_pre_scan()
guidelines = {}
- if not args.no_guide:
+ if not config.no_guide:
guidelines = bc_integration.get_guidelines()
- if args.check and args.skip_check:
- parser.error("--check and --skip-check can not be applied together. please use only one of them")
- return
- if args.list:
- print_checks(framework=args.framework)
+
+ ckv_to_bc_mapping = bc_integration.get_ckv_to_bc_id_mapping()
+ if ckv_to_bc_mapping:
+ all_checks = BaseCheckRegistry.get_all_registered_checks()
+ for check in all_checks:
+ check.bc_id = ckv_to_bc_mapping.get(check.id)
+
+ if config.list:
+ print_checks(framework=config.framework, use_bc_ids=config.output_bc_ids)
return
- external_checks_dir = get_external_checks_dir(args)
- if args.directory:
- for root_folder in args.directory:
- file = args.file
+
+ baseline = None
+ if config.baseline:
+ baseline = Baseline()
+ baseline.from_json(config.baseline)
+
+ external_checks_dir = get_external_checks_dir(config)
+ url = None
+ created_baseline_path = None
+
+ if config.directory:
+ exit_codes = []
+ for root_folder in config.directory:
+ file = config.file
scan_reports = runner_registry.run(root_folder=root_folder, external_checks_dir=external_checks_dir,
files=file, guidelines=guidelines)
+ if baseline:
+ baseline.compare_and_reduce_reports(scan_reports)
if bc_integration.is_integration_configured():
- bc_integration.persist_repository(root_folder)
+ bc_integration.persist_repository(root_folder, excluded_paths=runner_filter.excluded_paths)
bc_integration.persist_scan_results(scan_reports)
- bc_integration.commit_repository(args.branch)
- runner_registry.print_reports(scan_reports, args)
- return
- elif args.file:
- scan_reports = runner_registry.run(external_checks_dir=external_checks_dir, files=args.file,
- guidelines=guidelines)
+ url = bc_integration.commit_repository(config.branch)
+
+ if config.create_baseline:
+ overall_baseline = Baseline()
+ for report in scan_reports:
+ overall_baseline.add_findings_from_report(report)
+ created_baseline_path = os.path.join(os.path.abspath(root_folder), '.checkov.baseline')
+ with open(created_baseline_path, 'w') as f:
+ json.dump(overall_baseline.to_dict(), f, indent=4)
+ exit_codes.append(runner_registry.print_reports(scan_reports, config, url=url, created_baseline_path=created_baseline_path, baseline=baseline))
+ exit_code = 1 if 1 in exit_codes else 0
+ return exit_code
+ elif config.file:
+ scan_reports = runner_registry.run(external_checks_dir=external_checks_dir, files=config.file,
+ guidelines=guidelines,
+ repo_root_for_plan_enrichment=config.repo_root_for_plan_enrichment)
+ if baseline:
+ baseline.compare_and_reduce_reports(scan_reports)
+ if config.create_baseline:
+ overall_baseline = Baseline()
+ for report in scan_reports:
+ overall_baseline.add_findings_from_report(report)
+ created_baseline_path = os.path.join(os.path.abspath(os.path.commonprefix(config.file)), '.checkov.baseline')
+ with open(created_baseline_path, 'w') as f:
+ json.dump(overall_baseline.to_dict(), f, indent=4)
+
if bc_integration.is_integration_configured():
- files = [os.path.abspath(file) for file in args.file]
+ files = [os.path.abspath(file) for file in config.file]
root_folder = os.path.split(os.path.commonprefix(files))[0]
- bc_integration.persist_repository(root_folder)
+ bc_integration.persist_repository(root_folder, files, excluded_paths=runner_filter.excluded_paths)
bc_integration.persist_scan_results(scan_reports)
- bc_integration.commit_repository(args.branch)
- runner_registry.print_reports(scan_reports, args)
+ url = bc_integration.commit_repository(config.branch)
+ return runner_registry.print_reports(scan_reports, config, url=url, created_baseline_path=created_baseline_path,
+ baseline=baseline)
+ elif config.docker_image:
+ if config.bc_api_key is None:
+ parser.error("--bc-api-key argument is required when using --docker-image")
+ return
+ if config.dockerfile_path is None:
+ parser.error("--dockerfile-path argument is required when using --docker-image")
+ return
+ if config.branch is None:
+ parser.error("--branch argument is required when using --docker-image")
+ return
+ bc_integration.commit_repository(config.branch)
+ image_scanner.scan(config.docker_image, config.dockerfile_path)
else:
print(f"{banner}")
@@ -92,64 +223,120 @@ def run(banner=checkov_banner, argv=sys.argv[1:]):
def add_parser_args(parser):
- parser.add_argument('-v', '--version',
- help='version', action='store_true')
- parser.add_argument('-d', '--directory', action='append',
- help='IaC root directory (can not be used together with --file).')
- parser.add_argument('-f', '--file', action='append',
- help='IaC file(can not be used together with --directory)')
- parser.add_argument('--external-checks-dir', action='append',
- help='Directory for custom checks to be loaded. Can be repeated')
- parser.add_argument('--external-checks-git', action='append',
- help='Github url of external checks to be added. \n you can specify a subdirectory after a '
- 'double-slash //. \n cannot be used together with --external-checks-dir')
- parser.add_argument('-l', '--list', help='List checks', action='store_true')
- parser.add_argument('-o', '--output', nargs='?', choices=OUTPUT_CHOICES,
- default='cli',
- help='Report output format')
- parser.add_argument('--no-guide', action='store_true',
- default=False,
- help='do not fetch bridgecrew guide in checkov output report')
- parser.add_argument('--quiet', action='store_true',
- default=False,
- help='in case of CLI output, display only failed checks')
- parser.add_argument('--framework', help='filter scan to run only on a specific infrastructure code frameworks',
- choices=['cloudformation', 'terraform', 'terraform_plan', 'kubernetes', 'serverless', 'arm',
- 'all'],
- default='all')
- parser.add_argument('-c', '--check',
- help='filter scan to run only on a specific check identifier(allowlist), You can '
- 'specify multiple checks separated by comma delimiter', default=None)
- parser.add_argument('--skip-check',
- help='filter scan to run on all check but a specific check identifier(denylist), You can '
- 'specify multiple checks separated by comma delimiter', default=None)
- parser.add_argument('-s', '--soft-fail',
- help='Runs checks but suppresses error code', action='store_true')
- parser.add_argument('--bc-api-key', help='Bridgecrew API key')
- parser.add_argument('--repo-id',
- help='Identity string of the repository, with form /')
- parser.add_argument('-b', '--branch',
- help="Selected branch of the persisted repository. Only has effect when using the --bc-api-key flag",
- default='master')
- parser.add_argument('--download-external-modules',
- help="download external terraform modules from public git repositories and terraform registry",
- default=False)
- parser.add_argument('--external-modules-download-path',
- help="set the path for the download external terraform modules",
- default=DEFAULT_EXTERNAL_MODULES_DIR)
- parser.add_argument('--evaluate-variables',
- help="evaluate the values of variables and locals",
- default=True)
-
-
-def get_external_checks_dir(args):
- external_checks_dir = args.external_checks_dir
- if args.external_checks_git:
- git_getter = GitGetter(args.external_checks_git[0])
+ parser.add('-v', '--version',
+ help='version', action='version', version=version)
+ parser.add('-d', '--directory', action='append',
+ help='IaC root directory (can not be used together with --file).')
+ parser.add('-f', '--file', action='append',
+ help='IaC file(can not be used together with --directory)')
+ parser.add('--skip-path', action='append',
+ help='Path (file or directory) to skip, using regular expression logic, relative to current '
+ 'working directory. Word boundaries are not implicit; i.e., specifying "dir1" will skip any '
+ 'directory or subdirectory named "dir1". Ignored with -f. Can be specified multiple times.')
+ parser.add('--external-checks-dir', action='append',
+ help='Directory for custom checks to be loaded. Can be repeated')
+ parser.add('--external-checks-git', action='append',
+ help='Github url of external checks to be added. \n you can specify a subdirectory after a '
+ 'double-slash //. \n cannot be used together with --external-checks-dir')
+ parser.add('-l', '--list', help='List checks', action='store_true')
+ parser.add('-o', '--output', nargs='?', choices=OUTPUT_CHOICES,
+ default='cli',
+ help='Report output format')
+ parser.add('--output-bc-ids', action='store_true',
+ help='Print Bridgecrew platform IDs (BC...) instead of Checkov IDs (CKV...), if the check exists in the platform')
+ parser.add('--no-guide', action='store_true',
+ default=False,
+ help='Do not fetch Bridgecrew platform IDs and guidelines for the checkov output report. Note: this '
+ 'prevents Bridgecrew platform check IDs from being used anywhere in the CLI.')
+ parser.add('--quiet', action='store_true',
+ default=False,
+ help='in case of CLI output, display only failed checks')
+ parser.add('--compact', action='store_true',
+ default=False,
+ help='in case of CLI output, do not display code blocks')
+ parser.add('--framework', help='filter scan to run only on a specific infrastructure code frameworks',
+ choices=checkov_runners + ["all"],
+ default='all')
+ parser.add('--skip-framework', help='filter scan to skip specific infrastructure code frameworks. \n'
+ 'will be included automatically for some frameworks if system dependencies '
+ 'are missing.',
+ choices=checkov_runners,
+ default=None)
+ parser.add('-c', '--check',
+ help='filter scan to run only on a specific check identifier(allowlist), You can '
+ 'specify multiple checks separated by comma delimiter', action='append', default=None)
+ parser.add('--skip-check',
+ help='filter scan to run on all check but a specific check identifier(denylist), You can '
+ 'specify multiple checks separated by comma delimiter', action='append', default=None)
+ parser.add('--run-all-external-checks', action='store_true',
+ help='Run all external checks (loaded via --external-checks options) even if the checks are not present '
+ 'in the --check list. This allows you to always ensure that new checks present in the external '
+ 'source are used. If an external check is included in --skip-check, it will still be skipped.')
+ parser.add('--bc-api-key', help='Bridgecrew API key', env_var='BC_API_KEY')
+ parser.add('--docker-image', help='Scan docker images by name or ID. Only works with --bc-api-key flag')
+ parser.add('--dockerfile-path', help='Path to the Dockerfile of the scanned docker image')
+ parser.add('--repo-id',
+ help='Identity string of the repository, with form /')
+ parser.add('-b', '--branch',
+ help="Selected branch of the persisted repository. Only has effect when using the --bc-api-key flag",
+ default='master')
+ parser.add('--skip-fixes',
+ help='Do not download fixed resource templates from Bridgecrew. Only has effect when using the '
+ '--bc-api-key flag',
+ action='store_true')
+ parser.add('--skip-suppressions',
+ help='Do not download preconfigured suppressions from the Bridgecrew platform. Code comment '
+ 'suppressions will still be honored. '
+ 'Only has effect when using the --bc-api-key flag',
+ action='store_true')
+ parser.add('--skip-policy-download',
+ help='Do not download custom policies configured in the Bridgecrew platform. '
+ 'Only has effect when using the --bc-api-key flag',
+ action='store_true')
+ parser.add('--download-external-modules',
+ help="download external terraform modules from public git repositories and terraform registry",
+ default=os.environ.get('DOWNLOAD_EXTERNAL_MODULES', False), env_var='DOWNLOAD_EXTERNAL_MODULES')
+ parser.add('--external-modules-download-path',
+ help="set the path for the download external terraform modules",
+ default=DEFAULT_EXTERNAL_MODULES_DIR, env_var='EXTERNAL_MODULES_DIR')
+ parser.add('--evaluate-variables',
+ help="evaluate the values of variables and locals",
+ default=True)
+ parser.add('-ca', '--ca-certificate',
+ help='custom CA (bundle) file', default=None, env_var='CA_CERTIFICATE')
+ parser.add('--repo-root-for-plan-enrichment',
+ help='Directory containing the hcl code used to generate a given plan file. Use with -f.',
+ dest="repo_root_for_plan_enrichment")
+ parser.add('--config-file', help='path to the Checkov configuration YAML file', is_config_file=True, default=None)
+ parser.add('--create-config', help='takes the current command line args and writes them out to a config file at '
+ 'the given path', is_write_out_config_file_arg=True, default=None)
+ parser.add('--show-config', help='prints all args and config settings and where they came from '
+ '(eg. commandline, config file, environment variable or default)',
+ action='store_true', default=None)
+ parser.add('--create-baseline', help='Alongside outputting the findings, save all results to .checkov.baseline file'
+ ' so future runs will not re-flag the same noise. Works only with `--directory` flag',
+ action='store_true', default=False)
+ parser.add('--baseline', help='Use a .checkov.baseline file to compare current results with a known baseline. Report will include only failed checks that are new'
+ 'with respect to the provided baseline', default=None)
+ # Add mutually exclusive groups of arguments
+ exit_code_group = parser.add_mutually_exclusive_group()
+ exit_code_group.add('-s', '--soft-fail', help='Runs checks but suppresses error code', action='store_true')
+ exit_code_group.add('--soft-fail-on', help='Exits with a 0 exit code for specified checks. You can specify '
+ 'multiple checks separated by comma delimiter', action='append',
+ default=None)
+ exit_code_group.add('--hard-fail-on', help='Exits with a non-zero exit code for specified checks. You can specify '
+ 'multiple checks separated by comma delimiter', action='append',
+ default=None)
+
+
+def get_external_checks_dir(config):
+ external_checks_dir = config.external_checks_dir
+ if config.external_checks_git:
+ git_getter = GitGetter(config.external_checks_git[0])
external_checks_dir = [git_getter.get()]
atexit.register(shutil.rmtree, str(Path(external_checks_dir[0]).parent))
return external_checks_dir
if __name__ == '__main__':
- run()
+ exit(run())
diff --git a/checkov/runner_filter.py b/checkov/runner_filter.py
index ee744281ab..2844c67419 100644
--- a/checkov/runner_filter.py
+++ b/checkov/runner_filter.py
@@ -1,48 +1,71 @@
+import logging
import fnmatch
+from typing import Set, Optional, Union, List
+
from checkov.common.util.consts import DEFAULT_EXTERNAL_MODULES_DIR
+from checkov.common.util.type_forcers import convert_csv_string_arg_to_list
class RunnerFilter(object):
# NOTE: This needs to be static because different filters may be used at load time versus runtime
# (see note in BaseCheckRegistery.register). The concept of which checks are external is
# logically a "static" concept anyway, so this makes logical sense.
- __EXTERNAL_CHECK_IDS = set()
+ __EXTERNAL_CHECK_IDS: Set[str] = set()
- def __init__(self, framework='all', checks=None, skip_checks=None, download_external_modules=False, external_modules_download_path=DEFAULT_EXTERNAL_MODULES_DIR, evaluate_variables=True):
- if checks is None:
- checks = []
- if isinstance(checks, str):
- self.checks = checks.split(",")
- else:
- self.checks = checks
+ def __init__(
+ self,
+ framework: str = "all",
+ checks: Union[str, List[str], None] = None,
+ skip_checks: Union[str, List[str], None] = None,
+ download_external_modules: bool = False,
+ external_modules_download_path: str = DEFAULT_EXTERNAL_MODULES_DIR,
+ evaluate_variables: bool = True,
+ runners: Optional[List[str]] = None,
+ skip_framework: Optional[str] = None,
+ excluded_paths: Optional[List[str]] = None,
+ all_external: bool = False
+ ) -> None:
+
+ self.checks = convert_csv_string_arg_to_list(checks)
+ self.skip_checks = convert_csv_string_arg_to_list(skip_checks)
- if skip_checks is None:
- skip_checks = []
- if isinstance(skip_checks, str):
- self.skip_checks = skip_checks.split(",")
+ if skip_framework is None:
+ self.framework = framework
else:
- self.skip_checks = skip_checks
- self.framework = framework
+ if isinstance(skip_framework, str):
+ if framework == "all":
+ if runners is None:
+ runners = []
+
+ selected_frameworks = list(set(runners) - set(skip_framework.split(",")))
+ self.framework = ",".join(selected_frameworks)
+ else:
+ selected_frameworks = list(set(framework.split(",")) - set(skip_framework.split(",")))
+ self.framework = ",".join(selected_frameworks)
+ logging.info(f"Resultant set of frameworks (removing skipped frameworks): {self.framework}")
+
self.download_external_modules = download_external_modules
self.external_modules_download_path = external_modules_download_path
self.evaluate_variables = evaluate_variables
+ self.excluded_paths = excluded_paths
+ self.all_external = all_external
- def should_run_check(self, check_id):
- if RunnerFilter.is_external_check(check_id):
- pass # enabled unless skipped
+ def should_run_check(self, check_id: str, bc_check_id: Optional[str] = None) -> bool:
+ if RunnerFilter.is_external_check(check_id) and self.all_external:
+ pass # enabled unless skipped
elif self.checks:
- if check_id in self.checks:
+ if check_id in self.checks or bc_check_id in self.checks:
return True
else:
return False
- if self.skip_checks and any(fnmatch.fnmatch(check_id, pattern) for pattern in self.skip_checks):
+ if self.skip_checks and any((fnmatch.fnmatch(check_id, pattern) or (bc_check_id and fnmatch.fnmatch(bc_check_id, pattern))) for pattern in self.skip_checks):
return False
return True
@staticmethod
- def notify_external_check(check_id):
+ def notify_external_check(check_id: str) -> None:
RunnerFilter.__EXTERNAL_CHECK_IDS.add(check_id)
@staticmethod
- def is_external_check(check_id):
+ def is_external_check(check_id: str) -> bool:
return check_id in RunnerFilter.__EXTERNAL_CHECK_IDS
diff --git a/checkov/secrets/__init__.py b/checkov/secrets/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/secrets/plugins/__init__.py b/checkov/secrets/plugins/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/secrets/plugins/entropy_keyword_combinator.py b/checkov/secrets/plugins/entropy_keyword_combinator.py
new file mode 100644
index 0000000000..4444178498
--- /dev/null
+++ b/checkov/secrets/plugins/entropy_keyword_combinator.py
@@ -0,0 +1,35 @@
+from detect_secrets.core.potential_secret import PotentialSecret
+from detect_secrets.plugins.high_entropy_strings import Base64HighEntropyString, HexHighEntropyString
+from detect_secrets.plugins.keyword import KeywordDetector
+from detect_secrets.plugins.base import BasePlugin
+from typing import Generator, Any, Set
+
+
+class EntropyKeywordCombinator(BasePlugin):
+ secret_type = None
+
+ def __init__(self, limit: float) -> None:
+ self.high_entropy_scanners = (Base64HighEntropyString(limit=limit), HexHighEntropyString(limit=limit))
+ self.keyword_scanner = KeywordDetector()
+
+ def analyze_line(
+ self,
+ filename: str,
+ line: str,
+ line_number: int = 0,
+ **kwargs: Any
+ ) -> Set[PotentialSecret]:
+ """
+ This method first runs the keyword plugin. If it finds a match - it runs the entropy scanners, and if
+ one of the entropy scanners find a match (on a line which was already matched by keyword plugin) - it is returned.
+ """
+ keyword_matches = self.keyword_scanner.analyze_line(filename, line, line_number, **kwargs)
+ if len(keyword_matches):
+ for entropy_scanner in self.high_entropy_scanners:
+ matches = entropy_scanner.analyze_line(filename, line, line_number, **kwargs)
+ if len(matches) > 0:
+ return matches
+ return set([])
+
+ def analyze_string(self, string: str) -> Generator[str, None, None]:
+ raise NotImplementedError()
diff --git a/checkov/secrets/runner.py b/checkov/secrets/runner.py
new file mode 100644
index 0000000000..613b57d9c0
--- /dev/null
+++ b/checkov/secrets/runner.py
@@ -0,0 +1,191 @@
+import linecache
+import logging
+import os
+import re
+import time
+from typing import Optional, List
+
+from detect_secrets import SecretsCollection
+from detect_secrets.core.potential_secret import PotentialSecret
+from detect_secrets.settings import transient_settings
+from typing_extensions import TypedDict
+
+from checkov.common.comment.enum import COMMENT_REGEX
+from checkov.common.graph.graph_builder.utils import run_function_multithreaded
+from checkov.common.models.consts import SUPPORTED_FILE_EXTENSIONS
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.record import Record
+from checkov.common.output.report import Report
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
+from checkov.common.runners.base_runner import ignored_directories
+from checkov.common.util.consts import DEFAULT_EXTERNAL_MODULES_DIR
+from checkov.runner_filter import RunnerFilter
+
+SECRET_TYPE_TO_ID = {
+ 'Artifactory Credentials': 'CKV_SECRET_1',
+ 'AWS Access Key': 'CKV_SECRET_2',
+ 'Azure Storage Account access key': 'CKV_SECRET_3',
+ 'Basic Auth Credentials': 'CKV_SECRET_4',
+ 'Cloudant Credentials': 'CKV_SECRET_5',
+ 'Base64 High Entropy String': 'CKV_SECRET_6',
+ 'IBM Cloud IAM Key': 'CKV_SECRET_7',
+ 'IBM COS HMAC Credentials': 'CKV_SECRET_8',
+ 'JSON Web Token': 'CKV_SECRET_9',
+ # 'Secret Keyword': 'CKV_SECRET_10',
+ 'Mailchimp Access Key': 'CKV_SECRET_11',
+ 'NPM tokens': 'CKV_SECRET_12',
+ 'Private Key': 'CKV_SECRET_13',
+ 'Slack Token': 'CKV_SECRET_14',
+ 'SoftLayer Credentials': 'CKV_SECRET_15',
+ 'Square OAuth Secret': 'CKV_SECRET_16',
+ 'Stripe Access Key': 'CKV_SECRET_17',
+ 'Twilio API Key': 'CKV_SECRET_18',
+ 'Hex High Entropy String': 'CKV_SECRET_19'
+}
+CHECK_ID_TO_SECRET_TYPE = {v: k for k, v in SECRET_TYPE_TO_ID.items()}
+
+PROHIBITED_FILES = ['Pipfile.lock', 'yarn.lock', 'package-lock.json', 'requirements.txt']
+
+
+class _CheckResult(TypedDict, total=False):
+ result: CheckResult
+ suppress_comment: str
+
+
+class Runner(BaseRunner):
+ check_type = 'secrets'
+
+ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(),
+ collect_skip_comments=True) -> Report:
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ secrets = SecretsCollection()
+ with transient_settings({
+ # Only run scans with only these plugins.
+ 'plugins_used': [
+ {
+ 'name': 'AWSKeyDetector'
+ },
+ {
+ 'name': 'ArtifactoryDetector'
+ },
+ {
+ 'name': 'AzureStorageKeyDetector'
+ },
+ {
+ 'name': 'BasicAuthDetector'
+ },
+ {
+ 'name': 'CloudantDetector'
+ },
+ {
+ 'name': 'IbmCloudIamDetector'
+ },
+ {
+ 'name': 'MailchimpDetector'
+ },
+ {
+ 'name': 'PrivateKeyDetector'
+ },
+ {
+ 'name': 'SlackDetector'
+ },
+ {
+ 'name': 'SoftlayerDetector'
+ },
+ {
+ 'name': 'SquareOAuthDetector'
+ },
+ {
+ 'name': 'StripeDetector'
+ },
+ {
+ 'name': 'TwilioKeyDetector'
+ },
+ {
+ 'name': 'EntropyKeywordCombinator',
+ 'path': f'file://{current_dir}/plugins/entropy_keyword_combinator.py',
+ 'limit': 4.5
+ }
+ ]
+ }) as settings:
+ report = Report(self.check_type)
+ # Implement non IaC files (including .terraform dir)
+ files_to_scan = files or []
+ excluded_paths = (runner_filter.excluded_paths or []) + ignored_directories + [DEFAULT_EXTERNAL_MODULES_DIR]
+ if root_folder:
+ for root, d_names, f_names in os.walk(root_folder):
+ filter_ignored_paths(root, d_names, excluded_paths)
+ filter_ignored_paths(root, f_names, excluded_paths)
+ for file in f_names:
+ if file not in PROHIBITED_FILES and f".{file.split('.')[-1]}" in SUPPORTED_FILE_EXTENSIONS:
+ files_to_scan.append(os.path.join(root, file))
+ logging.info(f'Secrets scanning will scan {len(files_to_scan)} files')
+
+ settings.disable_filters(*['detect_secrets.filters.heuristic.is_indirect_reference'])
+
+ def _scan_file(file_paths: List[str]):
+ for file_path in file_paths:
+ start = time.time()
+ try:
+ secrets.scan_file(file_path)
+ except Exception as err:
+ logging.warning(f"Secret scanning:could not process file {file_path}, {err}")
+ continue
+ end = time.time()
+ scan_time = end - start
+ if scan_time > 10:
+ logging.info(f'Scanned {file_path}, took {scan_time} seconds')
+
+ run_function_multithreaded(_scan_file, files_to_scan, 1, num_of_workers=os.cpu_count())
+
+ for _, secret in iter(secrets):
+ check_id = SECRET_TYPE_TO_ID.get(secret.type)
+ if not check_id:
+ continue
+ if runner_filter.checks and check_id not in runner_filter.checks:
+ continue
+ result: _CheckResult = {'result': CheckResult.FAILED}
+ line_text = linecache.getline(secret.filename, secret.line_number)
+ if line_text != "" and line_text.split()[0] == 'git_commit':
+ continue
+ result = self.search_for_suppression(
+ check_id=check_id,
+ secret=secret,
+ skipped_checks=runner_filter.skip_checks,
+ ) or result
+ report.add_record(Record(
+ check_id=check_id,
+ check_name=secret.type,
+ check_result=result,
+ code_block=[(secret.line_number, line_text)],
+ file_path=f'/{os.path.relpath(secret.filename, root_folder)}',
+ file_line_range=[secret.line_number, secret.line_number + 1],
+ resource=secret.secret_hash,
+ check_class=None,
+ evaluations=None,
+ file_abs_path=os.path.abspath(secret.filename)
+ ))
+
+ return report
+
+ @staticmethod
+ def search_for_suppression(
+ check_id: str,
+ secret: PotentialSecret,
+ skipped_checks: List[str]
+ ) -> Optional[_CheckResult]:
+ if check_id in skipped_checks and check_id in CHECK_ID_TO_SECRET_TYPE.keys():
+ return {
+ "result": CheckResult.SKIPPED,
+ "suppress_comment": f"Secret scan {check_id} is skipped"
+ }
+ # Check for suppression comment in the line before, the line of, and the line after the secret
+ for line_number in [secret.line_number, secret.line_number - 1, secret.line_number + 1]:
+ lt = linecache.getline(secret.filename, line_number)
+ skip_search = re.search(COMMENT_REGEX, lt)
+ if skip_search and skip_search.group(2) == check_id:
+ return {
+ "result": CheckResult.SKIPPED,
+ "suppress_comment": skip_search.group(3)[1:] if skip_search.group(3) else "No comment provided"
+ }
+ return None
diff --git a/checkov/serverless/base_registry.py b/checkov/serverless/base_registry.py
index 0aa3ed9fa9..e09279e367 100644
--- a/checkov/serverless/base_registry.py
+++ b/checkov/serverless/base_registry.py
@@ -1,4 +1,4 @@
-from collections import Mapping
+from collections.abc import Mapping
from dataclasses import dataclass
from checkov.common.checks.base_check_registry import BaseCheckRegistry
@@ -25,7 +25,7 @@ def scan(self, scanned_file, entity, skipped_checks, runner_filter):
if check.id in [x['id'] for x in skipped_checks]:
skip_info = [x for x in skipped_checks if x['id'] == check.id][0]
- if runner_filter.should_run_check(check.id):
+ if runner_filter.should_run_check(check.id, check.bc_id):
self.logger.debug("Running check: {} on file {}".format(check.name, scanned_file))
result = check.run(scanned_file=scanned_file, entity_configuration=entity_configuration,
entity_name=entity_type, entity_type=entity_type, skip_info=skip_info)
diff --git a/checkov/serverless/checks/function/aws/AWSCredentials.py b/checkov/serverless/checks/function/aws/AWSCredentials.py
index 0ab54a7dec..2e6499d677 100644
--- a/checkov/serverless/checks/function/aws/AWSCredentials.py
+++ b/checkov/serverless/checks/function/aws/AWSCredentials.py
@@ -18,7 +18,7 @@ def scan_function_conf(self, conf):
"""
see: https://www.terraform.io/docs/providers/aws/index.html#static-credentials
"""
- if conf.get(ENVIRONMENT_TOKEN):
+ if conf.get(ENVIRONMENT_TOKEN) and isinstance(conf[ENVIRONMENT_TOKEN], dict):
env_variables_strings = {key: value for key, value in conf.get(ENVIRONMENT_TOKEN).items() if
isinstance(value, str)}
for env_var_value in env_variables_strings.values():
diff --git a/checkov/serverless/parsers/parser.py b/checkov/serverless/parsers/parser.py
index c6da85cac9..7997600977 100644
--- a/checkov/serverless/parsers/parser.py
+++ b/checkov/serverless/parsers/parser.py
@@ -24,7 +24,7 @@
TAGS_TOKEN = 'tags' #nosec
SUPPORTED_PROVIDERS = ['aws']
-DEFAULT_VAR_PATTERN = "\\${([^{}]+?)}"
+DEFAULT_VAR_PATTERN = r"\${([^{}]+?)}"
QUOTED_WORD_SYNTAX = re.compile(r"(?:('|\").*?\1)")
FILE_LOCATION_PATTERN = re.compile(r'^file\(([^?%*:|"<>]+?)\)')
@@ -51,7 +51,8 @@ def parse(filename):
except UnicodeDecodeError:
logger.error('Cannot read file contents: %s', filename)
return
- except YAMLError:
+ except YAMLError as e:
+ print(e)
return
process_variables(template, filename)
diff --git a/checkov/serverless/runner.py b/checkov/serverless/runner.py
index e3a942d002..106e3d6fea 100644
--- a/checkov/serverless/runner.py
+++ b/checkov/serverless/runner.py
@@ -1,5 +1,7 @@
import logging
import os
+
+from checkov.cloudformation import cfn_utils
from checkov.cloudformation.context_parser import ContextParser as CfnContextParser
from checkov.serverless.base_registry import EntityDetails
from checkov.serverless.parsers.context_parser import ContextParser as SlsContextParser
@@ -12,7 +14,7 @@
from checkov.serverless.checks.plugin.registry import plugin_registry
from checkov.serverless.checks.provider.registry import provider_registry
from checkov.serverless.checks.service.registry import service_registry
-from checkov.common.runners.base_runner import BaseRunner, filter_ignored_directories
+from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
from checkov.runner_filter import RunnerFilter
from checkov.common.output.record import Record
from checkov.common.output.report import Report
@@ -46,7 +48,7 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
files_list = []
if external_checks_dir:
for directory in external_checks_dir:
- function_registry.load_external_checks(directory, runner_filter)
+ function_registry.load_external_checks(directory)
if files:
for file in files:
@@ -62,7 +64,8 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if "node_modules" in d_names:
d_names.remove("node_modules")
- filter_ignored_directories(d_names)
+ filter_ignored_paths(root, d_names, runner_filter.excluded_paths)
+ filter_ignored_paths(root, f_names, runner_filter.excluded_paths)
for file in f_names:
if file in SLS_FILE_MASK:
full_path = os.path.join(root, file)
@@ -98,6 +101,8 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if CFN_RESOURCES_TOKEN in sls_file_data and isinstance(sls_file_data[CFN_RESOURCES_TOKEN], dict_node):
cf_sub_template = sls_file_data[CFN_RESOURCES_TOKEN]
+ if not cf_sub_template.get('Resources'):
+ continue
cf_context_parser = CfnContextParser(sls_file, cf_sub_template, definitions_raw[sls_file])
logging.debug("Template Dump for {}: {}".format(sls_file, sls_file_data, indent=2))
cf_context_parser.evaluate_default_refs()
@@ -105,6 +110,9 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if not isinstance(resource, dict_node):
continue
cf_resource_id = cf_context_parser.extract_cf_resource_id(resource, resource_name)
+ if not cf_resource_id:
+ # Not Type attribute for resource
+ continue
entity_lines_range, entity_code_lines = cf_context_parser.extract_cf_resource_code_lines(
resource)
if entity_lines_range and entity_code_lines:
@@ -112,14 +120,15 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
# TODO - Variable Eval Message!
variable_evaluations = {}
- results = cfn_registry.scan(sls_file, {resource_name: resource}, skipped_checks,
- runner_filter)
+ entity = {resource_name: resource}
+ results = cfn_registry.scan(sls_file, entity, skipped_checks, runner_filter)
+ tags = cfn_utils.get_resource_tags(entity, cfn_registry)
for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=sls_file,
file_line_range=entity_lines_range,
resource=cf_resource_id, evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path, entity_tags=tags)
report.add_record(record=record)
sls_context_parser = SlsContextParser(sls_file, sls_file_data, definitions_raw[sls_file])
@@ -141,15 +150,15 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
# function data from the provider block since logically that's what serverless
# does. This allows checks to see what the complete data would be.
sls_context_parser.enrich_function_with_provider(item_name)
- results = registry.scan(sls_file,
- EntityDetails(sls_context_parser.provider_type, item_content),
- skipped_checks, runner_filter)
+ entity = EntityDetails(sls_context_parser.provider_type, item_content)
+ results = registry.scan(sls_file, entity, skipped_checks, runner_filter)
+ tags = cfn_utils.get_resource_tags(entity, registry)
for check, check_result in results.items():
record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=sls_file,
file_line_range=entity_lines_range,
resource=item_name, evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path, entity_tags=tags)
report.add_record(record=record)
# Sub-sections that are a single item
for token, registry in SINGLE_ITEM_SECTIONS:
@@ -162,15 +171,15 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
skipped_checks = CfnContextParser.collect_skip_comments(entity_code_lines)
variable_evaluations = {}
- results = registry.scan(sls_file,
- EntityDetails(sls_context_parser.provider_type, item_content),
- skipped_checks, runner_filter)
+ entity = EntityDetails(sls_context_parser.provider_type, item_content)
+ results = registry.scan(sls_file, entity, skipped_checks, runner_filter)
+ tags = cfn_utils.get_resource_tags(entity, registry)
for check, check_result in results.items():
record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=sls_file,
file_line_range=entity_lines_range,
resource=token, evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path, entity_tags=tags)
report.add_record(record=record)
# "Complete" checks
@@ -179,9 +188,9 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
if entity_lines_range:
skipped_checks = CfnContextParser.collect_skip_comments(entity_code_lines)
variable_evaluations = {}
- results = complete_registry.scan(sls_file,
- EntityDetails(sls_context_parser.provider_type, sls_file_data),
- skipped_checks, runner_filter)
+ entity = EntityDetails(sls_context_parser.provider_type, sls_file_data)
+ results = complete_registry.scan(sls_file, entity, skipped_checks, runner_filter)
+ tags = cfn_utils.get_resource_tags(entity, complete_registry)
for check, check_result in results.items():
record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
code_block=[], # Don't show, could be large
@@ -189,7 +198,7 @@ def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=R
file_line_range=entity_lines_range,
resource="complete", # Weird, not sure what to put where
evaluations=variable_evaluations,
- check_class=check.__class__.__module__, file_abs_path=file_abs_path)
+ check_class=check.__class__.__module__, file_abs_path=file_abs_path, entity_tags=tags)
report.add_record(record=record)
return report
diff --git a/checkov/terraform/checks/data/BaseCloudsplainingIAMCheck.py b/checkov/terraform/checks/data/BaseCloudsplainingIAMCheck.py
new file mode 100644
index 0000000000..75749c3ebf
--- /dev/null
+++ b/checkov/terraform/checks/data/BaseCloudsplainingIAMCheck.py
@@ -0,0 +1,36 @@
+import json
+import logging
+from abc import abstractmethod
+from typing import Dict, List, Any, Union
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.data.base_check import BaseDataCheck
+from checkov.terraform.checks.utils.iam_terraform_document_to_policy_converter import \
+ convert_terraform_conf_to_iam_policy
+
+
+class BaseCloudsplainingIAMCheck(BaseDataCheck):
+ def __init__(self, name: str, id: str) -> None:
+ super().__init__(name=name, id=id, categories=[CheckCategories.IAM], supported_data=['aws_iam_policy_document'])
+
+ def scan_data_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ key = 'statement'
+ if key in conf.keys():
+ try:
+ converted_conf = convert_terraform_conf_to_iam_policy(conf)
+ policy = PolicyDocument(converted_conf)
+ violations = self.cloudsplaining_analysis(policy)
+ except Exception as e:
+ # this might occur with templated iam policies where ARN is not in place or similar
+ logging.debug("could not run cloudsplaining analysis on policy {}", conf)
+ return CheckResult.UNKNOWN
+ if violations:
+ logging.debug("detailed cloudsplainging finding: {}", json.dumps(violations))
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+ @abstractmethod
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> Union[List[str], List[Dict[str, Any]]]:
+ raise NotImplementedError()
diff --git a/checkov/terraform/checks/data/aws/AdminPolicyDocument.py b/checkov/terraform/checks/data/aws/AdminPolicyDocument.py
index fdc9d35fa0..9dabbe04b1 100644
--- a/checkov/terraform/checks/data/aws/AdminPolicyDocument.py
+++ b/checkov/terraform/checks/data/aws/AdminPolicyDocument.py
@@ -1,28 +1,40 @@
+from typing import Dict, List, Any
+
+from checkov.common.util.type_forcers import force_list
from checkov.terraform.checks.data.base_check import BaseDataCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class AdminPolicyDocument(BaseDataCheck):
- def __init__(self):
- name = "Ensure IAM policies that allow full \"*-*\" administrative privileges are not created"
+ def __init__(self) -> None:
+ name = 'Ensure IAM policies that allow full "*-*" administrative privileges are not created'
id = "CKV_AWS_1"
- supported_data = ['aws_iam_policy_document']
+ supported_data = ["aws_iam_policy_document"]
categories = [CheckCategories.IAM]
super().__init__(name=name, id=id, categories=categories, supported_data=supported_data)
- def scan_data_conf(self, conf):
+ def scan_data_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
"""
validates iam policy document
https://learn.hashicorp.com/terraform/aws/iam-policy
:param conf: aws_kms_key configuration
:return:
"""
- key = 'statement'
- if key in conf.keys():
- for statement in conf[key]:
- if 'actions' in statement and statement.get('effect', ['Allow'])[0] == 'Allow' and '*' in statement['actions'][0] \
- and '*' in statement['resources'][0]:
+
+ for statement in conf.get("statement", []):
+ if not isinstance(statement, list):
+ statement = [statement]
+ for stmt in statement:
+ if (
+ isinstance(stmt, dict)
+ and stmt.get("effect", ["Allow"]) == ["Allow"]
+ and stmt.get("actions")
+ and "*" in force_list(stmt["actions"][0])
+ and stmt.get("resources")
+ and "*" in force_list(stmt["resources"][0])
+ ):
return CheckResult.FAILED
+
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/data/aws/IAMCredentialsExposure.py b/checkov/terraform/checks/data/aws/IAMCredentialsExposure.py
new file mode 100644
index 0000000000..b1dd6fb3b2
--- /dev/null
+++ b/checkov/terraform/checks/data/aws/IAMCredentialsExposure.py
@@ -0,0 +1,20 @@
+from typing import List, Union, Dict, Any
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.terraform.checks.data.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class CloudSplainingCredentialsExposure(BaseCloudsplainingIAMCheck):
+ excluded_actions = {"ecr:GetAuthorizationToken"}
+
+ def __init__(self) -> None:
+ name = "Ensure IAM policies does not allow credentials exposure"
+ id = "CKV_AWS_107"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> Union[List[str], List[Dict[str, Any]]]:
+ return [x for x in policy.credentials_exposure if x not in CloudSplainingCredentialsExposure.excluded_actions]
+
+
+check = CloudSplainingCredentialsExposure()
diff --git a/checkov/terraform/checks/data/aws/IAMDataExfiltration.py b/checkov/terraform/checks/data/aws/IAMDataExfiltration.py
new file mode 100644
index 0000000000..702b1accfc
--- /dev/null
+++ b/checkov/terraform/checks/data/aws/IAMDataExfiltration.py
@@ -0,0 +1,18 @@
+from typing import List
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.terraform.checks.data.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class CloudSplainingDataExfiltration(BaseCloudsplainingIAMCheck):
+ def __init__(self) -> None:
+ name = "Ensure IAM policies does not allow data exfiltration"
+ id = "CKV_AWS_108"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> List[str]:
+ return policy.allows_data_exfiltration_actions
+
+
+check = CloudSplainingDataExfiltration()
diff --git a/checkov/terraform/checks/data/aws/IAMPermissionsManagement.py b/checkov/terraform/checks/data/aws/IAMPermissionsManagement.py
new file mode 100644
index 0000000000..ed5ca7d077
--- /dev/null
+++ b/checkov/terraform/checks/data/aws/IAMPermissionsManagement.py
@@ -0,0 +1,18 @@
+from typing import List, Dict, Any, Union
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.terraform.checks.data.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class CloudSplainingPermissionsManagement(BaseCloudsplainingIAMCheck):
+ def __init__(self) -> None:
+ name = "Ensure IAM policies does not allow permissions management / resource exposure without constraints"
+ id = "CKV_AWS_109"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> Union[List[str], List[Dict[str, Any]]]:
+ return policy.permissions_management_without_constraints
+
+
+check = CloudSplainingPermissionsManagement()
diff --git a/checkov/terraform/checks/data/aws/IAMPrivilegeEscalation.py b/checkov/terraform/checks/data/aws/IAMPrivilegeEscalation.py
new file mode 100644
index 0000000000..e90440825d
--- /dev/null
+++ b/checkov/terraform/checks/data/aws/IAMPrivilegeEscalation.py
@@ -0,0 +1,18 @@
+from typing import List, Dict, Any, Union
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.terraform.checks.data.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class CloudSplainingPrivilegeEscalation(BaseCloudsplainingIAMCheck):
+ def __init__(self) -> None:
+ name = "Ensure IAM policies does not allow privilege escalation"
+ id = "CKV_AWS_110"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> Union[List[str], List[Dict[str, Any]]]:
+ return policy.allows_privilege_escalation
+
+
+check = CloudSplainingPrivilegeEscalation()
diff --git a/checkov/terraform/checks/data/aws/IAMWriteAccess.py b/checkov/terraform/checks/data/aws/IAMWriteAccess.py
new file mode 100644
index 0000000000..653a80f75b
--- /dev/null
+++ b/checkov/terraform/checks/data/aws/IAMWriteAccess.py
@@ -0,0 +1,18 @@
+from typing import Union, List, Dict, Any
+
+from cloudsplaining.scan.policy_document import PolicyDocument
+
+from checkov.terraform.checks.data.BaseCloudsplainingIAMCheck import BaseCloudsplainingIAMCheck
+
+
+class CloudSplainingWriteAccess(BaseCloudsplainingIAMCheck):
+ def __init__(self) -> None:
+ name = "Ensure IAM policies does not allow write access without constraints"
+ id = "CKV_AWS_111"
+ super().__init__(name=name, id=id)
+
+ def cloudsplaining_analysis(self, policy: PolicyDocument) -> Union[List[str], List[Dict[str, Any]]]:
+ return policy.write_actions_without_constraints
+
+
+check = CloudSplainingWriteAccess()
diff --git a/checkov/terraform/checks/data/aws/StarActionPolicyDocument.py b/checkov/terraform/checks/data/aws/StarActionPolicyDocument.py
index b733167cd2..c2a6140401 100644
--- a/checkov/terraform/checks/data/aws/StarActionPolicyDocument.py
+++ b/checkov/terraform/checks/data/aws/StarActionPolicyDocument.py
@@ -1,28 +1,37 @@
+from typing import Dict, List, Any
+
from checkov.common.util.type_forcers import force_list
from checkov.terraform.checks.data.base_check import BaseDataCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class StarActionPolicyDocument(BaseDataCheck):
- def __init__(self):
- name = "Ensure no IAM policies documents allow \"*\" as a statement's actions"
+ def __init__(self) -> None:
+ name = 'Ensure no IAM policies documents allow "*" as a statement\'s actions'
id = "CKV_AWS_49"
- supported_data = ['aws_iam_policy_document']
+ supported_data = ["aws_iam_policy_document"]
categories = [CheckCategories.IAM]
super().__init__(name=name, id=id, categories=categories, supported_data=supported_data)
- def scan_data_conf(self, conf):
+ def scan_data_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
"""
validates iam policy document
https://learn.hashicorp.com/terraform/aws/iam-policy
:param conf: aws_kms_key configuration
:return:
"""
- key = 'statement'
- if key in conf.keys():
- for statement in conf[key]:
- if 'actions' in statement and '*' in force_list(statement['actions'][0]) and statement.get('effect', ['Allow'])[0] == 'Allow':
+
+ for statements in conf.get("statement", []):
+ statements = force_list(statements)
+ for statement in statements:
+ if (
+ isinstance(statement, dict)
+ and statement.get("effect", ["Allow"]) == ["Allow"]
+ and statement.get("actions")
+ and "*" in force_list(statement["actions"][0])
+ ):
return CheckResult.FAILED
+
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/data/aws/__init__.py b/checkov/terraform/checks/data/aws/__init__.py
index d66c1343db..bfe83e75bf 100644
--- a/checkov/terraform/checks/data/aws/__init__.py
+++ b/checkov/terraform/checks/data/aws/__init__.py
@@ -1,5 +1,4 @@
-from os.path import dirname, basename, isfile, join
-import glob
+from pathlib import Path
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+modules = Path(__file__).parent.glob("*.py")
+__all__ = [f.stem for f in modules if f.is_file() and not f.stem == "__init__"]
diff --git a/checkov/terraform/checks/data/base_check.py b/checkov/terraform/checks/data/base_check.py
index 7a33967edc..699c9ffa49 100644
--- a/checkov/terraform/checks/data/base_check.py
+++ b/checkov/terraform/checks/data/base_check.py
@@ -1,29 +1,35 @@
from abc import abstractmethod
+from typing import Dict, List, Callable, Optional, Any
from checkov.common.checks.base_check import BaseCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.common.multi_signature import multi_signature
from checkov.terraform.checks.data.registry import data_registry
class BaseDataCheck(BaseCheck):
- def __init__(self, name, id, categories, supported_data):
- super().__init__(name=name, id=id, categories=categories, supported_entities=supported_data,
- block_type="data")
+ def __init__(self, name: str, id: str, categories: List[CheckCategories], supported_data: List[str]) -> None:
+ super().__init__(name=name, id=id, categories=categories, supported_entities=supported_data, block_type="data")
self.supported_data = supported_data
data_registry.register(self)
- def scan_entity_conf(self, conf, entity_type):
+ def scan_entity_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
+ if conf.get("count") == [0]:
+ return CheckResult.UNKNOWN
+
return self.scan_data_conf(conf, entity_type)
@multi_signature()
@abstractmethod
- def scan_data_conf(self, conf, entity_type):
+ def scan_data_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
raise NotImplementedError()
@classmethod
@scan_data_conf.add_signature(args=["self", "conf"])
- def _scan_data_conf_self_conf(cls, wrapped):
- def wrapper(self, conf, entity_type=None):
+ def _scan_data_conf_self_conf(cls, wrapped: Callable[..., CheckResult]) -> Callable[..., CheckResult]:
+ def wrapper(
+ self: "BaseDataCheck", conf: Dict[str, List[Any]], entity_type: Optional[str] = None
+ ) -> CheckResult:
# keep default argument for entity_type so old code, that doesn't set it, will work.
return wrapped(self, conf)
diff --git a/checkov/terraform/checks/data/base_registry.py b/checkov/terraform/checks/data/base_registry.py
index 91625f4875..a118cdcc14 100644
--- a/checkov/terraform/checks/data/base_registry.py
+++ b/checkov/terraform/checks/data/base_registry.py
@@ -1,15 +1,12 @@
+from typing import Dict, Any, Tuple, List
+
from checkov.common.checks.base_check_registry import BaseCheckRegistry
class Registry(BaseCheckRegistry):
-
- def __init__(self):
- super().__init__()
-
- def extract_entity_details(self, entity):
- data_type = list(entity.keys())[0]
- data_name = list(list(entity.values())[0].keys())[0]
- data_object = entity[data_type]
- data_configuration = data_object[data_name]
+ def extract_entity_details(
+ self, entity: Dict[str, Dict[str, Dict[str, List[Dict[str, Any]]]]]
+ ) -> Tuple[str, str, Dict[str, List[Dict[str, Any]]]]:
+ data_type, data_object = next(iter(entity.items()))
+ data_name, data_configuration = next(iter(data_object.items()))
return data_type, data_name, data_configuration
-
diff --git a/checkov/terraform/checks/graph_checks/__init__.py b/checkov/terraform/checks/graph_checks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/terraform/checks/graph_checks/aws/ALBProtectedByWAF.yaml b/checkov/terraform/checks/graph_checks/aws/ALBProtectedByWAF.yaml
new file mode 100644
index 0000000000..cc36e2a5c9
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/ALBProtectedByWAF.yaml
@@ -0,0 +1,26 @@
+metadata:
+ id: "CKV2_AWS_28"
+ name: "Ensure public facing ALB are protected by WAF"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: filter
+ value:
+ - aws_lb
+ operator: within
+ attribute: resource_type
+ - or:
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - aws_lb
+ connected_resource_types:
+ - aws_wafregional_web_acl_association
+ - cond_type: attribute
+ value: true
+ attribute: internal
+ resource_types:
+ - aws_lb
+ operator: equals
+
+
diff --git a/checkov/terraform/checks/graph_checks/aws/ALBRedirectsHTTPToHTTPS.yaml b/checkov/terraform/checks/graph_checks/aws/ALBRedirectsHTTPToHTTPS.yaml
new file mode 100644
index 0000000000..a270141e40
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/ALBRedirectsHTTPToHTTPS.yaml
@@ -0,0 +1,70 @@
+metadata:
+ id: "CKV2_AWS_20"
+ name: "Ensure that ALB redirects HTTP requests into HTTPS ones"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: filter
+ value:
+ - aws_lb
+ operator: within
+ attribute: resource_type
+ - or:
+ - cond_type: connection
+ operator: not_exists
+ resource_types:
+ - aws_lb
+ connected_resource_types:
+ - aws_lb_listener
+ - and:
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - aws_lb
+ connected_resource_types:
+ - aws_lb_listener
+ - or:
+ - and:
+ - cond_type: attribute
+ attribute: port
+ operator: not_equals
+ value: "80"
+ resource_types:
+ - aws_lb_listener
+ - cond_type: attribute
+ attribute: protocol
+ operator: not_equals
+ value: HTTP
+ resource_types:
+ - aws_lb_listener
+ - and:
+ - cond_type: attribute
+ attribute: port
+ operator: equals
+ value: "80"
+ resource_types:
+ - aws_lb_listener
+ - cond_type: attribute
+ attribute: protocol
+ operator: equals
+ value: "HTTP"
+ resource_types:
+ - aws_lb_listener
+ - cond_type: attribute
+ attribute: default_action.type
+ operator: equals
+ value: "redirect"
+ resource_types:
+ - aws_lb_listener
+ - cond_type: attribute
+ attribute: default_action.redirect.*.port
+ operator: equals
+ value: "443"
+ resource_types:
+ - aws_lb_listener
+ - cond_type: attribute
+ attribute: default_action.redirect.*.protocol
+ operator: equals
+ value: "HTTPS"
+ resource_types:
+ - aws_lb_listener
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/AMRClustersNotOpenToInternet.yaml b/checkov/terraform/checks/graph_checks/aws/AMRClustersNotOpenToInternet.yaml
new file mode 100644
index 0000000000..2db730d22b
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/AMRClustersNotOpenToInternet.yaml
@@ -0,0 +1,23 @@
+metadata:
+ id: "CKV2_AWS_7"
+ name: "Ensure that Amazon EMR clusters' security groups are not open to the world"
+ category: "NETWORKING"
+definition:
+ and:
+ - resource_types:
+ - aws_emr_cluster
+ connected_resource_types:
+ - aws_security_group
+ operator: exists
+ cond_type: connection
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.*.cidr_blocks"
+ operator: "not_contains"
+ value: "0.0.0.0/0"
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_emr_cluster
+ operator: within
diff --git a/checkov/terraform/checks/graph_checks/aws/APIGWLoggingLevelsDefinedProperly.yaml b/checkov/terraform/checks/graph_checks/aws/APIGWLoggingLevelsDefinedProperly.yaml
new file mode 100644
index 0000000000..9e73353aa3
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/APIGWLoggingLevelsDefinedProperly.yaml
@@ -0,0 +1,37 @@
+metadata:
+ id: "CKV2_AWS_4"
+ name: "Ensure API Gateway stage have logging level defined as appropriate"
+ category: "LOGGING"
+definition:
+ and:
+ - resource_types:
+ - aws_api_gateway_stage
+ connected_resource_types:
+ - aws_api_gateway_method_settings
+ operator: exists
+ cond_type: connection
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_api_gateway_method_settings"
+ attribute: "settings.logging_level"
+ operator: "equals"
+ value: "ERROR"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_api_gateway_method_settings"
+ attribute: "settings.logging_level"
+ operator: "equals"
+ value: "INFO"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_api_gateway_method_settings"
+ attribute: "settings.metrics_enabled"
+ operator: "equals"
+ value: true
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_api_gateway_stage
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/aws/APIProtectedByWAF.yaml b/checkov/terraform/checks/graph_checks/aws/APIProtectedByWAF.yaml
new file mode 100644
index 0000000000..141fd53852
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/APIProtectedByWAF.yaml
@@ -0,0 +1,50 @@
+metadata:
+ id: "CKV2_AWS_29"
+ name: "Ensure public API gateway are protected by WAF"
+ category: "NETWORKING"
+definition:
+ or:
+ - and:
+ - cond_type: attribute
+ resource_types:
+ - aws_api_gateway_rest_api
+ attribute: endpoint_configuration.types
+ operator: contains
+ value: PRIVATE
+ - resource_types:
+ - aws_api_gateway_rest_api
+ connected_resource_types:
+ - aws_api_gateway_stage
+ operator: exists
+ cond_type: connection
+ - and:
+ - cond_type: attribute
+ resource_types:
+ - aws_api_gateway_rest_api
+ attribute: endpoint_configuration.types
+ operator: contains
+ value: REGIONAL
+ - resource_types:
+ - aws_api_gateway_rest_api
+ connected_resource_types:
+ - aws_api_gateway_stage
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - aws_api_gateway_stage
+ connected_resource_types:
+ - aws_wafregional_web_acl_association
+ operator: exists
+ cond_type: connection
+ - and:
+ - resource_types:
+ - aws_api_gateway_rest_api
+ connected_resource_types:
+ - aws_api_gateway_stage
+ operator: not_exists
+ cond_type: connection
+ - cond_type: filter
+ value:
+ - aws_api_gateway_stage
+ attribute: resource_type
+ operator: within
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/AutoScalingEnableOnDynamoDBTables.yaml b/checkov/terraform/checks/graph_checks/aws/AutoScalingEnableOnDynamoDBTables.yaml
new file mode 100644
index 0000000000..6eb780eee9
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/AutoScalingEnableOnDynamoDBTables.yaml
@@ -0,0 +1,42 @@
+metadata:
+ name: "Ensure that Auto Scaling is enabled on your DynamoDB tables"
+ id: "CKV2_AWS_16"
+ category: "GENERAL_SECURITY"
+definition:
+ or:
+ - and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_dynamodb_table
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - aws_dynamodb_table
+ connected_resource_types:
+ - aws_appautoscaling_target
+ operator: exists
+ - cond_type: connection
+ resource_types:
+ - aws_appautoscaling_target
+ connected_resource_types:
+ - aws_appautoscaling_policy
+ operator: exists
+ - cond_type: attribute
+ resource_types:
+ - aws_dynamodb_table
+ attribute: billing_mode
+ operator: equals
+ value: PROVISIONED
+ - cond_type: attribute
+ resource_types:
+ - aws_appautoscaling_target
+ attribute: service_namespace
+ operator: equals
+ value: dynamodb
+ - cond_type: attribute
+ resource_types:
+ - aws_dynamodb_table
+ attribute: billing_mode
+ operator: equals
+ value: PAY_PER_REQUEST
diff --git a/checkov/terraform/checks/graph_checks/aws/AutoScallingEnabledELB.yaml b/checkov/terraform/checks/graph_checks/aws/AutoScallingEnabledELB.yaml
new file mode 100644
index 0000000000..f234b5e01f
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/AutoScallingEnabledELB.yaml
@@ -0,0 +1,34 @@
+metadata:
+ id: "CKV2_AWS_15"
+ name: "Ensure that auto Scaling groups that are associated with a load balancer, are using Elastic Load Balancing health checks."
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: attribute
+ resource_types:
+ - aws_autoscaling_group
+ attribute: health_check_type
+ operator: equals
+ value: "ELB"
+ - resource_types:
+ - aws_autoscaling_group
+ connected_resource_types:
+ - aws_autoscaling_attachment
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - aws_elb
+ connected_resource_types:
+ - aws_autoscaling_attachment
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - aws_elb
+ attribute: health_check
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_autoscaling_attachment
+ operator: within
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/CloudtrailHasCloudwatch.yaml b/checkov/terraform/checks/graph_checks/aws/CloudtrailHasCloudwatch.yaml
new file mode 100644
index 0000000000..80221550a0
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/CloudtrailHasCloudwatch.yaml
@@ -0,0 +1,22 @@
+metadata:
+ name: "Ensure CloudTrail trails are integrated with CloudWatch Logs"
+ id: "CKV2_AWS_10"
+ category: "LOGGING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_cloudtrail
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - aws_cloudtrail
+ connected_resource_types:
+ - aws_cloudwatch_log_group
+ operator: exists
+ - cond_type: attribute
+ resource_types:
+ - aws_cloudtrail
+ attribute: cloud_watch_logs_group_arn
+ operator: exists
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/EBSAddedBackup.yaml b/checkov/terraform/checks/graph_checks/aws/EBSAddedBackup.yaml
new file mode 100644
index 0000000000..420d81bd50
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/EBSAddedBackup.yaml
@@ -0,0 +1,17 @@
+metadata:
+ name: "Ensure that EBS are added in the backup plans of AWS Backup"
+ id: "CKV2_AWS_9"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: connection
+ resource_types:
+ - aws_backup_selection
+ connected_resource_types:
+ - aws_ebs_volume
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_ebs_volume
+ operator: within
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/EFSAddedBackup.yaml b/checkov/terraform/checks/graph_checks/aws/EFSAddedBackup.yaml
new file mode 100644
index 0000000000..7657d064aa
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/EFSAddedBackup.yaml
@@ -0,0 +1,18 @@
+metadata:
+ name: "Ensure that Elastic File System (Amazon EFS) file systems are added in the backup plans of AWS Backup"
+ id: "CKV2_AWS_18"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: connection
+ resource_types:
+ - aws_backup_selection
+ connected_resource_types:
+ - aws_efs_file_system
+ - aws_backup_plan
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_efs_file_system
+ operator: within
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/EIPAllocatedToVPCAttachedEC2.yaml b/checkov/terraform/checks/graph_checks/aws/EIPAllocatedToVPCAttachedEC2.yaml
new file mode 100644
index 0000000000..8d5f3835de
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/EIPAllocatedToVPCAttachedEC2.yaml
@@ -0,0 +1,86 @@
+metadata:
+ id: "CKV2_AWS_19"
+ name: "Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances"
+ category: "NETWORKING"
+definition:
+ or:
+ - and:
+ - resource_types:
+ - aws_eip
+ connected_resource_types:
+ - aws_instance
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_eip
+ operator: within
+ - cond_type: attribute
+ resource_types:
+ - aws_eip
+ attribute: vpc
+ operator: equals
+ value: true
+ - and:
+ - resource_types:
+ - aws_eip_association
+ connected_resource_types:
+ - aws_instance
+ - aws_eip
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_eip
+ operator: within
+ - cond_type: attribute
+ resource_types:
+ - aws_eip
+ attribute: vpc
+ operator: equals
+ value: true
+ - cond_type: attribute
+ resource_types:
+ - aws_eip_association
+ attribute: instance_id
+ operator: exists
+ - and:
+ - resource_types:
+ - aws_eip
+ connected_resource_types:
+ - aws_nat_gateway
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_eip
+ operator: within
+ - cond_type: attribute
+ resource_types:
+ - aws_eip
+ attribute: vpc
+ operator: equals
+ value: true
+ - and:
+ - or:
+ - cond_type: attribute
+ resource_types:
+ - aws_eip
+ attribute: instance
+ operator: contains
+ value: "module."
+ - cond_type: attribute
+ resource_types:
+ - aws_eip
+ attribute: instance
+ operator: contains
+ value: "data."
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_eip
+ operator: within
+
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/EncryptedEBSVolumeOnlyConnectedToEC2s.yaml b/checkov/terraform/checks/graph_checks/aws/EncryptedEBSVolumeOnlyConnectedToEC2s.yaml
new file mode 100644
index 0000000000..ffdca4314c
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/EncryptedEBSVolumeOnlyConnectedToEC2s.yaml
@@ -0,0 +1,31 @@
+metadata:
+ name: "Ensure that only encrypted EBS volumes are attached to EC2 instances"
+ category: "ENCRYPTION"
+ id: "CKV2_AWS_2"
+definition:
+ and:
+ - or:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_volume_attachment"
+ connected_resource_types:
+ - "aws_ebs_volume"
+ operator: "not_exists"
+ - and:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_ebs_volume"
+ attribute: "encrypted"
+ operator: "equals"
+ value: true
+ - cond_type: "connection"
+ resource_types:
+ - "aws_volume_attachment"
+ connected_resource_types:
+ - "aws_ebs_volume"
+ operator: "exists"
+ - cond_type: "filter"
+ attribute: "resource_type"
+ value:
+ - "aws_ebs_volume"
+ operator: "within"
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/GuardDutyIsEnabled.yaml b/checkov/terraform/checks/graph_checks/aws/GuardDutyIsEnabled.yaml
new file mode 100644
index 0000000000..ddac3b351a
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/GuardDutyIsEnabled.yaml
@@ -0,0 +1,30 @@
+metadata:
+ id: "CKV2_AWS_3"
+ name: "Ensure GuardDuty is enabled to specific org/region"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - resource_types:
+ - aws_guardduty_detector
+ connected_resource_types:
+ - aws_guardduty_organization_configuration
+ operator: exists
+ cond_type: connection
+ - cond_type: "attribute"
+ resource_types:
+ - aws_guardduty_organization_configuration
+ attribute: auto_enable
+ operator: equals
+ value: true
+ - cond_type: "attribute"
+ resource_types:
+ - aws_guardduty_detector
+ attribute: enable
+ operator: equals
+ value: true
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_guardduty_detector
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/aws/IAMGroupHasAtLeastOneUser.yaml b/checkov/terraform/checks/graph_checks/aws/IAMGroupHasAtLeastOneUser.yaml
new file mode 100644
index 0000000000..09324600ab
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/IAMGroupHasAtLeastOneUser.yaml
@@ -0,0 +1,29 @@
+metadata:
+ id: "CKV2_AWS_14"
+ name: "Ensure that IAM groups includes at least one IAM user"
+ category: "IAM"
+definition:
+ and:
+ - resource_types:
+ - aws_iam_group
+ connected_resource_types:
+ - aws_iam_group_membership
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - aws_iam_group_membership
+ connected_resource_types:
+ - aws_iam_user
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ attribute: users
+ resource_types:
+ - aws_iam_group_membership
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_iam_group_membership
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/aws/IAMUserHasNoConsoleAccess.yaml b/checkov/terraform/checks/graph_checks/aws/IAMUserHasNoConsoleAccess.yaml
new file mode 100644
index 0000000000..eb4324768f
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/IAMUserHasNoConsoleAccess.yaml
@@ -0,0 +1,20 @@
+metadata:
+ id: "CKV2_AWS_22"
+ name: "Ensure an IAM User does not have access to the console"
+ category: "IAM"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_iam_user
+ operator: within
+ - resource_types:
+ - aws_iam_user
+ connected_resource_types:
+ - aws_iam_user_login_profile
+ operator: not_exists
+ cond_type: connection
+
+
+
diff --git a/checkov/terraform/checks/graph_checks/aws/IAMUsersAreMembersAtLeastOneGroup.yaml b/checkov/terraform/checks/graph_checks/aws/IAMUsersAreMembersAtLeastOneGroup.yaml
new file mode 100644
index 0000000000..0be21cb00d
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/IAMUsersAreMembersAtLeastOneGroup.yaml
@@ -0,0 +1,25 @@
+metadata:
+ id: "CKV2_AWS_21"
+ name: "Ensure that all IAM users are members of at least one IAM group."
+ category: "IAM"
+definition:
+ and:
+ - resource_types:
+ - aws_iam_group_membership
+ connected_resource_types:
+ - aws_iam_user
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - aws_iam_group_membership
+ connected_resource_types:
+ - aws_iam_group
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_iam_group_membership
+ operator: within
+
+
diff --git a/checkov/terraform/checks/graph_checks/aws/PostgresRDSHasQueryLoggingEnabled.yaml b/checkov/terraform/checks/graph_checks/aws/PostgresRDSHasQueryLoggingEnabled.yaml
new file mode 100644
index 0000000000..335e419f7d
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/PostgresRDSHasQueryLoggingEnabled.yaml
@@ -0,0 +1,35 @@
+metadata:
+ name: "Postgres RDS has Query Logging enabled"
+ id: "CKV2_AWS_27"
+ category: "LOGGING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_db_instance
+ operator: within
+ - cond_type: filter
+ resource_types:
+ - aws_db_instance
+ attribute: engine
+ operator: within
+ value: "postgres"
+ - cond_type: connection
+ resource_types:
+ - aws_db_instance
+ connected_resource_types:
+ - aws_rds_cluster_parameter_group
+ operator: exists
+ - cond_type: attribute
+ resource_types:
+ - aws_rds_cluster_parameter_group
+ attribute: "parameter.*.name"
+ operator: contains
+ value: "log_statement"
+ - cond_type: attribute
+ resource_types:
+ - aws_rds_cluster_parameter_group
+ attribute: "parameter.*.name"
+ operator: contains
+ value: "log_min_duration_statement"
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/RDSClusterHasBackupPlan.yaml b/checkov/terraform/checks/graph_checks/aws/RDSClusterHasBackupPlan.yaml
new file mode 100644
index 0000000000..937d2d6aaa
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/RDSClusterHasBackupPlan.yaml
@@ -0,0 +1,17 @@
+metadata:
+ id: "CKV2_AWS_8"
+ name: "Ensure that RDS clusters has backup plan of AWS Backup"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_rds_cluster
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - aws_rds_cluster
+ connected_resource_types:
+ - aws_backup_selection
+ operator: exists
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/RedshiftClusterHasBackupPlan.yaml b/checkov/terraform/checks/graph_checks/aws/RedshiftClusterHasBackupPlan.yaml
new file mode 100644
index 0000000000..9f3d9cbd8f
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/RedshiftClusterHasBackupPlan.yaml
@@ -0,0 +1,17 @@
+metadata:
+ id: "CKV2_AWS_13"
+ name: "Ensure that Redshift clusters has backup plan of AWS Backup"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_redshift_cluster
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - aws_redshift_cluster
+ connected_resource_types:
+ - aws_backup_selection
+ operator: exists
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/aws/Route53ARecordAttachedResource.yaml b/checkov/terraform/checks/graph_checks/aws/Route53ARecordAttachedResource.yaml
new file mode 100644
index 0000000000..b83a96ce41
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/Route53ARecordAttachedResource.yaml
@@ -0,0 +1,47 @@
+metadata:
+ name: "Route53 A Record has Attached Resource"
+ id: "CKV2_AWS_23"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_route53_record
+ operator: within
+ - cond_type: filter
+ resource_types:
+ - aws_route53_record
+ attribute: type
+ operator: within
+ value: "A"
+ - or:
+ - cond_type: attribute
+ attribute: alias.name
+ operator: contains
+ value: "module"
+ resource_types:
+ - aws_route53_record
+ - cond_type: attribute
+ attribute: alias.name
+ operator: contains
+ value: "data."
+ resource_types:
+ - aws_route53_record
+ - cond_type: connection
+ resource_types:
+ - aws_route53_record
+ connected_resource_types:
+ - aws_instance
+ - aws_eip
+ - aws_elb
+ - aws_lb
+ - aws_route53_record
+ - aws_s3_bucket
+ - aws_api_gateway_domain_name
+ - aws_elastic_beanstalk_environment
+ - aws_vpc_endpoint
+ - aws_globalaccelerator_accelerator
+ - aws_cloudfront_distribution
+ operator: exists
+ attribute: networking
diff --git a/checkov/terraform/checks/graph_checks/aws/S3BucketHasPublicAccessBlock.yaml b/checkov/terraform/checks/graph_checks/aws/S3BucketHasPublicAccessBlock.yaml
new file mode 100644
index 0000000000..4bbc2716da
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/S3BucketHasPublicAccessBlock.yaml
@@ -0,0 +1,29 @@
+metadata:
+ name: "Ensure that S3 bucket has a Public Access block"
+ category: "Networking"
+ id: "CKV2_AWS_6"
+definition:
+ and:
+ - resource_types:
+ - aws_s3_bucket
+ connected_resource_types:
+ - aws_s3_bucket_public_access_block
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_s3_bucket
+ operator: within
+ - cond_type: attribute
+ attribute: block_public_acls
+ value: true
+ operator: equals
+ resource_types:
+ - aws_s3_bucket_public_access_block
+ - cond_type: attribute
+ attribute: block_public_policy
+ value: true
+ operator: equals
+ resource_types:
+ - aws_s3_bucket_public_access_block
diff --git a/checkov/terraform/checks/graph_checks/aws/SGAttachedToResource.yaml b/checkov/terraform/checks/graph_checks/aws/SGAttachedToResource.yaml
new file mode 100644
index 0000000000..c7477d881a
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/SGAttachedToResource.yaml
@@ -0,0 +1,47 @@
+metadata:
+ name: "Ensure that Security Groups are attached to an other resource"
+ id: "CKV2_AWS_5"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_security_group
+ operator: within
+ - resource_types:
+ - aws_security_group
+ connected_resource_types:
+ - aws_alb
+ - aws_batch_compute_environment
+ - aws_cloudwatch_event_target
+ - aws_codebuild_project
+ - aws_db_instance
+ - aws_dms_replication_instance
+ - aws_docdb_cluster
+ - aws_ec2_client_vpn_network_association
+ - aws_ecs_service
+ - aws_efs_mount_target
+ - aws_eks_cluster
+ - aws_eks_node_group
+ - aws_elasticache_cluster
+ - aws_elasticsearch_domain
+ - aws_elb
+ - aws_emr_cluster
+ - aws_instance
+ - aws_lambda_function
+ - aws_launch_configuration
+ - aws_launch_template
+ - aws_lb
+ - aws_mq_broker
+ - aws_msk_cluster
+ - aws_mwaa_environment
+ - aws_neptune_cluster
+ - aws_network_interface
+ - aws_rds_cluster
+ - aws_redshift_cluster
+ - aws_sagemaker_notebook_instance
+ - aws_vpc_endpoint
+ operator: exists
+ attribute: networking
+ cond_type: connection
diff --git a/checkov/terraform/checks/graph_checks/aws/SubnetHasACL.yaml b/checkov/terraform/checks/graph_checks/aws/SubnetHasACL.yaml
new file mode 100644
index 0000000000..e2330c15f3
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/SubnetHasACL.yaml
@@ -0,0 +1,45 @@
+metadata:
+ id: "CKV2_AWS_1"
+ name: "Ensure that all NACL are attached to subnets"
+ category: "NETWORKING"
+definition:
+ or:
+ - and :
+ - resource_types:
+ - aws_network_acl
+ connected_resource_types:
+ - aws_vpc
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - resource_types:
+ - aws_subnet
+ connected_resource_types:
+ - aws_vpc
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_vpc
+ operator: within
+ - and :
+ - resource_types:
+ - aws_network_acl
+ connected_resource_types:
+ - aws_subnet
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - aws_network_acl
+ attribute: subnet_ids
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_network_acl
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/aws/VPCHasFlowLog.yaml b/checkov/terraform/checks/graph_checks/aws/VPCHasFlowLog.yaml
new file mode 100644
index 0000000000..ee70ba9799
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/VPCHasFlowLog.yaml
@@ -0,0 +1,19 @@
+metadata:
+ name: "Ensure VPC flow logging is enabled in all VPCs"
+ category: "LOGGING"
+ id: "CKV2_AWS_11"
+definition:
+ and:
+ - resource_types:
+ - aws_vpc
+ connected_resource_types:
+ - aws_flow_log
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_vpc
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/aws/VPCHasRestrictedSG.yaml b/checkov/terraform/checks/graph_checks/aws/VPCHasRestrictedSG.yaml
new file mode 100644
index 0000000000..e121a9f3ae
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/aws/VPCHasRestrictedSG.yaml
@@ -0,0 +1,63 @@
+metadata:
+ name: "Ensure the default security group of every VPC restricts all traffic"
+ id: "CKV2_AWS_12"
+ category: "LOGGING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_vpc
+ operator: within
+ - cond_type: "connection"
+ resource_types:
+ - "aws_vpc"
+ connected_resource_types:
+ - "aws_default_security_group"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.to_port"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.from_port"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.self"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "egress.to_port"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "egress.from_port"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "egress.cidr_blocks"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "egress.protocol"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.protocol"
+ operator: "not_exists"
+ - cond_type: "connection"
+ resource_types:
+ - "aws_default_security_group"
+ connected_resource_types:
+ - "aws_security_group_rule"
+ operator: "not_exists"
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/AccessToPostgreSQLFromAzureServicesIsDisabled.yaml b/checkov/terraform/checks/graph_checks/azure/AccessToPostgreSQLFromAzureServicesIsDisabled.yaml
new file mode 100644
index 0000000000..a028b134ea
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AccessToPostgreSQLFromAzureServicesIsDisabled.yaml
@@ -0,0 +1,37 @@
+metadata:
+ id: "CKV2_AZURE_6"
+ name: "Ensure 'Allow access to Azure services' for PostgreSQL Database Server is disabled"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_sql_server
+ operator: within
+ - or:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_sql_firewall_rule
+ operator: not_exists
+ cond_type: connection
+ - and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_sql_firewall_rule
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - azurerm_sql_firewall_rule
+ attribute: start_ip_address
+ value: "0.0.0.0"
+ operator: not_equals
+ - cond_type: attribute
+ resource_types:
+ - azurerm_sql_firewall_rule
+ attribute: end_ip_address
+ value: "0.0.0.0"
+ operator: not_equals
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureActiveDirectoryAdminIsConfigured.yaml b/checkov/terraform/checks/graph_checks/azure/AzureActiveDirectoryAdminIsConfigured.yaml
new file mode 100644
index 0000000000..4de77bf755
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureActiveDirectoryAdminIsConfigured.yaml
@@ -0,0 +1,18 @@
+metadata:
+ id: "CKV2_AZURE_7"
+ name: "Ensure that Azure Active Directory Admin is configured"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_sql_active_directory_administrator
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_sql_server
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs.yaml b/checkov/terraform/checks/graph_checks/azure/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs.yaml
new file mode 100644
index 0000000000..c2fef2bb98
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs.yaml
@@ -0,0 +1,35 @@
+metadata:
+ id: "CKV2_AZURE_10"
+ name: "Ensure that Microsoft Antimalware is configured to automatically updates for Virtual Machines"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_virtual_machine
+ operator: within
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - azurerm_virtual_machine
+ connected_resource_types:
+ - azurerm_virtual_machine_extension
+ - cond_type: attribute
+ attribute: type
+ value: "IaaSAntimalware"
+ operator: equals
+ resource_types:
+ - azurerm_virtual_machine_extension
+ - cond_type: attribute
+ attribute: publisher
+ value: "Microsoft.Azure.Security"
+ operator: equals
+ resource_types:
+ - azurerm_virtual_machine_extension
+ - cond_type: attribute
+ attribute: auto_upgrade_minor_version
+ value: true
+ operator: equals
+ resource_types:
+ - azurerm_virtual_machine_extension
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureDataFactoriesEncryptedWithCustomerManagedKey.yaml b/checkov/terraform/checks/graph_checks/azure/AzureDataFactoriesEncryptedWithCustomerManagedKey.yaml
new file mode 100644
index 0000000000..e354d366ea
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureDataFactoriesEncryptedWithCustomerManagedKey.yaml
@@ -0,0 +1,17 @@
+metadata:
+ id: "CKV2_AZURE_15"
+ name: "Ensure that Azure data factories are encrypted with a customer-managed key"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_data_factory
+ operator: within
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - azurerm_data_factory
+ connected_resource_types:
+ - azurerm_data_factory_linked_service_key_vault
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureMSSQLServerHasSecurityAlertPolicy.yaml b/checkov/terraform/checks/graph_checks/azure/AzureMSSQLServerHasSecurityAlertPolicy.yaml
new file mode 100644
index 0000000000..885a2d85dc
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureMSSQLServerHasSecurityAlertPolicy.yaml
@@ -0,0 +1,31 @@
+metadata:
+ id: "CKV2_AZURE_13"
+ name: "Ensure that sql servers enables data security policy"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_sql_server
+ operator: within
+ - or:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: not_exists
+ cond_type: connection
+ - and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: equals
+ cond_type: attribute
+ attribute: state
+ value: "Enabled"
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureStorageAccountsUseCustomerManagedKeyForEncryption.yaml b/checkov/terraform/checks/graph_checks/azure/AzureStorageAccountsUseCustomerManagedKeyForEncryption.yaml
new file mode 100644
index 0000000000..58d6877780
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureStorageAccountsUseCustomerManagedKeyForEncryption.yaml
@@ -0,0 +1,23 @@
+metadata:
+ id: "CKV2_AZURE_18"
+ name: "Ensure that Storage Accounts use customer-managed key for encryption"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_storage_account
+ operator: within
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - azurerm_storage_account
+ connected_resource_types:
+ - azurerm_storage_account_customer_managed_key
+ - cond_type: attribute
+ operator: not_equals
+ attribute: key_vault_id
+ resource_types:
+ - azurerm_storage_account_customer_managed_key
+ value: ""
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached.yaml b/checkov/terraform/checks/graph_checks/azure/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached.yaml
new file mode 100644
index 0000000000..78506686e5
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached.yaml
@@ -0,0 +1,18 @@
+metadata:
+ id: "CKV2_AZURE_19"
+ name: "Ensure that Azure Synapse workspaces have no IP firewall rules attached"
+ category: "NETWORKING"
+definition:
+ and:
+ - resource_types:
+ - azurerm_synapse_workspace
+ connected_resource_types:
+ - azurerm_synapse_firewall_rule
+ operator: not_exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_synapse_workspace
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/AzureUnattachedDisksAreEncrypted.yaml b/checkov/terraform/checks/graph_checks/azure/AzureUnattachedDisksAreEncrypted.yaml
new file mode 100644
index 0000000000..7cae3d45f4
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/AzureUnattachedDisksAreEncrypted.yaml
@@ -0,0 +1,43 @@
+metadata:
+ id: "CKV2_AZURE_14"
+ name: "Ensure that Unattached disks are encrypted"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_virtual_machine
+ operator: within
+ - or:
+ - resource_types:
+ - azurerm_virtual_machine
+ connected_resource_types:
+ - azurerm_managed_disk
+ operator: not_exists
+ cond_type: connection
+ - and:
+ - resource_types:
+ - azurerm_virtual_machine
+ connected_resource_types:
+ - azurerm_managed_disk
+ operator: exists
+ cond_type: connection
+ - or:
+ - cond_type: attribute
+ operator: exists
+ attribute: disk_encryption_set_id
+ resource_types:
+ - azurerm_managed_disk
+ - and:
+ - cond_type: attribute
+ operator: exists
+ attribute: encryption_settings
+ resource_types:
+ - azurerm_managed_disk
+ - cond_type: attribute
+ operator: not_equals
+ attribute: encryption_settings.enabled
+ value: false
+ resource_types:
+ - azurerm_managed_disk
diff --git a/checkov/terraform/checks/graph_checks/azure/DataExplorerEncryptionUsesCustomKey.yaml b/checkov/terraform/checks/graph_checks/azure/DataExplorerEncryptionUsesCustomKey.yaml
new file mode 100644
index 0000000000..f41d104c72
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/DataExplorerEncryptionUsesCustomKey.yaml
@@ -0,0 +1,19 @@
+metadata:
+ id: "CKV2_AZURE_11"
+ name: "Ensure that Azure Data Explorer encryption at rest uses a customer-managed key"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - resource_types:
+ - azurerm_kusto_cluster
+ connected_resource_types:
+ - azurerm_kusto_cluster_customer_managed_key
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_kusto_cluster
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/MSQLenablesCustomerManagedKey.yaml b/checkov/terraform/checks/graph_checks/azure/MSQLenablesCustomerManagedKey.yaml
new file mode 100644
index 0000000000..98d86f5fff
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/MSQLenablesCustomerManagedKey.yaml
@@ -0,0 +1,24 @@
+metadata:
+ id: "CKV2_AZURE_16"
+ name: "Ensure that MySQL server enables customer-managed key for encryption"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - resource_types:
+ - azurerm_mysql_server
+ connected_resource_types:
+ - azurerm_mysql_server_key
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_mysql_server_key
+ connected_resource_types:
+ - azurerm_key_vault_key
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_mysql_server
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/PGSQLenablesCustomerManagedKey.yaml b/checkov/terraform/checks/graph_checks/azure/PGSQLenablesCustomerManagedKey.yaml
new file mode 100644
index 0000000000..9870b84a35
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/PGSQLenablesCustomerManagedKey.yaml
@@ -0,0 +1,24 @@
+metadata:
+ id: "CKV2_AZURE_17"
+ name: "Ensure that PostgreSQL server enables customer-managed key for encryption"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - resource_types:
+ - azurerm_postgresql_server
+ connected_resource_types:
+ - azurerm_postgresql_server_key
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_postgresql_server_key
+ connected_resource_types:
+ - azurerm_key_vault_key
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_postgresql_server_key
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/StorageContainerActivityLogsNotPublic.yaml b/checkov/terraform/checks/graph_checks/azure/StorageContainerActivityLogsNotPublic.yaml
new file mode 100644
index 0000000000..c9ef7522b7
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/StorageContainerActivityLogsNotPublic.yaml
@@ -0,0 +1,40 @@
+metadata:
+ name: "Ensure the storage container storing the activity logs is not publicly accessible"
+ id: "CKV2_AZURE_8"
+ category: "LOGGING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_storage_account
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - azurerm_monitor_activity_log_alert
+ connected_resource_types:
+ - azurerm_storage_account
+ operator: exists
+ - cond_type: attribute
+ resource_types:
+ - azurerm_monitor_activity_log_alert
+ attribute: criteria.resource_id
+ operator: exists
+ - or:
+ - cond_type: attribute
+ resource_types:
+ - azurerm_monitor_activity_log_alert
+ attribute: enabled
+ operator: not_exists
+ - cond_type: attribute
+ resource_types:
+ - azurerm_monitor_activity_log_alert
+ attribute: enabled
+ operator: equals
+ value: true
+ - cond_type: connection
+ resource_types:
+ - azurerm_storage_container
+ connected_resource_types:
+ - azurerm_storage_account
+ operator: exists
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/StorageCriticalDataEncryptedCMK.yaml b/checkov/terraform/checks/graph_checks/azure/StorageCriticalDataEncryptedCMK.yaml
new file mode 100644
index 0000000000..ec11ffa67f
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/StorageCriticalDataEncryptedCMK.yaml
@@ -0,0 +1,17 @@
+metadata:
+ name: "Ensure storage for critical data are encrypted with Customer Managed Key"
+ id: "CKV2_AZURE_1"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_storage_account
+ operator: within
+ - cond_type: connection
+ resource_types:
+ - azurerm_storage_account
+ connected_resource_types:
+ - azurerm_storage_account_customer_managed_key
+ operator: exists
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForBlobService.yaml b/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForBlobService.yaml
new file mode 100644
index 0000000000..1bcae3735e
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForBlobService.yaml
@@ -0,0 +1,35 @@
+metadata:
+ id: "CKV2_AZURE_21"
+ name: "Ensure Storage logging is enabled for Blob service for read requests"
+ category: "LOGGING"
+definition:
+ and:
+ - resource_types:
+ - azurerm_storage_container
+ connected_resource_types:
+ - azurerm_storage_account
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_storage_account
+ connected_resource_types:
+ - azurerm_log_analytics_storage_insights
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_log_analytics_storage_insights
+ operator: exists
+ cond_type: attribute
+ attribute: blob_container_names
+ - resource_types:
+ - azurerm_storage_container
+ operator: equals
+ cond_type: attribute
+ attribute: container_access_type
+ value: "blob"
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_storage_container
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForTableService.yaml b/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForTableService.yaml
new file mode 100644
index 0000000000..0014bb61a0
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/StorageLoggingIsEnabledForTableService.yaml
@@ -0,0 +1,28 @@
+metadata:
+ id: "CKV2_AZURE_20"
+ name: "Ensure Storage logging is enabled for Table service for read requests"
+ category: "LOGGING"
+definition:
+ and:
+ - resource_types:
+ - azurerm_storage_table
+ connected_resource_types:
+ - azurerm_storage_account
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_storage_account
+ connected_resource_types:
+ - azurerm_log_analytics_storage_insights
+ operator: exists
+ cond_type: connection
+ - resource_types:
+ - azurerm_log_analytics_storage_insights
+ operator: exists
+ cond_type: attribute
+ attribute: table_names
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_storage_table
+ operator: within
diff --git a/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReports.yaml b/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReports.yaml
new file mode 100644
index 0000000000..3dc39575c4
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReports.yaml
@@ -0,0 +1,45 @@
+metadata:
+ name: "Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server"
+ id: "CKV2_AZURE_4"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - "azurerm_mssql_server_security_alert_policy"
+ attribute: state
+ operator: equals
+ value: Enabled
+ - resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ connected_resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ operator: exists
+ cond_type: connection
+ - or :
+ - cond_type: attribute
+ resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ attribute: 'recurring_scans.email_subscription_admins'
+ operator: equals
+ value: true
+ - cond_type: attribute
+ resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ attribute: 'recurring_scans.emails'
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_mssql_server_security_alert_policy
+ operator: within
+
+
+
+
diff --git a/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReportsToAdmins.yaml b/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReportsToAdmins.yaml
new file mode 100644
index 0000000000..c6028b439b
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VAconfiguredToSendReportsToAdmins.yaml
@@ -0,0 +1,44 @@
+metadata:
+ name: "Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server"
+ id: "CKV2_AZURE_5"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - "azurerm_mssql_server_security_alert_policy"
+ attribute: state
+ operator: equals
+ value: Enabled
+ - resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ connected_resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ attribute: 'recurring_scans.email_subscription_admins'
+ operator: equals
+ value: true
+ - cond_type: attribute
+ resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ attribute: 'recurring_scans.emails'
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_mssql_server_security_alert_policy
+ operator: within
+
+
+
+
diff --git a/checkov/terraform/checks/graph_checks/azure/VAisEnabledInStorageAccount.yaml b/checkov/terraform/checks/graph_checks/azure/VAisEnabledInStorageAccount.yaml
new file mode 100644
index 0000000000..cedab96538
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VAisEnabledInStorageAccount.yaml
@@ -0,0 +1,25 @@
+metadata:
+ name: "Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account"
+ id: "CKV2_AZURE_2"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_sql_server
+ operator: within
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - "azurerm_mssql_server_security_alert_policy"
+ attribute: state
+ operator: equals
+ value: Enabled
+
+
diff --git a/checkov/terraform/checks/graph_checks/azure/VAsetPeriodicScansOnSQL.yaml b/checkov/terraform/checks/graph_checks/azure/VAsetPeriodicScansOnSQL.yaml
new file mode 100644
index 0000000000..e3c8398c5e
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VAsetPeriodicScansOnSQL.yaml
@@ -0,0 +1,36 @@
+metadata:
+ name: "Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server"
+ id: "CKV2_AZURE_3"
+ category: "GENERAL_SECURITY"
+definition:
+ and:
+ - resource_types:
+ - azurerm_sql_server
+ connected_resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - "azurerm_mssql_server_security_alert_policy"
+ attribute: state
+ operator: equals
+ value: Enabled
+ - resource_types:
+ - azurerm_mssql_server_security_alert_policy
+ connected_resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - azurerm_mssql_server_vulnerability_assessment
+ attribute: 'recurring_scans.enabled'
+ operator: equals
+ value: true
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_mssql_server_security_alert_policy
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/VMHasBackUpMachine.yaml b/checkov/terraform/checks/graph_checks/azure/VMHasBackUpMachine.yaml
new file mode 100644
index 0000000000..47c96ec148
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VMHasBackUpMachine.yaml
@@ -0,0 +1,19 @@
+metadata:
+ id: "CKV2_AZURE_12"
+ name: "Ensure that virtual machines are backed up using Azure Backup"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - resource_types:
+ - azurerm_virtual_machine
+ connected_resource_types:
+ - azurerm_backup_protected_vm
+ operator: exists
+ attribute: networking
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_virtual_machine
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/azure/VirtualMachinesUtilizingManagedDisks.yaml b/checkov/terraform/checks/graph_checks/azure/VirtualMachinesUtilizingManagedDisks.yaml
new file mode 100644
index 0000000000..95368f893e
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/azure/VirtualMachinesUtilizingManagedDisks.yaml
@@ -0,0 +1,21 @@
+metadata:
+ name: "Ensure Virtual Machines are utilizing Managed Disks"
+ category: "GENERAL_SECURITY"
+ id: "CKV2_AZURE_9"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - azurerm_virtual_machine
+ operator: within
+ - cond_type: attribute
+ attribute: storage_os_disk.managed_disk_type
+ operator: exists
+ resource_types:
+ - azurerm_virtual_machine
+ - cond_type: attribute
+ operator: not_exists
+ attribute: storage_os_disk.vhd_uri
+ resource_types:
+ - azurerm_virtual_machine
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/gcp/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword.yaml b/checkov/terraform/checks/graph_checks/gcp/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword.yaml
new file mode 100644
index 0000000000..ba1fcd688c
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword.yaml
@@ -0,0 +1,44 @@
+metadata:
+ id: "CKV2_GCP_7"
+ name: "Ensure that a MySQL database instance does not allow anyone to connect with administrative privileges"
+ category: "IAM"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_sql_database_instance
+ operator: within
+ - or:
+ - resource_types:
+ - google_sql_database_instance
+ connected_resource_types:
+ - google_sql_user
+ operator: not_exists
+ cond_type: connection
+ - and:
+ - resource_types:
+ - google_sql_database_instance
+ connected_resource_types:
+ - google_sql_user
+ operator: exists
+ cond_type: connection
+ - or:
+ - cond_type: attribute
+ attribute: name
+ operator: not_starting_with
+ value: "root"
+ resource_types:
+ - google_sql_user
+ - and:
+ - cond_type: attribute
+ attribute: name
+ operator: starting_with
+ value: "root"
+ resource_types:
+ - google_sql_user
+ - cond_type: attribute
+ attribute: password
+ operator: exists
+ resource_types:
+ - google_sql_user
diff --git a/checkov/terraform/checks/graph_checks/gcp/GCPAuditLogsConfiguredForAllServicesAndUsers.yaml b/checkov/terraform/checks/graph_checks/gcp/GCPAuditLogsConfiguredForAllServicesAndUsers.yaml
new file mode 100644
index 0000000000..fbc2a2d3c1
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/GCPAuditLogsConfiguredForAllServicesAndUsers.yaml
@@ -0,0 +1,28 @@
+metadata:
+ id: "CKV2_GCP_5"
+ name: "Ensure that Cloud Audit Logging is configured properly across all services and all users from a project"
+ category: "Logging"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_project
+ operator: within
+ - resource_types:
+ - google_project
+ connected_resource_types:
+ - google_project_iam_audit_config
+ operator: exists
+ cond_type: connection
+ - cond_type: attribute
+ resource_types:
+ - google_project_iam_audit_config
+ attribute: "audit_log_config.*.exempted_members"
+ operator: not_exists
+ - cond_type: attribute
+ resource_types:
+ - google_project_iam_audit_config
+ attribute: service
+ operator: equals
+ value: "allServices"
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/gcp/GCPKMSCryptoKeysAreNotPubliclyAccessible.yaml b/checkov/terraform/checks/graph_checks/gcp/GCPKMSCryptoKeysAreNotPubliclyAccessible.yaml
new file mode 100644
index 0000000000..96abb21377
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/GCPKMSCryptoKeysAreNotPubliclyAccessible.yaml
@@ -0,0 +1,64 @@
+metadata:
+ id: "CKV2_GCP_6"
+ name: "Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_kms_crypto_key
+ operator: within
+ - and:
+ - or:
+ - cond_type: connection
+ operator: not_exists
+ resource_types:
+ - google_kms_crypto_key
+ connected_resource_types:
+ - google_kms_crypto_key_iam_member
+ - and:
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - google_kms_crypto_key
+ connected_resource_types:
+ - google_kms_crypto_key_iam_member
+ - cond_type: attribute
+ attribute: member
+ operator: not_equals
+ value: "allAuthenticatedUsers"
+ resource_types:
+ - google_kms_crypto_key_iam_member
+ - cond_type: attribute
+ attribute: member
+ operator: not_equals
+ value: "allUsers"
+ resource_types:
+ - google_kms_crypto_key_iam_member
+ - or:
+ - cond_type: connection
+ operator: not_exists
+ resource_types:
+ - google_kms_crypto_key
+ connected_resource_types:
+ - google_kms_crypto_key_iam_binding
+ - and:
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - google_kms_crypto_key
+ connected_resource_types:
+ - google_kms_crypto_key_iam_binding
+ - cond_type: attribute
+ attribute: members
+ operator: not_contains
+ value: "allAuthenticatedUsers"
+ resource_types:
+ - google_kms_crypto_key_iam_binding
+ - cond_type: attribute
+ attribute: members
+ operator: not_contains
+ value: "allUsers"
+ resource_types:
+ - google_kms_crypto_key_iam_binding
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/gcp/GCPLogBucketsConfiguredUsingLock.yaml b/checkov/terraform/checks/graph_checks/gcp/GCPLogBucketsConfiguredUsingLock.yaml
new file mode 100644
index 0000000000..385c83ae37
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/GCPLogBucketsConfiguredUsingLock.yaml
@@ -0,0 +1,38 @@
+metadata:
+ id: "CKV2_GCP_4"
+ name: "Ensure that retention policies on log buckets are configured using Bucket Lock"
+ category: "LOGGING"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_logging_organization_sink
+ - google_logging_folder_sink
+ - google_logging_project_sink
+ operator: within
+ - or:
+ - cond_type: connection
+ operator: not_exists
+ resource_types:
+ - google_logging_organization_sink
+ - google_logging_project_sink
+ - google_logging_folder_sink
+ connected_resource_types:
+ - google_storage_bucket
+ - and:
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - google_logging_organization_sink
+ - google_logging_project_sink
+ - google_logging_folder_sink
+ connected_resource_types:
+ - google_storage_bucket
+ - cond_type: attribute
+ attribute: "retention_policy.is_locked"
+ value: true
+ operator: equals
+ resource_types:
+ - google_storage_bucket
+
diff --git a/checkov/terraform/checks/graph_checks/gcp/GCPProjectHasNoLegacyNetworks.yaml b/checkov/terraform/checks/graph_checks/gcp/GCPProjectHasNoLegacyNetworks.yaml
new file mode 100644
index 0000000000..a1067ec808
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/GCPProjectHasNoLegacyNetworks.yaml
@@ -0,0 +1,39 @@
+metadata:
+ id: "CKV2_GCP_2"
+ name: "Ensure legacy networks do not exist for a project"
+ category: "Networking"
+definition:
+ and:
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_project
+ operator: within
+ - or:
+ - resource_types:
+ - google_compute_network
+ connected_resource_types:
+ - google_project
+ operator: not_exists
+ attribute: project_id
+ cond_type: connection
+ - and:
+ - resource_types:
+ - google_compute_network
+ connected_resource_types:
+ - google_project
+ operator: exists
+ attribute: project_id
+ cond_type: connection
+ - or:
+ - resource_types:
+ - google_compute_network
+ operator: not_exists
+ attribute: auto_create_subnetworks
+ cond_type: attribute
+ - resource_types:
+ - google_compute_network
+ operator: equals
+ value: false
+ attribute: auto_create_subnetworks
+ cond_type: attribute
\ No newline at end of file
diff --git a/checkov/terraform/checks/graph_checks/gcp/GKEClustersAreNotUsingDefaultServiceAccount.yaml b/checkov/terraform/checks/graph_checks/gcp/GKEClustersAreNotUsingDefaultServiceAccount.yaml
new file mode 100644
index 0000000000..6132734809
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/GKEClustersAreNotUsingDefaultServiceAccount.yaml
@@ -0,0 +1,24 @@
+metadata:
+ id: "CKV2_GCP_1"
+ name: "Ensure GKE clusters are not running using the Compute Engine default service account "
+ category: "NETWORKING"
+definition:
+ and:
+ - resource_types:
+ - google_project_default_service_accounts
+ connected_resource_types:
+ - google_container_node_pool
+ operator: not_exists
+ cond_type: connection
+ - resource_types:
+ - google_project_default_service_accounts
+ connected_resource_types:
+ - google_container_cluster
+ operator: not_exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_project_default_service_accounts
+ operator: within
+
diff --git a/checkov/terraform/checks/graph_checks/gcp/ServiceAccountHasGCPmanagedKey.yaml b/checkov/terraform/checks/graph_checks/gcp/ServiceAccountHasGCPmanagedKey.yaml
new file mode 100644
index 0000000000..f35fef81fa
--- /dev/null
+++ b/checkov/terraform/checks/graph_checks/gcp/ServiceAccountHasGCPmanagedKey.yaml
@@ -0,0 +1,19 @@
+metadata:
+ id: "CKV2_GCP_3"
+ name: "Ensure that there are only GCP-managed service account keys for each service account"
+ category: "ENCRYPTION"
+scope:
+ provider: "GCP"
+definition:
+ and:
+ - resource_types:
+ - google_service_account
+ connected_resource_types:
+ - google_service_account_key
+ operator: exists
+ cond_type: connection
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - google_service_account
+ operator: within
\ No newline at end of file
diff --git a/checkov/terraform/checks/module/base_module_check.py b/checkov/terraform/checks/module/base_module_check.py
index dfc2ee9fb5..94647dec5f 100644
--- a/checkov/terraform/checks/module/base_module_check.py
+++ b/checkov/terraform/checks/module/base_module_check.py
@@ -1,11 +1,15 @@
from abc import abstractmethod
+from typing import List, Optional, Dict, Any
from checkov.common.checks.base_check import BaseCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.module.registry import module_registry
class BaseModuleCheck(BaseCheck):
- def __init__(self, name, id, categories, supported_resources=None):
+ def __init__(
+ self, name: str, id: str, categories: List[CheckCategories], supported_resources: Optional[List[str]] = None
+ ) -> None:
"""
Base class for terraform module call related checks.
@@ -18,16 +22,17 @@ def __init__(self, name, id, categories, supported_resources=None):
checks that extend this class.
"""
if supported_resources is None:
- supported_resources = ['module']
- super().__init__(name=name, id=id, categories=categories, supported_entities=supported_resources,
- block_type="module")
+ supported_resources = ["module"]
+ super().__init__(
+ name=name, id=id, categories=categories, supported_entities=supported_resources, block_type="module"
+ )
self.supported_resources = supported_resources
module_registry.register(self)
- def scan_entity_conf(self, conf, entity_type):
+ def scan_entity_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
# entity_type is always 'module'
return self.scan_module_conf(conf)
@abstractmethod
- def scan_module_conf(self, conf):
+ def scan_module_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
raise NotImplementedError()
diff --git a/checkov/terraform/checks/module/base_registry.py b/checkov/terraform/checks/module/base_registry.py
index 0892495174..7e042ded97 100644
--- a/checkov/terraform/checks/module/base_registry.py
+++ b/checkov/terraform/checks/module/base_registry.py
@@ -1,13 +1,9 @@
+from typing import Dict, Any, Tuple
+
from checkov.common.checks.base_check_registry import BaseCheckRegistry
class Registry(BaseCheckRegistry):
-
- def __init__(self):
- super().__init__()
-
- def extract_entity_details(self, entity):
- module_name = list(entity.keys())[0]
- module_configuration = entity[module_name]
+ def extract_entity_details(self, entity: Dict[str, Dict[str, Any]]) -> Tuple[str, str, Dict[str, Any]]:
+ module_name, module_configuration = next(iter(entity.items()))
return "module", module_name, module_configuration
-
diff --git a/checkov/terraform/checks/provider/aws/__init__.py b/checkov/terraform/checks/provider/aws/__init__.py
index d66c1343db..c619945591 100644
--- a/checkov/terraform/checks/provider/aws/__init__.py
+++ b/checkov/terraform/checks/provider/aws/__init__.py
@@ -2,4 +2,4 @@
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith("__init__.py")]
diff --git a/checkov/terraform/checks/provider/aws/credentials.py b/checkov/terraform/checks/provider/aws/credentials.py
index de7b408fa0..fd631b7db3 100644
--- a/checkov/terraform/checks/provider/aws/credentials.py
+++ b/checkov/terraform/checks/provider/aws/credentials.py
@@ -1,19 +1,20 @@
import re
+from typing import Dict, List, Any
+
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.provider.base_check import BaseProviderCheck
from checkov.common.models.consts import access_key_pattern, secret_key_pattern
class AWSCredentials(BaseProviderCheck):
-
- def __init__(self):
+ def __init__(self) -> None:
name = "Ensure no hard coded AWS access key and secret key exists in provider"
id = "CKV_AWS_41"
- supported_provider = ['aws']
+ supported_provider = ["aws"]
categories = [CheckCategories.SECRETS]
super().__init__(name=name, id=id, categories=categories, supported_provider=supported_provider)
- def scan_provider_conf(self, conf):
+ def scan_provider_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
"""
see: https://www.terraform.io/docs/providers/aws/index.html#static-credentials
"""
@@ -24,7 +25,7 @@ def scan_provider_conf(self, conf):
return CheckResult.PASSED
@staticmethod
- def secret_found(conf, field, pattern):
+ def secret_found(conf: Dict[str, List[Any]], field: str, pattern: str) -> bool:
if field in conf.keys():
value = conf[field][0]
if re.match(pattern, value) is not None:
diff --git a/checkov/terraform/checks/provider/base_check.py b/checkov/terraform/checks/provider/base_check.py
index c698388aaf..b9ab76679d 100644
--- a/checkov/terraform/checks/provider/base_check.py
+++ b/checkov/terraform/checks/provider/base_check.py
@@ -1,30 +1,22 @@
from abc import abstractmethod
+from typing import List, Dict, Any
from checkov.common.checks.base_check import BaseCheck
-from checkov.common.multi_signature import multi_signature
+from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.provider.registry import provider_registry
class BaseProviderCheck(BaseCheck):
- def __init__(self, name, id, categories, supported_provider):
- super().__init__(name=name, id=id, categories=categories, supported_entities=supported_provider,
- block_type="provider")
+ def __init__(self, name: str, id: str, categories: List[CheckCategories], supported_provider: List[str]) -> None:
+ super().__init__(
+ name=name, id=id, categories=categories, supported_entities=supported_provider, block_type="provider"
+ )
self.supported_provider = supported_provider
provider_registry.register(self)
- def scan_entity_conf(self, conf, entity_type):
- return self.scan_provider_conf(conf, entity_type)
+ def scan_entity_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
+ return self.scan_provider_conf(conf)
- @multi_signature()
@abstractmethod
- def scan_provider_conf(self, conf, provider_type):
+ def scan_provider_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
raise NotImplementedError()
-
- @classmethod
- @scan_provider_conf.add_signature(args=["self", "conf"])
- def _scan_provider_conf_self_conf(cls, wrapped):
- def wrapper(self, conf, provider_type=None):
- # keep default argument for entity_type so old code, that doesn't set it, will work.
- return wrapped(self, conf)
-
- return wrapper
diff --git a/checkov/terraform/checks/provider/base_registry.py b/checkov/terraform/checks/provider/base_registry.py
index e22a7f7bbf..f745fdd14a 100644
--- a/checkov/terraform/checks/provider/base_registry.py
+++ b/checkov/terraform/checks/provider/base_registry.py
@@ -1,14 +1,11 @@
+from typing import Dict, Any, Tuple
+
from checkov.common.checks.base_check_registry import BaseCheckRegistry
class Registry(BaseCheckRegistry):
-
- def __init__(self):
- super().__init__()
-
- def extract_entity_details(self, entity):
+ def extract_entity_details(self, entity: Dict[str, Any]) -> Tuple[str, str, Dict[str, Any]]:
provider_type = list(entity.keys())[0]
provider_name = list(entity.keys())[0]
provider_configuration = entity[provider_name]
return provider_type, provider_name, provider_configuration
-
diff --git a/checkov/terraform/checks/provider/linode/__init__.py b/checkov/terraform/checks/provider/linode/__init__.py
index d66c1343db..c619945591 100644
--- a/checkov/terraform/checks/provider/linode/__init__.py
+++ b/checkov/terraform/checks/provider/linode/__init__.py
@@ -2,4 +2,4 @@
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith("__init__.py")]
diff --git a/checkov/terraform/checks/provider/linode/credentials.py b/checkov/terraform/checks/provider/linode/credentials.py
index ad9de8fdb9..f9ec25619d 100644
--- a/checkov/terraform/checks/provider/linode/credentials.py
+++ b/checkov/terraform/checks/provider/linode/credentials.py
@@ -1,25 +1,26 @@
import re
+from typing import Dict, List, Any
+
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.provider.base_check import BaseProviderCheck
from checkov.common.models.consts import linode_token_pattern
class LinodeCredentials(BaseProviderCheck):
-
- def __init__(self):
+ def __init__(self) -> None:
name = "Ensure no hard coded Linode tokens exist in provider"
id = "CKV_LIN_1"
- supported_provider = ['linode']
+ supported_provider = ["linode"]
categories = [CheckCategories.SECRETS]
super().__init__(name=name, id=id, categories=categories, supported_provider=supported_provider)
- def scan_provider_conf(self, conf):
+ def scan_provider_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
if self.secret_found(conf, "token", linode_token_pattern):
return CheckResult.FAILED
return CheckResult.PASSED
@staticmethod
- def secret_found(conf, field, pattern):
+ def secret_found(conf: Dict[str, List[Any]], field: str, pattern: str) -> bool:
if field in conf.keys():
value = conf[field][0]
if re.match(pattern, value) is not None:
diff --git a/checkov/terraform/checks/resource/__init__.py b/checkov/terraform/checks/resource/__init__.py
index 1bae3c30b3..192a0303d5 100644
--- a/checkov/terraform/checks/resource/__init__.py
+++ b/checkov/terraform/checks/resource/__init__.py
@@ -2,4 +2,4 @@
from checkov.terraform.checks.resource.gcp import *
from checkov.terraform.checks.resource.azure import *
from checkov.terraform.checks.resource.github import *
-from checkov.terraform.checks.resource.linode import *
\ No newline at end of file
+from checkov.terraform.checks.resource.linode import *
diff --git a/checkov/terraform/checks/resource/aws/ALBDropHttpHeaders.py b/checkov/terraform/checks/resource/aws/ALBDropHttpHeaders.py
new file mode 100644
index 0000000000..20a6a5b64d
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ALBDropHttpHeaders.py
@@ -0,0 +1,22 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class ALBDropHttpHeaders(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that ALB drops HTTP headers"
+ id = "CKV_AWS_131"
+ supported_resources = ["aws_lb", "aws_alb"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if conf.get("load_balancer_type") in (["gateway"], ["network"]):
+ return CheckResult.UNKNOWN
+ return super().scan_resource_conf(conf)
+
+ def get_inspected_key(self):
+ return "drop_invalid_header_fields"
+
+
+check = ALBDropHttpHeaders()
diff --git a/checkov/terraform/checks/resource/aws/APIGatewayAuthorization.py b/checkov/terraform/checks/resource/aws/APIGatewayAuthorization.py
index b591cc029d..20e42463d2 100644
--- a/checkov/terraform/checks/resource/aws/APIGatewayAuthorization.py
+++ b/checkov/terraform/checks/resource/aws/APIGatewayAuthorization.py
@@ -12,8 +12,8 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
- self.evaluated_keys = ['http_method', 'authorization']
- if conf['http_method'][0] != "OPTIONS" and conf['authorization'][0] == "NONE":
+ self.evaluated_keys = ['http_method', 'authorization', 'api_key_required']
+ if conf['http_method'][0] != "OPTIONS" and conf['authorization'][0] == "NONE" and ('api_key_required' not in conf or conf['api_key_required'][0] == False):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/APIGatewayCacheEnable.py b/checkov/terraform/checks/resource/aws/APIGatewayCacheEnable.py
new file mode 100644
index 0000000000..422047e841
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/APIGatewayCacheEnable.py
@@ -0,0 +1,18 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class APIGatewayCacheEnable(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure API Gateway caching is enabled"
+ id = "CKV_AWS_120"
+ supported_resources = ['aws_api_gateway_stage']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "cache_cluster_enabled"
+
+
+check = APIGatewayCacheEnable()
diff --git a/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py b/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
index d91b706c82..64061cf6cd 100644
--- a/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
+++ b/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
@@ -42,9 +42,10 @@ def scan_resource_conf(self, conf):
if isinstance(rule, dict):
if self.contains_violation(rule):
self.evaluated_keys = [
- f'ingress/{ingress_conf.index(ingress_rule)}/from_port',
- f'ingress/{ingress_conf.index(ingress_rule)}/to_port',
- f'ingress/{ingress_conf.index(ingress_rule)}/cidr_blocks',
+ f'ingress/[{ingress_conf.index(ingress_rule)}]/from_port',
+ f'ingress/[{ingress_conf.index(ingress_rule)}]/to_port',
+ f'ingress/[{ingress_conf.index(ingress_rule)}]/cidr_blocks',
+ f'ingress/[{ingress_conf.index(ingress_rule)}]/ipv6_cidr_blocks',
]
return CheckResult.FAILED
@@ -53,7 +54,7 @@ def scan_resource_conf(self, conf):
if 'type' in conf: # This means it's an SG_rule resource.
type = force_list(conf['type'])[0]
if type == 'ingress':
- self.evaluated_keys = ['from_port','to_port','cidr_blocks']
+ self.evaluated_keys = ['from_port','to_port','cidr_blocks', 'ipv6_cidr_blocks']
if self.contains_violation(conf):
return CheckResult.FAILED
return CheckResult.PASSED
@@ -66,9 +67,18 @@ def contains_violation(self, conf):
from_port = force_int(force_list(conf.get('from_port',[{-1}]))[0])
to_port = force_int(force_list(conf.get('to_port',[{-1}]))[0])
+ if from_port == 0 and to_port == 0:
+ to_port = 65535
+
if from_port is not None and to_port is not None and (from_port <= self.port <= to_port):
- cidr_blocks = force_list(conf.get('cidr_blocks', [[]])[0])
+ conf_cidr_blocks = conf.get('cidr_blocks', [[]])
+ if len(conf_cidr_blocks) > 0:
+ conf_cidr_blocks = conf_cidr_blocks[0]
+ cidr_blocks = force_list(conf_cidr_blocks)
if "0.0.0.0/0" in cidr_blocks:
return True
-
+ ipv6_cidr_blocks = conf.get('ipv6_cidr_blocks', [])
+ if len(ipv6_cidr_blocks) > 0 and ipv6_cidr_blocks[0] is not None and \
+ any(ip in ['::/0', '0000:0000:0000:0000:0000:0000:0000:0000/0'] for ip in ipv6_cidr_blocks[0]):
+ return True
return False
diff --git a/checkov/terraform/checks/resource/aws/AppLoadBalancerTLS12.py b/checkov/terraform/checks/resource/aws/AppLoadBalancerTLS12.py
new file mode 100644
index 0000000000..d41f802a24
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/AppLoadBalancerTLS12.py
@@ -0,0 +1,45 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_list
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AppLoadBalancerTLS12(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that load balancer is using TLS 1.2"
+ id = "CKV_AWS_103"
+ supported_resources = ["aws_lb_listener"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(
+ name=name,
+ id=id,
+ categories=categories,
+ supported_resources=supported_resources,
+ )
+
+ def scan_resource_conf(self, conf):
+ key = "protocol"
+ if key in conf.keys():
+ if conf[key] in (["HTTPS"], ["TLS"]):
+ # Only interested in HTTPS & TLS listeners
+ policy = "ssl_policy"
+ if policy in conf.keys():
+ name = str(conf[policy]).strip("['']")
+ if name.startswith("ELBSecurityPolicy-FS-1-2") or name.startswith("ELBSecurityPolicy-TLS-1-2"):
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+ elif conf[key] in (["TCP"], ["UDP"], ["TCP_UDP"]):
+ return CheckResult.PASSED
+ else:
+ for action in conf.get("default_action", []):
+ for redirect in force_list(action.get("redirect", [])):
+ if redirect.get("protocol", []) == ["HTTPS"]:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+
+
+check = AppLoadBalancerTLS12()
diff --git a/checkov/terraform/checks/resource/aws/AthenaWorkgroupEncryption.py b/checkov/terraform/checks/resource/aws/AthenaWorkgroupEncryption.py
new file mode 100644
index 0000000000..0bdbf5653e
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/AthenaWorkgroupEncryption.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AthenaWorkgroupEncryption(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that Athena Workgroup is encrypted"
+ id = "CKV_AWS_159"
+ supported_resources = ['aws_athena_workgroup']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "configuration/[0]/result_configuration/[0]/encryption_configuration/[0]/encryption_option"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+check = AthenaWorkgroupEncryption()
diff --git a/checkov/terraform/checks/resource/aws/AutoScalingTagging.py b/checkov/terraform/checks/resource/aws/AutoScalingTagging.py
new file mode 100644
index 0000000000..a37acead2f
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/AutoScalingTagging.py
@@ -0,0 +1,22 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AutoScalingTagging(BaseResourceCheck):
+ def __init__(self):
+ name = "Autoscaling groups should supply tags to launch configurations"
+ id = "CKV_AWS_153"
+ supported_resources = ['aws_autoscaling_group']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Looks for tag or tags
+ """
+
+ if "tag" in conf.keys() or "tags" in conf.keys():
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+check = AutoScalingTagging()
diff --git a/checkov/terraform/checks/resource/aws/BackupVaultEncrypted.py b/checkov/terraform/checks/resource/aws/BackupVaultEncrypted.py
new file mode 100644
index 0000000000..514f93b6dc
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/BackupVaultEncrypted.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class BackupVaultEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Backup Vault is encrypted at rest using KMS CMK"
+ id = "CKV_AWS_166"
+ supported_resources = ['aws_backup_vault']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'kms_key_arn'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+check = BackupVaultEncrypted()
\ No newline at end of file
diff --git a/checkov/terraform/checks/resource/aws/CloudWatchLogGroupKMSKey.py b/checkov/terraform/checks/resource/aws/CloudWatchLogGroupKMSKey.py
new file mode 100644
index 0000000000..4dea523298
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/CloudWatchLogGroupKMSKey.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class CloudWatchLogGroupKMSKey(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that CloudWatch Log Group is encrypted by KMS"
+ id = "CKV_AWS_158"
+ supported_resources = ['aws_cloudwatch_log_group']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "kms_key_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = CloudWatchLogGroupKMSKey()
diff --git a/checkov/terraform/checks/resource/aws/cloudwatchLogGroupRetention.py b/checkov/terraform/checks/resource/aws/CloudWatchLogGroupRetention.py
similarity index 77%
rename from checkov/terraform/checks/resource/aws/cloudwatchLogGroupRetention.py
rename to checkov/terraform/checks/resource/aws/CloudWatchLogGroupRetention.py
index 629a9fe61b..9f6d40a880 100644
--- a/checkov/terraform/checks/resource/aws/cloudwatchLogGroupRetention.py
+++ b/checkov/terraform/checks/resource/aws/CloudWatchLogGroupRetention.py
@@ -3,9 +3,9 @@
from checkov.common.models.consts import ANY_VALUE
-class cloudwatchLogGroupRetention(BaseResourceValueCheck):
+class CloudWatchLogGroupRetention(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure cloudwatch log groups specify retention days"
+ name = "Ensure that CloudWatch Log Group specifies retention days"
id = "CKV_AWS_66"
supported_resource = ['aws_cloudwatch_log_group']
categories = [CheckCategories.LOGGING]
@@ -18,4 +18,4 @@ def get_expected_value(self):
return ANY_VALUE
-check = cloudwatchLogGroupRetention()
\ No newline at end of file
+check = CloudWatchLogGroupRetention()
diff --git a/checkov/terraform/checks/resource/aws/CloudformationStackNotificationArns.py b/checkov/terraform/checks/resource/aws/CloudformationStackNotificationArns.py
new file mode 100644
index 0000000000..bc4d74dc47
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/CloudformationStackNotificationArns.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class CloudformationStackNotificationArns(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that CloudFormation stacks are sending event notifications to an SNS topic"
+ id = "CKV_AWS_124"
+ supported_resources = ['aws_cloudformation_stack']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'notification_arns'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = CloudformationStackNotificationArns()
diff --git a/checkov/terraform/checks/resource/aws/CloudfrontDistributionEncryption.py b/checkov/terraform/checks/resource/aws/CloudfrontDistributionEncryption.py
index 7cb6515604..49bc8b6e5e 100644
--- a/checkov/terraform/checks/resource/aws/CloudfrontDistributionEncryption.py
+++ b/checkov/terraform/checks/resource/aws/CloudfrontDistributionEncryption.py
@@ -20,8 +20,8 @@ def scan_resource_conf(self, conf):
if "default_cache_behavior" in conf.keys():
self.evaluated_keys = 'default_cache_behavior/[0]/viewer_protocol_policy'
if isinstance(conf["default_cache_behavior"][0], dict):
- default_viewer_policy = conf["default_cache_behavior"][0]["viewer_protocol_policy"][0]
- if default_viewer_policy == "allow-all":
+ default_viewer_policy = conf["default_cache_behavior"][0]["viewer_protocol_policy"]
+ if default_viewer_policy and default_viewer_policy[0] == "allow-all":
return CheckResult.FAILED
if "ordered_cache_behavior" in conf.keys():
for behavior in conf["ordered_cache_behavior"]:
diff --git a/checkov/terraform/checks/resource/aws/CodeBuildEncrypted.py b/checkov/terraform/checks/resource/aws/CodeBuildEncrypted.py
new file mode 100644
index 0000000000..f3581b0e31
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/CodeBuildEncrypted.py
@@ -0,0 +1,22 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class CodeBuildEncrypted(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that CodeBuild projects are encrypted"
+ id = "CKV_AWS_147"
+ supported_resources = ['aws_codebuild_project']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "encryption_key"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = CodeBuildEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/CodeBuildProjectEncryption.py b/checkov/terraform/checks/resource/aws/CodeBuildProjectEncryption.py
index b3a9f84212..995a495277 100644
--- a/checkov/terraform/checks/resource/aws/CodeBuildProjectEncryption.py
+++ b/checkov/terraform/checks/resource/aws/CodeBuildProjectEncryption.py
@@ -16,11 +16,13 @@ def scan_resource_conf(self, conf):
return CheckResult.UNKNOWN
artifact = conf['artifacts'][0]
if isinstance(artifact, dict):
- if artifact['type'] == "NO_ARTIFACTS":
+ if artifact['type'] == ["NO_ARTIFACTS"]:
self.evaluated_keys = 'artifacts/[0]/type'
- elif 'encryption_disabled' in artifact and artifact['encryption_disabled']:
- self.evaluated_keys = 'artifacts/[0]/encryption_disabled'
- return CheckResult.FAILED
+ return CheckResult.UNKNOWN
+ if 'encryption_disabled' in artifact:
+ if artifact['encryption_disabled'] == [True]:
+ self.evaluated_keys = 'artifacts/[0]/encryption_disabled'
+ return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/ConfigConfgurationAggregatorAllRegions.py b/checkov/terraform/checks/resource/aws/ConfigConfgurationAggregatorAllRegions.py
new file mode 100644
index 0000000000..248854e083
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ConfigConfgurationAggregatorAllRegions.py
@@ -0,0 +1,34 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class ConfigConfigurationAggregator(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure AWS Config is enabled in all regions"
+ id = "CKV_AWS_121"
+ supported_resources = ['aws_config_configuration_aggregator']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Looks for account_aggregation_source / organization_aggregation_source
+ at aws_config_configuration_aggregator:
+ https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/config_configuration_aggregator#account-based-aggregation
+ :param conf: aws_config_configuration_aggregator configuration
+ :return:
+ """
+ self.evaluated_keys = ["account_aggregation_source", "organization_aggregation_source"]
+
+ if "account_aggregation_source" in conf:
+ aggregation_source = conf.get("account_aggregation_source", {})[0]
+ if isinstance(aggregation_source, dict) and aggregation_source.get("all_regions"):
+ return CheckResult.PASSED
+ if "organization_aggregation_source" in conf:
+ aggregation_source = conf.get("organization_aggregation_source", {})[0]
+ if isinstance(aggregation_source, dict) and aggregation_source.get("all_regions"):
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = ConfigConfigurationAggregator()
diff --git a/checkov/terraform/checks/resource/aws/DBInstanceBackupRetentionPeriod.py b/checkov/terraform/checks/resource/aws/DBInstanceBackupRetentionPeriod.py
new file mode 100644
index 0000000000..44b81a13bb
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/DBInstanceBackupRetentionPeriod.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_int
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class DBInstanceBackupRetentionPeriod(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that RDS instances has backup policy"
+ id = "CKV_AWS_133"
+ supported_resources = ['aws_rds_cluster']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ key = "backup_retention_period"
+ if key in conf.keys():
+ period = force_int(conf[key][0])
+ if period and 0 < period <= 35:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+
+
+check = DBInstanceBackupRetentionPeriod()
diff --git a/checkov/terraform/checks/resource/aws/DBInstanceLogging.py b/checkov/terraform/checks/resource/aws/DBInstanceLogging.py
new file mode 100644
index 0000000000..843c0b8e49
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/DBInstanceLogging.py
@@ -0,0 +1,22 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class DBInstanceLogging(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that respective logs of Amazon Relational Database Service (Amazon RDS) are enabled"
+ id = "CKV_AWS_129"
+ supported_resources = ["aws_db_instance"]
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ # which log types are chooseable depends on the engine and engine version and even region,
+ # therefore it is only checked, if something is enabled.
+ if conf.get("enabled_cloudwatch_logs_exports", [[]])[0]:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = DBInstanceLogging()
diff --git a/checkov/terraform/checks/resource/aws/DocDBAuditLogs.py b/checkov/terraform/checks/resource/aws/DocDBAuditLogs.py
new file mode 100644
index 0000000000..35f0b425e8
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/DocDBAuditLogs.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class DocDBAuditLogs(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure DocDB has audit logs enabled"
+ id = "CKV_AWS_104"
+ supported_resources = ['aws_docdb_cluster_parameter_group']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'parameter' in conf:
+ for elem in conf["parameter"]:
+ if isinstance(elem, dict) and elem["name"][0] == "audit_logs" and elem["value"][0] == "enabled":
+ self.evaluated_keys = [f'parameter/[{conf["parameter"].index(elem)}]/name', f'parameter/[{conf["parameter"].index(elem)}]/value']
+ return CheckResult.PASSED
+
+ #no matching params
+ return CheckResult.FAILED
+
+ else:
+ # no params at all
+ return CheckResult.FAILED
+
+
+check = DocDBAuditLogs()
diff --git a/checkov/terraform/checks/resource/aws/DynamoDBGlobalTableRecovery.py b/checkov/terraform/checks/resource/aws/DynamoDBGlobalTableRecovery.py
new file mode 100644
index 0000000000..936c393063
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/DynamoDBGlobalTableRecovery.py
@@ -0,0 +1,21 @@
+from typing import Dict, List, Any
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class DynamodbGlobalTableRecovery(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Dynamodb point in time recovery (backup) is enabled for global tables"
+ id = "CKV_AWS_165"
+ supported_resources = ['aws_dynamodb_global_table']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ # This field cannot be set in terraform's aws_dyanmodb_global_table
+ # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_global_table
+ return CheckResult.PASSED
+
+
+check = DynamodbGlobalTableRecovery()
diff --git a/checkov/terraform/checks/resource/aws/DynamoDBTablesEncrypted.py b/checkov/terraform/checks/resource/aws/DynamoDBTablesEncrypted.py
new file mode 100644
index 0000000000..2715bb7c9e
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/DynamoDBTablesEncrypted.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class DynamoDBTablesEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure DynamoDB Tables are encrypted using KMS"
+ id = "CKV_AWS_119"
+ supported_resources = ["aws_dynamodb_table"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "server_side_encryption/[0]/enabled"
+
+
+check = DynamoDBTablesEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/DynamodbRecovery.py b/checkov/terraform/checks/resource/aws/DynamodbRecovery.py
index 86fecdf9aa..169d694360 100644
--- a/checkov/terraform/checks/resource/aws/DynamodbRecovery.py
+++ b/checkov/terraform/checks/resource/aws/DynamodbRecovery.py
@@ -13,5 +13,8 @@ def __init__(self):
def get_inspected_key(self):
return "point_in_time_recovery/[0]/enabled"
+ def get_expected_values(self):
+ return [self.get_expected_value(), 'true'] # terraformer exports this as the string 'true'
+
check = DynamodbRecovery()
diff --git a/checkov/terraform/checks/resource/aws/EBSDefaultEncryption.py b/checkov/terraform/checks/resource/aws/EBSDefaultEncryption.py
new file mode 100644
index 0000000000..470cd9db0a
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/EBSDefaultEncryption.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class EBSDefaultEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure EBS default encryption is enabled"
+ id = "CKV_AWS_106"
+ supported_resources = ["aws_ebs_encryption_by_default"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ enabled = conf.get("enabled")
+ if enabled and enabled == [False]:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.PASSED
+
+
+check = EBSDefaultEncryption()
diff --git a/checkov/terraform/checks/resource/aws/EC2Credentials.py b/checkov/terraform/checks/resource/aws/EC2Credentials.py
index 5b4570f086..c16014c597 100644
--- a/checkov/terraform/checks/resource/aws/EC2Credentials.py
+++ b/checkov/terraform/checks/resource/aws/EC2Credentials.py
@@ -6,7 +6,7 @@
class EC2Credentials(BaseResourceCheck):
def __init__(self):
- name = "Ensure no hard coded AWS access key and secret key exists in EC2 user data"
+ name = "Ensure no hard-coded secrets exist in EC2 user data"
id = "CKV_AWS_46"
supported_resources = ['aws_instance']
categories = [CheckCategories.SECRETS]
@@ -17,7 +17,7 @@ def scan_resource_conf(self, conf):
if 'user_data' in conf.keys():
user_data = conf['user_data'][0]
if isinstance(user_data, str):
- if string_has_secrets(user_data, AWS):
+ if string_has_secrets(user_data):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/EC2DetailedMonitoringEnabled.py b/checkov/terraform/checks/resource/aws/EC2DetailedMonitoringEnabled.py
new file mode 100644
index 0000000000..78da518a87
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/EC2DetailedMonitoringEnabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class EC2DetailedMonitoringEnabled(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that detailed monitoring is enabled for EC2 instances"
+ id = "CKV_AWS_126"
+ supported_resources = ['aws_instance']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'monitoring'
+
+ def get_expected_value(self):
+ return True
+
+
+check = EC2DetailedMonitoringEnabled()
diff --git a/checkov/terraform/checks/resource/aws/EC2EBSOptimized.py b/checkov/terraform/checks/resource/aws/EC2EBSOptimized.py
new file mode 100644
index 0000000000..abd6373286
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/EC2EBSOptimized.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class EC2EBSOptimized(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that EC2 is EBS optimized"
+ id = "CKV_AWS_135"
+ supported_resources = ['aws_instance']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "ebs_optimized"
+
+
+check = EC2EBSOptimized()
diff --git a/checkov/terraform/checks/resource/aws/ECRImageScanning.py b/checkov/terraform/checks/resource/aws/ECRImageScanning.py
index c5a89ea82a..b060772ef5 100644
--- a/checkov/terraform/checks/resource/aws/ECRImageScanning.py
+++ b/checkov/terraform/checks/resource/aws/ECRImageScanning.py
@@ -5,7 +5,7 @@
class ECRImageScanning(BaseResourceValueCheck):
def __init__(self):
name = "Ensure ECR image scanning on push is enabled"
- id = "CKV_AWS_33"
+ id = "CKV_AWS_163"
supported_resources = ['aws_ecr_repository']
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
diff --git a/checkov/terraform/checks/resource/aws/ECRPolicy.py b/checkov/terraform/checks/resource/aws/ECRPolicy.py
index 66c37ea3a3..0b7a9552b4 100644
--- a/checkov/terraform/checks/resource/aws/ECRPolicy.py
+++ b/checkov/terraform/checks/resource/aws/ECRPolicy.py
@@ -1,6 +1,7 @@
import json
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import is_json
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
@@ -26,12 +27,4 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
-def is_json(myjson):
- try:
- json_object = json.loads(myjson)
- except ValueError as e:
- return False
- return True
-
-
check = ECRPolicy()
diff --git a/checkov/terraform/checks/resource/aws/ECRRepositoryEncrypted.py b/checkov/terraform/checks/resource/aws/ECRRepositoryEncrypted.py
new file mode 100644
index 0000000000..a08d1cae88
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ECRRepositoryEncrypted.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ECRRepositoryEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that ECR repositories are encrypted using KMS"
+ id = "CKV_AWS_136"
+ supported_resources = ['aws_ecr_repository']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'encryption_configuration/[0]/encryption_type'
+
+ def get_expected_value(self):
+ return "KMS"
+
+
+check = ECRRepositoryEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py b/checkov/terraform/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py
index 2ed6cacdc6..99363df314 100644
--- a/checkov/terraform/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py
+++ b/checkov/terraform/checks/resource/aws/ECSTaskDefinitionEFSVolumeEncryption.py
@@ -4,7 +4,7 @@
class ECSTaskDefinitionEFSVolumeEncryption(BaseResourceCheck):
def __init__(self):
- name = "Ensure Encryption in transit is enabled for ECS Task defintion EFS volumes"
+ name = "Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions"
id = "CKV_AWS_97"
supported_resources = ['aws_ecs_task_definition']
categories = [CheckCategories.ENCRYPTION]
diff --git a/checkov/terraform/checks/resource/aws/EKSNodeGroupRemoteAccess.py b/checkov/terraform/checks/resource/aws/EKSNodeGroupRemoteAccess.py
index 4921370f4a..43af550c2c 100644
--- a/checkov/terraform/checks/resource/aws/EKSNodeGroupRemoteAccess.py
+++ b/checkov/terraform/checks/resource/aws/EKSNodeGroupRemoteAccess.py
@@ -12,8 +12,11 @@ def __init__(self):
def scan_resource_conf(self, conf):
if "remote_access" in conf.keys():
- if "ec2_ssh_key" in conf["remote_access"][0].keys() and not 'source_security_group_ids' in conf["remote_access"][0].keys():
- return CheckResult.FAILED
+ try:
+ if "ec2_ssh_key" in conf["remote_access"][0].keys() and not 'source_security_group_ids' in conf["remote_access"][0].keys():
+ return CheckResult.FAILED
+ except:
+ return CheckResult.PASSED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/EKSPublicAccessCIDR.py b/checkov/terraform/checks/resource/aws/EKSPublicAccessCIDR.py
index de6d420f6d..2c41152c19 100644
--- a/checkov/terraform/checks/resource/aws/EKSPublicAccessCIDR.py
+++ b/checkov/terraform/checks/resource/aws/EKSPublicAccessCIDR.py
@@ -19,12 +19,13 @@ def scan_resource_conf(self, conf):
"""
self.evaluated_keys = 'vpc_config'
if "vpc_config" in conf.keys():
- if "endpoint_public_access" in conf["vpc_config"][0].keys() and not conf["vpc_config"][0]["endpoint_public_access"][0]:
+ if "endpoint_public_access" in conf["vpc_config"][0] and not conf["vpc_config"][0]["endpoint_public_access"][0]:
self.evaluated_keys = 'vpc_config/[0]/endpoint_public_access'
return CheckResult.PASSED
- elif "public_access_cidrs" in conf["vpc_config"][0].keys():
+ elif "public_access_cidrs" in conf["vpc_config"][0]:
self.evaluated_keys = 'vpc_config/[0]/public_access_cidrs'
- if not len(conf["vpc_config"][0]["public_access_cidrs"][0]) or "0.0.0.0/0" in conf["vpc_config"][0]["public_access_cidrs"][0]: # nosec
+ cidrs = conf["vpc_config"][0]["public_access_cidrs"]
+ if not cidrs or not len(cidrs[0]) or "0.0.0.0/0" in cidrs[0]:
return CheckResult.FAILED
else:
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/ELBCrossZoneEnable.py b/checkov/terraform/checks/resource/aws/ELBCrossZoneEnable.py
new file mode 100644
index 0000000000..8bac89422d
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ELBCrossZoneEnable.py
@@ -0,0 +1,19 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class ELBCrossZoneEnable(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that ELB is cross-zone-load-balancing enabled"
+ id = "CKV_AWS_138"
+ supported_resources = ['aws_elb']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'cross_zone_load_balancing'
+
+
+check = ELBCrossZoneEnable()
diff --git a/checkov/terraform/checks/resource/aws/ELBUsesSSL.py b/checkov/terraform/checks/resource/aws/ELBUsesSSL.py
new file mode 100644
index 0000000000..a9024127ce
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ELBUsesSSL.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class ELBUsesSSL(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Elastic Load Balancer(s) uses SSL certificates provided by AWS Certificate Manager"
+ id = "CKV_AWS_127"
+ supported_resources = ['aws_elb']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'listener' in conf:
+ for listener in conf['listener']:
+ if 'ssl_certificate_id' not in listener:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = ELBUsesSSL()
diff --git a/checkov/terraform/checks/resource/aws/ELBv2AccessLogs.py b/checkov/terraform/checks/resource/aws/ELBv2AccessLogs.py
index 91c6bf0834..6dc0ba19f1 100644
--- a/checkov/terraform/checks/resource/aws/ELBv2AccessLogs.py
+++ b/checkov/terraform/checks/resource/aws/ELBv2AccessLogs.py
@@ -6,12 +6,17 @@ class ELBv2AccessLogs(BaseResourceValueCheck):
def __init__(self):
name = "Ensure the ELBv2 (Application/Network) has access logging enabled"
id = "CKV_AWS_91"
- supported_resources = ['aws_lb', 'aws_alb']
+ supported_resources = ["aws_lb", "aws_alb"]
categories = [CheckCategories.LOGGING]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def get_inspected_key(self):
- return 'access_logs/0/enabled/0'
+ return "access_logs/0/enabled/0"
+
+ def scan_resource_conf(self, conf):
+ if conf.get("load_balancer_type") == ["gateway"]:
+ return CheckResult.UNKNOWN
+ return super().scan_resource_conf(conf)
check = ELBv2AccessLogs()
diff --git a/checkov/terraform/checks/resource/aws/EMRClusterIsEncryptedKMS.py b/checkov/terraform/checks/resource/aws/EMRClusterIsEncryptedKMS.py
new file mode 100644
index 0000000000..6efe2702be
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/EMRClusterIsEncryptedKMS.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class EMRClusterIsEncryptedKMS(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Cluster security configuration encryption is using SSE-KMS"
+ id = "CKV_AWS_171"
+ supported_resources = ['aws_emr_security_configuration']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'configuration' in conf:
+ configuration = conf['configuration'][0]
+ if "SSE-KMS" in configuration:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.SKIPPED
+
+
+check = EMRClusterIsEncryptedKMS()
diff --git a/checkov/terraform/checks/resource/aws/EMRClusterKerberosAttributes.py b/checkov/terraform/checks/resource/aws/EMRClusterKerberosAttributes.py
new file mode 100644
index 0000000000..69d17fda00
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/EMRClusterKerberosAttributes.py
@@ -0,0 +1,25 @@
+from typing import Dict, List, Any
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class EMRClusterKerberosAttributes(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure that EMR clusters with Kerberos have Kerberos Realm set"
+ id = "CKV_AWS_114"
+ supported_resources = ["aws_emr_cluster"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ if "kerberos_attributes" in conf:
+ kerberos_attributes = conf["kerberos_attributes"][0]
+ if kerberos_attributes and "realm" in kerberos_attributes:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.UNKNOWN
+
+
+check = EMRClusterKerberosAttributes()
diff --git a/checkov/terraform/checks/resource/aws/ElasticCacheAutomaticBackup.py b/checkov/terraform/checks/resource/aws/ElasticCacheAutomaticBackup.py
new file mode 100644
index 0000000000..6639b8313c
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ElasticCacheAutomaticBackup.py
@@ -0,0 +1,32 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+
+
+class ElasticCacheAutomaticBackup(BaseResourceNegativeValueCheck):
+ def __init__(self):
+ name = "Ensure that Amazon ElastiCache Redis clusters have automatic backup turned on"
+ id = "CKV_AWS_134"
+ supported_resources = ["aws_elasticache_cluster"]
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(
+ name=name,
+ id=id,
+ categories=categories,
+ supported_resources=supported_resources,
+ missing_attribute_result=CheckResult.FAILED,
+ )
+
+ def scan_resource_conf(self, conf):
+ if conf.get("engine") == ["memcached"]:
+ return CheckResult.UNKNOWN
+
+ return super().scan_resource_conf(conf)
+
+ def get_inspected_key(self):
+ return "snapshot_retention_limit"
+
+ def get_forbidden_values(self):
+ return [0]
+
+
+check = ElasticCacheAutomaticBackup()
diff --git a/checkov/terraform/checks/resource/aws/ElasticsearchInVPC.py b/checkov/terraform/checks/resource/aws/ElasticsearchInVPC.py
new file mode 100644
index 0000000000..5e8951758f
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/ElasticsearchInVPC.py
@@ -0,0 +1,22 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ElasticsearchInVPC(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that Elasticsearch is configured inside a VPC"
+ id = "CKV_AWS_137"
+ supported_resources = ['aws_elasticsearch_domain']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "vpc_options"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = ElasticsearchInVPC()
diff --git a/checkov/terraform/checks/resource/aws/ElasticsearchNodeToNodeEncryption.py b/checkov/terraform/checks/resource/aws/ElasticsearchNodeToNodeEncryption.py
index ae3996692e..b4ec0d8f94 100644
--- a/checkov/terraform/checks/resource/aws/ElasticsearchNodeToNodeEncryption.py
+++ b/checkov/terraform/checks/resource/aws/ElasticsearchNodeToNodeEncryption.py
@@ -1,5 +1,6 @@
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.type_forcers import force_int
class ElasticsearchNodeToNodeEncryption(BaseResourceCheck):
@@ -24,12 +25,23 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
self.evaluated_keys = ['cluster_config/[0]/instance_count']
instance_count = cluster_config["instance_count"]
- if isinstance(instance_count, int):
+ if isinstance(instance_count, list):
+ instance_count = instance_count[0]
+ if not isinstance(instance_count, int):
+ return CheckResult.UNKNOWN
+ if instance_count:
if instance_count > 1:
self.evaluated_keys.append('node_to_node_encryption/[0]/enabled')
if "node_to_node_encryption" in conf.keys() and "enabled" in conf["node_to_node_encryption"][0]:
- if conf["node_to_node_encryption"][0]["enabled"]:
+ n2n_enc_enabled = conf["node_to_node_encryption"][0]["enabled"]
+ if isinstance(n2n_enc_enabled, list):
+ n2n_enc_enabled = conf["node_to_node_encryption"][0]["enabled"][0]
+ if not isinstance(n2n_enc_enabled, bool):
+ return CheckResult.UNKNOWN
+ if n2n_enc_enabled:
return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
return CheckResult.FAILED
return CheckResult.PASSED
return CheckResult.UNKNOWN
diff --git a/checkov/terraform/checks/resource/aws/GlacierVaultAnyPrincipal.py b/checkov/terraform/checks/resource/aws/GlacierVaultAnyPrincipal.py
new file mode 100644
index 0000000000..b7762bbfab
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/GlacierVaultAnyPrincipal.py
@@ -0,0 +1,30 @@
+import re
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from policyuniverse.policy import Policy
+
+DATA_TO_JSON_PATTERN = r"\$?\{?(.+?)(?=.json).json\}?"
+
+class GlacierVaultAnyPrincipal(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure Glacier Vault access policy is not public by only allowing specific services or principals to access it"
+ id = "CKV_AWS_167"
+ supported_resources = ['aws_glacier_vault']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'access_policy' in conf:
+ policy_obj = conf['access_policy'][0]
+ if isinstance(policy_obj, str):
+ if re.match(DATA_TO_JSON_PATTERN, policy_obj):
+ return CheckResult.UNKNOWN
+ policy = Policy(policy_obj)
+ if policy.is_internet_accessible():
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = GlacierVaultAnyPrincipal()
diff --git a/checkov/terraform/checks/resource/aws/IAMAdminPolicyDocument.py b/checkov/terraform/checks/resource/aws/IAMAdminPolicyDocument.py
index 0937a6e6c5..66b1dc1232 100644
--- a/checkov/terraform/checks/resource/aws/IAMAdminPolicyDocument.py
+++ b/checkov/terraform/checks/resource/aws/IAMAdminPolicyDocument.py
@@ -1,7 +1,6 @@
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_list, extract_policy_dict
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.util.type_forcers import force_list
-import json
class IAMAdminPolicyDocument(BaseResourceCheck):
@@ -16,14 +15,15 @@ def __init__(self):
def scan_resource_conf(self, conf):
if 'policy' in conf.keys():
try:
- policy_block = json.loads(conf['policy'][0])
- if 'Statement' in policy_block.keys():
+ policy_block = extract_policy_dict(conf['policy'][0])
+ if policy_block and 'Statement' in policy_block.keys():
for statement in force_list(policy_block['Statement']):
- if 'Action' in statement and \
- statement.get('Effect', ['Allow']) == 'Allow' and \
- '*' in statement.get('Action', ['']) and \
- '*' in statement.get('Resource', ['']):
- return CheckResult.FAILED
+ if 'Action' in statement:
+ effect = statement.get('Effect', 'Allow')
+ action = force_list(statement.get('Action', ['']))
+ resource = force_list(statement.get('Resource', ['']))
+ if effect == 'Allow' and '*' in action and '*' in resource:
+ return CheckResult.FAILED
except: # nosec
pass
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py b/checkov/terraform/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
index c19fcff479..0b51d2cb10 100644
--- a/checkov/terraform/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
+++ b/checkov/terraform/checks/resource/aws/IAMRoleAllowAssumeFromAccount.py
@@ -1,7 +1,8 @@
+import re
+
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import extract_policy_dict
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-import json
-import re
class IAMRoleAllowAssumeFromAccount(BaseResourceCheck):
@@ -16,14 +17,14 @@ def __init__(self):
def scan_resource_conf(self, conf):
if conf.get('assume_role_policy') and isinstance(conf['assume_role_policy'][0], str):
try:
- assume_role_block = json.loads(conf['assume_role_policy'][0])
- if 'Statement' in assume_role_block.keys():
+ assume_role_block = extract_policy_dict(conf['assume_role_policy'][0])
+ if assume_role_block and 'Statement' in assume_role_block.keys():
if 'Principal' in assume_role_block['Statement'][0]:
if 'AWS' in assume_role_block['Statement'][0]['Principal']:
account_access = re.compile(r'\d{12}|arn:aws:iam::\d{12}:root')
if re.match(account_access, assume_role_block['Statement'][0]['Principal']['AWS']):
return CheckResult.FAILED
- except: # nosec
+ except: # nosec
pass
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/IAMRoleAllowsPublicAssume.py b/checkov/terraform/checks/resource/aws/IAMRoleAllowsPublicAssume.py
index 9a3937ba97..442dc5cf20 100644
--- a/checkov/terraform/checks/resource/aws/IAMRoleAllowsPublicAssume.py
+++ b/checkov/terraform/checks/resource/aws/IAMRoleAllowsPublicAssume.py
@@ -1,6 +1,6 @@
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import extract_policy_dict
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-import json
class IAMRoleAllowsPublicAssume(BaseResourceCheck):
@@ -13,10 +13,10 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
- if conf.get('assume_role_policy') and isinstance(conf['assume_role_policy'][0], str):
+ if conf.get('assume_role_policy'):
try:
- assume_role_block = json.loads(conf['assume_role_policy'][0])
- if 'Statement' in assume_role_block.keys():
+ assume_role_block = extract_policy_dict(conf['assume_role_policy'][0])
+ if assume_role_block and 'Statement' in assume_role_block.keys():
for statement in assume_role_block['Statement']:
if 'Effect' in statement and statement['Effect'] == 'Deny':
continue
@@ -25,7 +25,7 @@ def scan_resource_conf(self, conf):
aws = statement['Principal']['AWS']
if (type(aws) == str and aws == '*') or (type(aws) == list and '*' in aws):
return CheckResult.FAILED
- except: # nosec
+ except: # nosec
pass
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/IAMStarActionPolicyDocument.py b/checkov/terraform/checks/resource/aws/IAMStarActionPolicyDocument.py
index 8c0e7bdd6d..31e195184c 100644
--- a/checkov/terraform/checks/resource/aws/IAMStarActionPolicyDocument.py
+++ b/checkov/terraform/checks/resource/aws/IAMStarActionPolicyDocument.py
@@ -1,7 +1,6 @@
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_list, extract_policy_dict
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.util.type_forcers import force_list
-import json
class IAMStarActionPolicyDocument(BaseResourceCheck):
@@ -16,14 +15,14 @@ def __init__(self):
def scan_resource_conf(self, conf):
if 'policy' in conf.keys():
try:
- policy_block = json.loads(conf['policy'][0])
- if 'Statement' in policy_block.keys():
+ policy_block = extract_policy_dict(conf['policy'][0])
+ if policy_block and 'Statement' in policy_block.keys():
for statement in force_list(policy_block['Statement']):
if 'Action' in statement and \
statement.get('Effect', ['Allow']) == 'Allow' and \
'*' in force_list(statement['Action']):
return CheckResult.FAILED
- except: # nosec
+ except: # nosec
pass
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/KMSKeyWildcardPrincipal.py b/checkov/terraform/checks/resource/aws/KMSKeyWildcardPrincipal.py
new file mode 100644
index 0000000000..b0bc8752c1
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/KMSKeyWildcardPrincipal.py
@@ -0,0 +1,37 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.type_forcers import force_list
+import json
+
+
+class KMSKeyWildcardPrincipal(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure KMS key policy does not contain wildcard (*) principal"
+ id = "CKV_AWS_33"
+ supported_resources = ['aws_kms_key']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'policy' in conf:
+ try:
+ policy_block = conf['policy'][0]
+ if 'Statement' in policy_block:
+ for statement in force_list(policy_block['Statement']):
+ if 'Principal' in statement:
+ principal = statement['Principal']
+ if 'Effect' in statement and statement['Effect'] == 'Deny':
+ continue
+ if 'AWS' in principal:
+ aws = principal['AWS']
+ if (type(aws) == str and aws == '*') or (type(aws) == list and '*' in aws):
+ return CheckResult.FAILED
+ if (type(principal) == str and principal == '*') or (type(principal) == list and '*' in principal):
+ return CheckResult.FAILED
+ except: # nosec
+ pass
+ return CheckResult.PASSED
+
+
+check = KMSKeyWildcardPrincipal()
diff --git a/checkov/terraform/checks/resource/aws/KMSRotation.py b/checkov/terraform/checks/resource/aws/KMSRotation.py
index 5bb148635d..d74bcb5206 100644
--- a/checkov/terraform/checks/resource/aws/KMSRotation.py
+++ b/checkov/terraform/checks/resource/aws/KMSRotation.py
@@ -1,5 +1,5 @@
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
-from checkov.common.models.enums import CheckCategories
+from checkov.common.models.enums import CheckCategories, CheckResult
class KMSRotation(BaseResourceValueCheck):
@@ -13,5 +13,13 @@ def __init__(self):
def get_inspected_key(self):
return "enable_key_rotation"
+ def scan_resource_conf(self, conf):
+ # Only symmetric keys support auto rotation. The attribute is optional and defaults to symmetric.
+ spec = conf.get('customer_master_key_spec')
+ if not spec or 'SYMMETRIC_DEFAULT' in spec:
+ return super().scan_resource_conf(conf)
+ else:
+ return CheckResult.PASSED
+
check = KMSRotation()
diff --git a/checkov/terraform/checks/resource/aws/KubernetesSecretsEncryptedUsingCMK.py b/checkov/terraform/checks/resource/aws/KubernetesSecretsEncryptedUsingCMK.py
new file mode 100644
index 0000000000..af45746d0f
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/KubernetesSecretsEncryptedUsingCMK.py
@@ -0,0 +1,29 @@
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class KubernetesSecretsEncryptedUsingCMK(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Kubernetes Secrets are encrypted using Customer Master Keys (CMKs) managed in AWS KMS"
+ id = "CKV_AWS_151"
+ supported_resources = ["aws_eks_cluster"]
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if "encryption_config" not in conf.keys():
+ return CheckResult.FAILED
+ else:
+ encryption_config = conf.get("encryption_config")[0]
+ if "resources" in encryption_config and "provider" in encryption_config:
+ resources = encryption_config.get("resources")[0]
+ provider = encryption_config.get("provider")[0]
+ if isinstance(resources, list):
+ if "secrets" in resources and "key_arn" in provider:
+ return CheckResult.PASSED
+ return CheckResult.UNKNOWN
+
+ return CheckResult.FAILED
+
+
+check = KubernetesSecretsEncryptedUsingCMK()
diff --git a/checkov/terraform/checks/resource/aws/LBCrossZone.py b/checkov/terraform/checks/resource/aws/LBCrossZone.py
new file mode 100644
index 0000000000..f6b4763f24
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/LBCrossZone.py
@@ -0,0 +1,24 @@
+from typing import Dict, List
+
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class LBCrossZone(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure that Load Balancer (Network/Gateway) has cross-zone load balancing enabled"
+ id = "CKV_AWS_152"
+ supported_resources = ["aws_lb", "aws_alb"]
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List]) -> CheckResult:
+ if conf.get("load_balancer_type", ["application"]) == ["application"]:
+ return CheckResult.UNKNOWN
+ return super().scan_resource_conf(conf)
+
+ def get_inspected_key(self) -> str:
+ return "enable_cross_zone_load_balancing"
+
+
+check = LBCrossZone()
diff --git a/checkov/terraform/checks/resource/aws/LBDeletionProtection.py b/checkov/terraform/checks/resource/aws/LBDeletionProtection.py
new file mode 100644
index 0000000000..88bab79a4e
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/LBDeletionProtection.py
@@ -0,0 +1,17 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class LBDeletionProtection(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Load Balancer has deletion protection enabled"
+ id = "CKV_AWS_150"
+ supported_resources = ["aws_lb", "aws_alb"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "enable_deletion_protection"
+
+
+check = LBDeletionProtection()
diff --git a/checkov/terraform/checks/resource/aws/LambdaDLQConfigured.py b/checkov/terraform/checks/resource/aws/LambdaDLQConfigured.py
new file mode 100644
index 0000000000..952f8017af
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/LambdaDLQConfigured.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class LambdaDLQConfigured(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ)"
+ id = "CKV_AWS_116"
+ supported_resources = ['aws_lambda_function']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "dead_letter_config/[0]/target_arn"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = LambdaDLQConfigured()
diff --git a/checkov/terraform/checks/resource/aws/LambdaEnvironmentCredentials.py b/checkov/terraform/checks/resource/aws/LambdaEnvironmentCredentials.py
index 72cd3b6d86..33b7146013 100644
--- a/checkov/terraform/checks/resource/aws/LambdaEnvironmentCredentials.py
+++ b/checkov/terraform/checks/resource/aws/LambdaEnvironmentCredentials.py
@@ -1,13 +1,13 @@
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.util.secrets import string_has_secrets, AWS
+from checkov.common.util.secrets import string_has_secrets, AWS, GENERAL
from checkov.common.util.type_forcers import force_list
class LambdaEnvironmentCredentials(BaseResourceCheck):
def __init__(self):
- name = "Ensure no hard coded AWS access key and secret key exists in lambda environment"
+ name = "Ensure no hard-coded secrets exist in lambda environment"
id = "CKV_AWS_45"
supported_resources = ['aws_lambda_function']
categories = [CheckCategories.SECRETS]
@@ -15,14 +15,14 @@ def __init__(self):
def scan_resource_conf(self, conf):
self.evaluated_keys = 'environment/[0]/variables'
- if 'environment' in conf.keys():
+ if len(conf.get('environment', [])) > 0:
if isinstance(conf['environment'][0], dict):
if 'variables' in conf['environment'][0]:
if isinstance(force_list(conf['environment'][0]['variables'])[0], dict):
# variables can be a string, which in this case it points to a variable
for values in list(force_list(conf['environment'][0]['variables'])[0].values()):
for value in list(filter(lambda value: isinstance(value, str), force_list(values))):
- if string_has_secrets(value, AWS):
+ if string_has_secrets(value,AWS,GENERAL):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/LambdaFunctionLevelConcurrentExecutionLimit.py b/checkov/terraform/checks/resource/aws/LambdaFunctionLevelConcurrentExecutionLimit.py
new file mode 100644
index 0000000000..8dd65da4a0
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/LambdaFunctionLevelConcurrentExecutionLimit.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class LambdaFunctionLevelConcurrentExecutionLimit(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that AWS Lambda function is configured for function-level concurrent execution limit"
+ id = "CKV_AWS_115"
+ supported_resources = ['aws_lambda_function']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ key = 'reserved_concurrent_executions'
+ if key not in conf.keys() or conf.get(key)[0] == '${-1}':
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = LambdaFunctionLevelConcurrentExecutionLimit()
diff --git a/checkov/terraform/checks/resource/aws/LambdaInVPC.py b/checkov/terraform/checks/resource/aws/LambdaInVPC.py
new file mode 100644
index 0000000000..409c4e4935
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/LambdaInVPC.py
@@ -0,0 +1,22 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class LambdaInVPC(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that AWS Lambda function is configured inside a VPC"
+ id = "CKV_AWS_117"
+ supported_resources = ['aws_lambda_function']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "vpc_config"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = LambdaInVPC()
diff --git a/checkov/terraform/checks/resource/aws/NeptuneClusterInstancePublic.py b/checkov/terraform/checks/resource/aws/NeptuneClusterInstancePublic.py
new file mode 100644
index 0000000000..a8dc557660
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/NeptuneClusterInstancePublic.py
@@ -0,0 +1,20 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class NeptuneClusterInstancePublic(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Neptune Cluster instance is not publicly available"
+ id = "CKV_AWS_102"
+ supported_resources = ['aws_neptune_cluster_instance']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'publicly_accessible' in conf.keys():
+ if conf['publicly_accessible'] == [True]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = NeptuneClusterInstancePublic()
diff --git a/checkov/terraform/checks/resource/aws/NeptuneClusterLogging.py b/checkov/terraform/checks/resource/aws/NeptuneClusterLogging.py
new file mode 100644
index 0000000000..7103c58f6b
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/NeptuneClusterLogging.py
@@ -0,0 +1,20 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class NeptuneClusterLogging(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Neptune logging is enabled"
+ id = "CKV_AWS_101"
+ supported_resources = ['aws_neptune_cluster']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ log_types = ["audit"]
+ if 'enable_cloudwatch_logs_exports' in conf:
+ if conf['enable_cloudwatch_logs_exports'][0] and all(elem in conf['enable_cloudwatch_logs_exports'][0] for elem in log_types):
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+check = NeptuneClusterLogging()
diff --git a/checkov/terraform/checks/resource/aws/QLDBLedgerDeletionProtection.py b/checkov/terraform/checks/resource/aws/QLDBLedgerDeletionProtection.py
new file mode 100644
index 0000000000..fa5160a4b0
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/QLDBLedgerDeletionProtection.py
@@ -0,0 +1,25 @@
+from typing import Dict, List, Any
+
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class QLDBLedgerDeletionProtection(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure QLDB ledger has deletion protection enabled"
+ id = "CKV_AWS_172"
+ supported_resources = ["aws_qldb_ledger"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ # deletion protection is enabled on default
+ if "deletion_protection" not in conf:
+ return CheckResult.PASSED
+ return super().scan_resource_conf(conf)
+
+ def get_inspected_key(self) -> str:
+ return "deletion_protection"
+
+
+check = QLDBLedgerDeletionProtection()
diff --git a/checkov/terraform/checks/resource/aws/QLDBLedgerPermissionsMode.py b/checkov/terraform/checks/resource/aws/QLDBLedgerPermissionsMode.py
new file mode 100644
index 0000000000..0c91c95c7c
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/QLDBLedgerPermissionsMode.py
@@ -0,0 +1,20 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class QLDBLedgerPermissionsMode(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure QLDB ledger permissions mode is set to STANDARD"
+ id = "CKV_AWS_170"
+ supported_resources = ["aws_qldb_ledger"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "permissions_mode"
+
+ def get_expected_value(self) -> str:
+ return "STANDARD"
+
+
+check = QLDBLedgerPermissionsMode()
diff --git a/checkov/terraform/checks/resource/aws/RDSClusterEncrypted.py b/checkov/terraform/checks/resource/aws/RDSClusterEncrypted.py
new file mode 100644
index 0000000000..665555d841
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSClusterEncrypted.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class RDSClusterEncrypted(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that RDS global clusters are encrypted"
+ id = "CKV_AWS_140"
+ supported_resources = ['aws_rds_global_cluster']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Looks for storage_encrypted at aws_rds_global_cluster:
+ https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_global_cluster
+ :param conf: aws_rds_global_cluster configuration
+ :return:
+ """
+ self.evaluated_keys = 'aws_rds_global_cluster'
+ if "source_db_cluster_identifier" in conf.keys():
+ return CheckResult.UNKNOWN
+ if "storage_encrypted" in conf.keys():
+ if conf["storage_encrypted"][0]:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+ return CheckResult.FAILED
+
+
+check = RDSClusterEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/RDSClusterIAMAuthentication.py b/checkov/terraform/checks/resource/aws/RDSClusterIAMAuthentication.py
new file mode 100644
index 0000000000..7d61094686
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSClusterIAMAuthentication.py
@@ -0,0 +1,17 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class RDSClusterIAMAuthentication(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure RDS cluster has IAM authentication enabled"
+ id = "CKV_AWS_162"
+ supported_resources = ["aws_rds_cluster"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "iam_database_authentication_enabled"
+
+
+check = RDSClusterIAMAuthentication()
diff --git a/checkov/terraform/checks/resource/aws/RDSClusterSnapshotEncrypted.py b/checkov/terraform/checks/resource/aws/RDSClusterSnapshotEncrypted.py
new file mode 100644
index 0000000000..099592a5bd
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSClusterSnapshotEncrypted.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RDSClusterSnapshotEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that RDS database cluster snapshot is encrypted"
+ id = "CKV_AWS_146"
+ supported_resources = ['aws_db_cluster_snapshot']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'storage_encrypted'
+
+
+check = RDSClusterSnapshotEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/RDSDeletionProtection.py b/checkov/terraform/checks/resource/aws/RDSDeletionProtection.py
new file mode 100644
index 0000000000..bc8e024cbd
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSDeletionProtection.py
@@ -0,0 +1,18 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class RDSDeletionProtection(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that RDS clusters have deletion protection enabled"
+ id = "CKV_AWS_139"
+ supported_resources = ['aws_rds_cluster']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'deletion_protection'
+
+
+check = RDSDeletionProtection()
diff --git a/checkov/terraform/checks/resource/aws/RDSEnableIAMAuthentication.py b/checkov/terraform/checks/resource/aws/RDSEnableIAMAuthentication.py
new file mode 100644
index 0000000000..7c334a1dd6
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSEnableIAMAuthentication.py
@@ -0,0 +1,18 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class RDSEnableIAMAuthentication(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that an Amazon RDS Clusters have AWS Identity and Access Management (IAM) authentication enabled"
+ id = "CKV_AWS_128"
+ supported_resources = ['aws_rds_cluster']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'iam_database_authentication_enabled'
+
+
+check = RDSEnableIAMAuthentication()
diff --git a/checkov/terraform/checks/resource/aws/RDSEnhancedMonitorEnabled.py b/checkov/terraform/checks/resource/aws/RDSEnhancedMonitorEnabled.py
new file mode 100644
index 0000000000..444c1e5218
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSEnhancedMonitorEnabled.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RDSEnhancedMonitorEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that enhanced monitoring is enabled for Amazon RDS instances"
+ id = "CKV_AWS_118"
+ supported_resources = ['aws_db_instance', 'aws_rds_cluster_instance']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'monitoring_interval'
+
+ def get_expected_values(self):
+ return [1, 5, 10, 15, 30, 60]
+
+
+check = RDSEnhancedMonitorEnabled()
diff --git a/checkov/terraform/checks/resource/aws/RDSIAMAuthentication.py b/checkov/terraform/checks/resource/aws/RDSIAMAuthentication.py
new file mode 100644
index 0000000000..095b86900c
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RDSIAMAuthentication.py
@@ -0,0 +1,26 @@
+from typing import Dict, List, Any
+
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories, CheckResult
+
+
+class RDSIAMAuthentication(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure RDS database has IAM authentication enabled"
+ id = "CKV_AWS_161"
+ supported_resources = ["aws_db_instance"]
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "iam_database_authentication_enabled"
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ # IAM authentication is only supported for MySQL and PostgreSQL
+ if conf.get("engine") not in (["mysql"], ["postgres"]):
+ return CheckResult.UNKNOWN
+
+ return super().scan_resource_conf(conf)
+
+
+check = RDSIAMAuthentication()
diff --git a/checkov/terraform/checks/resource/aws/S3MFADelete.py b/checkov/terraform/checks/resource/aws/RDSMultiAZEnabled.py
similarity index 60%
rename from checkov/terraform/checks/resource/aws/S3MFADelete.py
rename to checkov/terraform/checks/resource/aws/RDSMultiAZEnabled.py
index 88ee426474..4c07ce619d 100644
--- a/checkov/terraform/checks/resource/aws/S3MFADelete.py
+++ b/checkov/terraform/checks/resource/aws/RDSMultiAZEnabled.py
@@ -2,17 +2,17 @@
from checkov.common.models.enums import CheckCategories
-class S3MFADelete(BaseResourceValueCheck):
+class RDSMultiAZEnabled(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure S3 bucket has MFA delete enabled"
- id = "CKV_AWS_52"
- supported_resources = ['aws_s3_bucket']
+ name = "Ensure that RDS instances have Multi-AZ enabled"
+ id = "CKV_AWS_157"
+ supported_resources = ['aws_db_instance']
categories = [CheckCategories.BACKUP_AND_RECOVERY]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def get_inspected_key(self):
- return "versioning/[0]/mfa_delete"
+ return 'multi_az'
-scanner = S3MFADelete()
+check = RDSMultiAZEnabled()
diff --git a/checkov/terraform/checks/resource/aws/RDSPubliclyAccessible.py b/checkov/terraform/checks/resource/aws/RDSPubliclyAccessible.py
index 3cfc7f744f..5d99810661 100644
--- a/checkov/terraform/checks/resource/aws/RDSPubliclyAccessible.py
+++ b/checkov/terraform/checks/resource/aws/RDSPubliclyAccessible.py
@@ -5,7 +5,7 @@
class RDSPubliclyAccessible(BaseResourceNegativeValueCheck):
def __init__(self):
- name = "Ensure all data stored in the RDS bucket is not public accessible"
+ name = "Ensure all data stored in RDS is not publicly accessible"
id = "CKV_AWS_17"
supported_resources = ['aws_db_instance', 'aws_rds_cluster_instance']
categories = [CheckCategories.NETWORKING]
diff --git a/checkov/terraform/checks/resource/aws/RedShiftSSL.py b/checkov/terraform/checks/resource/aws/RedShiftSSL.py
new file mode 100644
index 0000000000..12b4d08661
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RedShiftSSL.py
@@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class RedShiftSSL(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Redshift uses SSL"
+ id = "CKV_AWS_105"
+ supported_resources = ['aws_redshift_parameter_group']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(
+ name=name,
+ id=id,
+ categories=categories,
+ supported_resources=supported_resources,
+ )
+
+ def scan_resource_conf(self, conf):
+ if 'parameter' in conf:
+ for elem in conf["parameter"]:
+ if isinstance(elem, dict) and elem["name"][0] == "require_ssl" and elem["value"] == [True]:
+ self.evaluated_keys = [f'parameter/[{conf["parameter"].index(elem)}]/name', f'parameter/[{conf["parameter"].index(elem)}]/value']
+ return CheckResult.PASSED
+
+ #no matching params
+ return CheckResult.FAILED
+
+ else:
+ # no params at all
+ return CheckResult.FAILED
+
+
+check = RedShiftSSL()
diff --git a/checkov/terraform/checks/resource/aws/RedshiftClusterAllowVersionUpgrade.py b/checkov/terraform/checks/resource/aws/RedshiftClusterAllowVersionUpgrade.py
new file mode 100644
index 0000000000..0b1826df1e
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RedshiftClusterAllowVersionUpgrade.py
@@ -0,0 +1,19 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.enums import CheckResult
+
+
+class RedshiftClusterAllowVersionUpgrade(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensured that redshift cluster allowing version upgrade by default"
+ id = "CKV_AWS_141"
+ supported_resources = ['aws_redshift_cluster']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return "allow_version_upgrade"
+
+
+check = RedshiftClusterAllowVersionUpgrade()
diff --git a/checkov/terraform/checks/resource/aws/RedshiftClusterKMSKey.py b/checkov/terraform/checks/resource/aws/RedshiftClusterKMSKey.py
new file mode 100644
index 0000000000..5011c574fa
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RedshiftClusterKMSKey.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class RedshiftClusterKMSKey(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Redshift cluster is encrypted by KMS"
+ id = "CKV_AWS_142"
+ supported_resources = ['aws_redshift_cluster']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "kms_key_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = RedshiftClusterKMSKey()
diff --git a/checkov/terraform/checks/resource/aws/RedshiftInEc2ClassicMode.py b/checkov/terraform/checks/resource/aws/RedshiftInEc2ClassicMode.py
new file mode 100644
index 0000000000..331cde7648
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/RedshiftInEc2ClassicMode.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RedshiftInEc2ClassicMode(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Redshift is not deployed outside of a VPC"
+ id = "CKV_AWS_154"
+ supported_resources = ['aws_redshift_cluster']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "cluster_subnet_group_name"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = RedshiftInEc2ClassicMode()
diff --git a/checkov/terraform/checks/resource/aws/RedshitClusterPubliclyAvailable.py b/checkov/terraform/checks/resource/aws/RedshitClusterPubliclyAvailable.py
index dba11b27d9..7bed25dc13 100644
--- a/checkov/terraform/checks/resource/aws/RedshitClusterPubliclyAvailable.py
+++ b/checkov/terraform/checks/resource/aws/RedshitClusterPubliclyAvailable.py
@@ -1,9 +1,8 @@
-from checkov.common.models.consts import ANY_VALUE
from checkov.common.models.enums import CheckCategories
-from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
-class RedshiftClusterPubliclyAccessible(BaseResourceNegativeValueCheck):
+class RedshiftClusterPubliclyAccessible(BaseResourceValueCheck):
def __init__(self):
name = "Redshift cluster should not be publicly accessible"
id = "CKV_AWS_87"
@@ -14,8 +13,8 @@ def __init__(self):
def get_inspected_key(self):
return 'publicly_accessible'
- def get_forbidden_values(self):
- return [True]
+ def get_expected_value(self):
+ return False
check = RedshiftClusterPubliclyAccessible()
diff --git a/checkov/terraform/checks/resource/aws/S3BucketObjectLock.py b/checkov/terraform/checks/resource/aws/S3BucketObjectLock.py
new file mode 100644
index 0000000000..bc4b1137fd
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/S3BucketObjectLock.py
@@ -0,0 +1,26 @@
+from typing import Dict, List, Any
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class S3BucketObjectLock(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure that S3 bucket has lock configuration enabled by default"
+ id = "CKV_AWS_143"
+ supported_resources = ["aws_s3_bucket"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ lock_conf = conf.get("object_lock_configuration")
+ if lock_conf and lock_conf[0]:
+ lock_enabled = lock_conf[0].get("object_lock_enabled")
+ if lock_enabled in ["Enabled", ["Enabled"]]:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+ return CheckResult.UNKNOWN
+
+
+check = S3BucketObjectLock()
diff --git a/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py b/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py
new file mode 100644
index 0000000000..23fa22c497
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class S3BucketReplicationConfiguration(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that S3 bucket has cross-region replication enabled"
+ id = "CKV_AWS_144"
+ supported_resources = ['aws_s3_bucket']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "replication_configuration/[0]/role"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = S3BucketReplicationConfiguration()
diff --git a/checkov/terraform/checks/resource/aws/S3KMSEncryptedByDefault.py b/checkov/terraform/checks/resource/aws/S3KMSEncryptedByDefault.py
new file mode 100644
index 0000000000..b3cac2d311
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/S3KMSEncryptedByDefault.py
@@ -0,0 +1,21 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class S3KMSEncryptedByDefault(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that S3 buckets are encrypted with KMS by default"
+ id = "CKV_AWS_145"
+ supported_resources = ['aws_s3_bucket']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "server_side_encryption_configuration/[0]/rule/[0]/" \
+ "apply_server_side_encryption_by_default/[0]/sse_algorithm"
+
+ def get_expected_value(self):
+ return 'aws:kms'
+
+
+check = S3KMSEncryptedByDefault()
diff --git a/checkov/terraform/checks/resource/aws/SNSTopicPolicyAnyPrincipal.py b/checkov/terraform/checks/resource/aws/SNSTopicPolicyAnyPrincipal.py
new file mode 100644
index 0000000000..05d020e176
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SNSTopicPolicyAnyPrincipal.py
@@ -0,0 +1,32 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.type_forcers import force_list
+
+from policyuniverse.policy import Policy
+
+import json
+
+
+class SNSTopicPolicyAnyPrincipal(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure SNS topic policy is not public by only allowing specific services or principals to access it"
+ id = "CKV_AWS_169"
+ supported_resources = ['aws_sns_topic_policy']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ conf_policy = conf.get("policy")
+ if conf_policy:
+ if isinstance(conf_policy[0], dict):
+ policy = Policy(conf['policy'][0])
+ if policy.is_internet_accessible():
+ return CheckResult.FAILED
+ else:
+ return CheckResult.UNKNOWN
+ return CheckResult.PASSED
+
+check = SNSTopicPolicyAnyPrincipal()
+
+
diff --git a/checkov/terraform/checks/resource/aws/SQSPolicy.py b/checkov/terraform/checks/resource/aws/SQSPolicy.py
index de66cae6c7..3882f54a22 100644
--- a/checkov/terraform/checks/resource/aws/SQSPolicy.py
+++ b/checkov/terraform/checks/resource/aws/SQSPolicy.py
@@ -1,6 +1,7 @@
import json
from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import is_json
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
@@ -26,12 +27,4 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
-def is_json(myjson):
- try:
- json_object = json.loads(myjson)
- except ValueError as e:
- return False
- return True
-
-
check = SQSPolicy()
diff --git a/checkov/terraform/checks/resource/aws/SQSQueuePolicyAnyPrincipal.py b/checkov/terraform/checks/resource/aws/SQSQueuePolicyAnyPrincipal.py
new file mode 100644
index 0000000000..10b21e4aa1
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SQSQueuePolicyAnyPrincipal.py
@@ -0,0 +1,30 @@
+from policyuniverse.policy import Policy
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class SQSQueuePolicyAnyPrincipal(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure SQS queue policy is not public by only allowing specific services or principals to access it"
+ id = "CKV_AWS_168"
+ supported_resources = ['aws_sqs_queue_policy']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ conf_policy = conf.get("policy")
+ if conf_policy:
+ if isinstance(conf_policy[0], dict):
+ policy = Policy(conf_policy[0])
+ if policy.is_internet_accessible():
+ return CheckResult.FAILED
+ else:
+ return CheckResult.UNKNOWN
+
+ return CheckResult.PASSED
+
+check = SQSQueuePolicyAnyPrincipal()
+
+
diff --git a/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentEncryption.py b/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentEncryption.py
new file mode 100644
index 0000000000..c4a5229386
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentEncryption.py
@@ -0,0 +1,39 @@
+import json
+
+import yaml
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import is_json, is_yaml
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class SSMSessionManagerDocumentEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Session Manager data is encrypted in transit"
+ id = "CKV_AWS_112"
+ supported_resources = ["aws_ssm_document"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if conf.get("document_type") == ["Session"]:
+ doc_format = conf.get("document_format", ["JSON"])
+
+ if "content" in conf.keys():
+ content = conf["content"][0]
+ inputs = None
+
+ if doc_format == ["JSON"] and is_json(content):
+ inputs = json.loads(content).get("inputs", {})
+ elif doc_format == ["YAML"] and is_yaml(content):
+ inputs = yaml.safe_load(content).get("inputs", {})
+
+ if inputs and not inputs.get("kmsKeyId"):
+ return CheckResult.FAILED
+
+ return CheckResult.PASSED
+
+ return CheckResult.UNKNOWN
+
+
+check = SSMSessionManagerDocumentEncryption()
diff --git a/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentLogging.py b/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentLogging.py
new file mode 100644
index 0000000000..28350da4ec
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SSMSessionManagerDocumentLogging.py
@@ -0,0 +1,44 @@
+import json
+
+import yaml
+
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import is_json, is_yaml
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class SSMSessionManagerDocumentLogging(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Session Manager logs are enabled and encrypted"
+ id = "CKV_AWS_113"
+ supported_resources = ["aws_ssm_document"]
+ categories = [CheckCategories.ENCRYPTION, CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if conf.get("document_type") == ["Session"]:
+ doc_format = conf.get("document_format", ["JSON"])
+
+ if "content" in conf.keys():
+ content = conf["content"][0]
+ inputs = None
+
+ if doc_format == ["JSON"] and is_json(content):
+ inputs = json.loads(content).get("inputs", {})
+ elif doc_format == ["YAML"] and is_yaml(content):
+ inputs = yaml.safe_load(content).get("inputs", {})
+
+ if inputs:
+ if inputs.get("s3BucketName") and inputs.get("s3EncryptionEnabled"):
+ return CheckResult.PASSED
+ elif inputs.get("cloudWatchLogGroupName") and inputs.get("cloudWatchEncryptionEnabled"):
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+ return CheckResult.UNKNOWN
+
+ return CheckResult.UNKNOWN
+
+
+check = SSMSessionManagerDocumentLogging()
diff --git a/checkov/terraform/checks/resource/aws/SageMakerInternetAccessDisabled.py b/checkov/terraform/checks/resource/aws/SageMakerInternetAccessDisabled.py
new file mode 100644
index 0000000000..f63831929e
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SageMakerInternetAccessDisabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class SageMakerInternetAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that direct internet access is disabled for an Amazon SageMaker Notebook Instance"
+ id = "CKV_AWS_122"
+ supported_resources = ['aws_sagemaker_notebook_instance']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'direct_internet_access'
+
+ def get_expected_value(self):
+ return 'Disabled'
+
+
+check = SageMakerInternetAccessDisabled()
diff --git a/checkov/terraform/checks/resource/aws/SagemakerNotebookEncryption.py b/checkov/terraform/checks/resource/aws/SagemakerNotebookEncryption.py
index 16fbd3a352..5902ff2867 100644
--- a/checkov/terraform/checks/resource/aws/SagemakerNotebookEncryption.py
+++ b/checkov/terraform/checks/resource/aws/SagemakerNotebookEncryption.py
@@ -5,7 +5,7 @@
class SagemakerNotebookEncryption(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure all data stored in the Sagemaker Notebook is securely encrypted at rest"
+ name = "Ensure SageMaker Notebook is encrypted at rest using KMS CMK"
id = "CKV_AWS_22"
supported_resources = ['aws_sagemaker_notebook_instance']
categories = [CheckCategories.ENCRYPTION]
diff --git a/checkov/terraform/checks/resource/aws/SecretManagerSecretEncrypted.py b/checkov/terraform/checks/resource/aws/SecretManagerSecretEncrypted.py
new file mode 100644
index 0000000000..7d79fbce86
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SecretManagerSecretEncrypted.py
@@ -0,0 +1,21 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class SecretManagerSecretEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Secrets Manager secret is encrypted using KMS"
+ id = "CKV_AWS_149"
+ supported_resources = ["aws_secretsmanager_secret"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+ def get_inspected_key(self):
+ return "kms_key_id"
+
+
+check = SecretManagerSecretEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/SecurityGroupRuleDescription.py b/checkov/terraform/checks/resource/aws/SecurityGroupRuleDescription.py
index e3d54823ea..85b9a66451 100644
--- a/checkov/terraform/checks/resource/aws/SecurityGroupRuleDescription.py
+++ b/checkov/terraform/checks/resource/aws/SecurityGroupRuleDescription.py
@@ -38,7 +38,7 @@ def check_rule(self, rule_type, conf):
for rule in conf[rule_type]:
if isinstance(rule, dict):
if 'description' not in rule.keys() or not rule['description']:
- self.evaluated_keys.append(f'{rule_type}/{conf[rule_type].index(rule)}')
+ self.evaluated_keys.append(f'{rule_type}/[{conf[rule_type].index(rule)}]')
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/SubnetPublicIP.py b/checkov/terraform/checks/resource/aws/SubnetPublicIP.py
new file mode 100644
index 0000000000..ff121d6e99
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/SubnetPublicIP.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+
+
+class SubnetPublicIP(BaseResourceNegativeValueCheck):
+ def get_forbidden_values(self):
+ return [True]
+
+ def get_inspected_key(self):
+ return "map_public_ip_on_launch"
+
+ def __init__(self):
+ name = "Ensure VPC subnets do not assign public IP by default"
+ id = "CKV_AWS_130"
+ supported_resources = ['aws_subnet']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+check = SubnetPublicIP()
diff --git a/checkov/terraform/checks/resource/aws/TimestreamDatabaseKMSKey.py b/checkov/terraform/checks/resource/aws/TimestreamDatabaseKMSKey.py
new file mode 100644
index 0000000000..09095334ce
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/TimestreamDatabaseKMSKey.py
@@ -0,0 +1,23 @@
+from typing import Any
+
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class TimestreamDatabaseKMSKey(BaseResourceValueCheck):
+ def __init__(self) -> None:
+ name = "Ensure that Timestream database is encrypted with KMS CMK"
+ id = "CKV_AWS_160"
+ supported_resources = ["aws_timestreamwrite_database"]
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self) -> str:
+ return "kms_key_id"
+
+ def get_expected_value(self) -> Any:
+ return ANY_VALUE
+
+
+check = TimestreamDatabaseKMSKey()
diff --git a/checkov/terraform/checks/resource/aws/TransferServerIsPublic.py b/checkov/terraform/checks/resource/aws/TransferServerIsPublic.py
new file mode 100644
index 0000000000..52325af2b0
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/TransferServerIsPublic.py
@@ -0,0 +1,19 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class TransferServerIsPublic(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Transfer Server is not exposed publicly."
+ id = "CKV_AWS_164"
+ supported_resources = ['aws_transfer_server']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'endpoint_type'
+
+ def get_expected_values(self):
+ return ["VPC", "VPC_ENDPOINT"]
+
+check = TransferServerIsPublic()
diff --git a/checkov/terraform/checks/resource/aws/VPCDefaultNetwork.py b/checkov/terraform/checks/resource/aws/VPCDefaultNetwork.py
new file mode 100644
index 0000000000..d9bc051d52
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/VPCDefaultNetwork.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+class VPCDefaultNetwork(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure no default VPC is planned to be provisioned"
+ id = "CKV_AWS_148"
+ supported_resources = ['aws_default_vpc']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Checks if there is any attempt to create a default VPC configuration :
+ https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_vpc
+ :param conf: aws_default_vpc configuration
+ :return:
+ """
+
+ return CheckResult.FAILED
+check = VPCDefaultNetwork()
+
+
diff --git a/checkov/terraform/checks/resource/aws/VPCEndpointAcceptanceConfigured.py b/checkov/terraform/checks/resource/aws/VPCEndpointAcceptanceConfigured.py
new file mode 100644
index 0000000000..bc3bc75ce5
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/VPCEndpointAcceptanceConfigured.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class VPCEndpointAcceptanceConfigured(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that VPC Endpoint Service is configured for Manual Acceptance"
+ id = "CKV_AWS_123"
+ supported_resources = ['aws_vpc_endpoint_service']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'acceptance_required'
+
+
+check = VPCEndpointAcceptanceConfigured()
diff --git a/checkov/terraform/checks/resource/aws/WorkspaceRootVolumeEncrypted.py b/checkov/terraform/checks/resource/aws/WorkspaceRootVolumeEncrypted.py
new file mode 100644
index 0000000000..0c2b15d527
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/WorkspaceRootVolumeEncrypted.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class WorkspaceRootVolumeEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Workspace root volumes are encrypted"
+ id = "CKV_AWS_156"
+ supported_resources = ['aws_workspaces_workspace']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'root_volume_encryption_enabled'
+
+ def get_expected_value(self):
+ return True
+
+
+check = WorkspaceRootVolumeEncrypted()
diff --git a/checkov/terraform/checks/resource/aws/WorkspaceUserVolumeEncrypted.py b/checkov/terraform/checks/resource/aws/WorkspaceUserVolumeEncrypted.py
new file mode 100644
index 0000000000..c28ac6f0fb
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/WorkspaceUserVolumeEncrypted.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class WorkspaceUserVolumeEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Workspace user volumes are encrypted"
+ id = "CKV_AWS_155"
+ supported_resources = ['aws_workspaces_workspace']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'user_volume_encryption_enabled'
+
+ def get_expected_value(self):
+ return True
+
+
+check = WorkspaceUserVolumeEncrypted()
diff --git a/checkov/terraform/checks/resource/azure/AKSApiServerAuthorizedIpRanges.py b/checkov/terraform/checks/resource/azure/AKSApiServerAuthorizedIpRanges.py
index 63d34cb75f..df22914e47 100644
--- a/checkov/terraform/checks/resource/azure/AKSApiServerAuthorizedIpRanges.py
+++ b/checkov/terraform/checks/resource/azure/AKSApiServerAuthorizedIpRanges.py
@@ -12,7 +12,7 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def get_inspected_key(self):
- return 'api_server_authorized_ip_ranges/[0]/[0]'
+ return 'api_server_authorized_ip_ranges/[0]'
def get_expected_value(self):
return ANY_VALUE
diff --git a/checkov/terraform/checks/resource/azure/AKSEnablesPrivateClusters.py b/checkov/terraform/checks/resource/azure/AKSEnablesPrivateClusters.py
new file mode 100644
index 0000000000..e2ca1f5892
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AKSEnablesPrivateClusters.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class APIServicesUseVirtualNetwork(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that AKS enables private clusters"
+ id = "CKV_AZURE_115"
+ supported_resources = ['azurerm_kubernetes_cluster']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "private_cluster_enabled"
+
+
+check = APIServicesUseVirtualNetwork()
diff --git a/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py b/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py
new file mode 100644
index 0000000000..5132754c7c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AKSUsesAzurePoliciesAddon(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that AKS uses Azure Policies Add-on"
+ id = "CKV_AZURE_116"
+ supported_resources = ['azurerm_kubernetes_cluster']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "addon_profile/[0]/azure_policy/[0]/enabled"
+
+
+check = AKSUsesAzurePoliciesAddon()
diff --git a/checkov/terraform/checks/resource/azure/AKSUsesDiskEncryptionSet.py b/checkov/terraform/checks/resource/azure/AKSUsesDiskEncryptionSet.py
new file mode 100644
index 0000000000..c914b8983f
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AKSUsesDiskEncryptionSet.py
@@ -0,0 +1,22 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AKSUsesDiskEncryptionSet(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that AKS uses disk encryption set"
+ id = "CKV_AZURE_117"
+ supported_resources = ['azurerm_kubernetes_cluster']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.FAILED)
+
+ def get_inspected_key(self):
+ return "disk_encryption_set_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AKSUsesDiskEncryptionSet()
diff --git a/checkov/terraform/checks/resource/azure/APIServicesUseVirtualNetwork.py b/checkov/terraform/checks/resource/azure/APIServicesUseVirtualNetwork.py
new file mode 100644
index 0000000000..d9330e2728
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/APIServicesUseVirtualNetwork.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class APIServicesUseVirtualNetwork(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that API management services use virtual networks"
+ id = "CKV_AZURE_107"
+ supported_resources = ['azurerm_api_management']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "virtual_network_configuration/[0]/subnet_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = APIServicesUseVirtualNetwork()
diff --git a/checkov/terraform/checks/resource/azure/ActiveDirectoryUsedAuthenticationServiceFabric.py b/checkov/terraform/checks/resource/azure/ActiveDirectoryUsedAuthenticationServiceFabric.py
new file mode 100644
index 0000000000..0d4ee37536
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/ActiveDirectoryUsedAuthenticationServiceFabric.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ActiveDirectoryUsedAuthenticationServiceFabric(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensures that Active Directory is used for authentication for Service Fabric"
+ id = "CKV_AZURE_126"
+ supported_resources = ['azurerm_service_fabric_cluster']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "azure_active_directory/[0]/tenant_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = ActiveDirectoryUsedAuthenticationServiceFabric()
diff --git a/checkov/terraform/checks/resource/azure/AppGWUseWAFMode.py b/checkov/terraform/checks/resource/azure/AppGWUseWAFMode.py
new file mode 100644
index 0000000000..58b163f074
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppGWUseWAFMode.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AppGWUseWAFMode(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Application Gateway uses WAF in \"Detection\" or \"Prevention\" modes"
+ id = "CKV_AZURE_122"
+ supported_resources = ['azurerm_web_application_firewall_policy']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'policy_settings' in conf and conf['policy_settings'][0]:
+ policy_settings = conf['policy_settings'][0]
+ if 'enabled' in policy_settings and not policy_settings['enabled'][0]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = AppGWUseWAFMode()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceDetailedErrorMessagesEnabled.py b/checkov/terraform/checks/resource/azure/AppServiceDetailedErrorMessagesEnabled.py
new file mode 100644
index 0000000000..2eabb6fbc4
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceDetailedErrorMessagesEnabled.py
@@ -0,0 +1,17 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class AppServiceDetailedErrorMessagesEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that App service enables detailed error messages"
+ id = "CKV_AZURE_65"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "logs/[0]/detailed_error_messages_enabled"
+
+
+check = AppServiceDetailedErrorMessagesEnabled()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceDisallowCORS.py b/checkov/terraform/checks/resource/azure/AppServiceDisallowCORS.py
new file mode 100644
index 0000000000..40dd4e408c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceDisallowCORS.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+
+
+class AppServiceDisallowCORS(BaseResourceNegativeValueCheck):
+ def __init__(self):
+ name = "Ensure that CORS disallows every resource to access app services"
+ id = "CKV_AZURE_57"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, missing_attribute_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'site_config/[0]/cors/[0]/allowed_origins'
+
+ def get_forbidden_values(self):
+ return [['*']]
+
+
+check = AppServiceDisallowCORS()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceDotnetFrameworkVersion.py b/checkov/terraform/checks/resource/azure/AppServiceDotnetFrameworkVersion.py
new file mode 100644
index 0000000000..e4fd2f4619
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceDotnetFrameworkVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AppServiceDotnetFrameworkVersion(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that 'Net Framework' version is the latest, if used as a part of the web app"
+ id = "CKV_AZURE_80"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "site_config/0/dotnet_framework_version"
+
+ def get_expected_value(self):
+ return "v5.0"
+
+
+check = AppServiceDotnetFrameworkVersion()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceEnableFailedRequest.py b/checkov/terraform/checks/resource/azure/AppServiceEnableFailedRequest.py
new file mode 100644
index 0000000000..228c5356ce
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceEnableFailedRequest.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AppServiceEnableFailedRequest(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that App service enables failed request tracing"
+ id = "CKV_AZURE_66"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'logs/[0]/failed_request_tracing_enabled'
+
+
+check = AppServiceEnableFailedRequest()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceFTPSState.py b/checkov/terraform/checks/resource/azure/AppServiceFTPSState.py
new file mode 100644
index 0000000000..b1122c5a05
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceFTPSState.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AppServiceFTPSState(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure FTP deployments are disabled"
+ id = "CKV_AZURE_78"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.APPLICATION_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "site_config/0/ftps_state"
+
+ def get_expected_value(self):
+ return "Disabled"
+
+ def get_expected_values(self):
+ return ["Disabled", "FtpsOnly"]
+
+
+check = AppServiceFTPSState()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceHttpLoggingEnabled.py b/checkov/terraform/checks/resource/azure/AppServiceHttpLoggingEnabled.py
new file mode 100644
index 0000000000..1840ed05ef
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceHttpLoggingEnabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class AppServiceHttpLoggingEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that App service enables HTTP logging"
+ id = "CKV_AZURE_63"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "logs/[0]/http_logs"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AppServiceHttpLoggingEnabled()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceIdentityProviderEnabled.py b/checkov/terraform/checks/resource/azure/AppServiceIdentityProviderEnabled.py
new file mode 100644
index 0000000000..ca87036b39
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceIdentityProviderEnabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AppServiceIdentityProviderEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Managed identity provider is enabled for app services"
+ id = "CKV_AZURE_71"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "identity/[0]/type"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AppServiceIdentityProviderEnabled()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceJavaVersion.py b/checkov/terraform/checks/resource/azure/AppServiceJavaVersion.py
new file mode 100644
index 0000000000..27f092f4d1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceJavaVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AppServiceJavaVersion(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that 'Java version' is the latest, if used to run the web app"
+ id = "CKV_AZURE_83"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'site_config' in conf and 'java_version' in conf['site_config'][0]:
+ if conf['site_config'][0]['java_version'][0] != '11':
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = AppServiceJavaVersion()
diff --git a/checkov/terraform/checks/resource/azure/AppServicePHPVersion.py b/checkov/terraform/checks/resource/azure/AppServicePHPVersion.py
new file mode 100644
index 0000000000..faabe5d7e1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServicePHPVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AppServicePHPVersion(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that 'PHP version' is the latest, if used to run the web app"
+ id = "CKV_AZURE_81"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if len(conf.get('site_config', [])) > 0 and 'php_version' in conf['site_config'][0]:
+ if conf['site_config'][0]['php_version'][0] != '7.4':
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = AppServicePHPVersion()
diff --git a/checkov/terraform/checks/resource/azure/AppServicePythonVersion.py b/checkov/terraform/checks/resource/azure/AppServicePythonVersion.py
new file mode 100644
index 0000000000..eb7c778076
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServicePythonVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AppServicePythonVersion(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that 'Python version' is the latest, if used to run the web app"
+ id = "CKV_AZURE_82"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'site_config' in conf and 'python_version' in conf['site_config'][0]:
+ if conf['site_config'][0]['python_version'][0] != '3.4':
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = AppServicePythonVersion()
diff --git a/checkov/terraform/checks/resource/azure/AppServiceUsedAzureFiles.py b/checkov/terraform/checks/resource/azure/AppServiceUsedAzureFiles.py
new file mode 100644
index 0000000000..646029e860
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AppServiceUsedAzureFiles.py
@@ -0,0 +1,20 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class AppServiceUsedAzureFiles(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that app services use Azure Files"
+ id = "CKV_AZURE_88"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "storage_account/type"
+
+ def get_expected_value(self):
+ return 'AzureFiles'
+
+
+check = AppServiceUsedAzureFiles()
diff --git a/checkov/terraform/checks/resource/azure/ApplicationGatewayEnablesWAF.py b/checkov/terraform/checks/resource/azure/ApplicationGatewayEnablesWAF.py
new file mode 100644
index 0000000000..ab39f85cd1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/ApplicationGatewayEnablesWAF.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class ApplicationGatewayEnablesWAF(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Application Gateway enables WAF"
+ id = "CKV_AZURE_120"
+ supported_resources = ['azurerm_application_gateway']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "waf_configuration/[0]/enabled"
+
+
+check = ApplicationGatewayEnablesWAF()
diff --git a/checkov/terraform/checks/resource/azure/AutomationEncrypted.py b/checkov/terraform/checks/resource/azure/AutomationEncrypted.py
new file mode 100644
index 0000000000..d59b895b3e
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AutomationEncrypted.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AutomationEncrypted(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Automation account variables are encrypted"
+ id = "CKV_AZURE_73"
+ supported_resources = ['azurerm_automation_variable_bool','azurerm_automation_variable_string', 'azurerm_automation_variable_int', 'azurerm_automation_variable_datetime']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'encrypted'
+
+
+check = AutomationEncrypted()
diff --git a/checkov/terraform/checks/resource/azure/AzureBatchAccountUsesKeyVaultEncryption.py b/checkov/terraform/checks/resource/azure/AzureBatchAccountUsesKeyVaultEncryption.py
new file mode 100644
index 0000000000..f8e97c0e7a
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureBatchAccountUsesKeyVaultEncryption.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureBatchAccountUsesKeyVaultEncryption(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Batch account uses key vault to encrypt data"
+ id = "CKV_AZURE_76"
+ supported_resources = ['azurerm_batch_account']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'key_vault_reference/[0]/id'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AzureBatchAccountUsesKeyVaultEncryption()
diff --git a/checkov/terraform/checks/resource/azure/AzureContainerGroupDeployedIntoVirtualNetwork.py b/checkov/terraform/checks/resource/azure/AzureContainerGroupDeployedIntoVirtualNetwork.py
new file mode 100644
index 0000000000..623bd2fba1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureContainerGroupDeployedIntoVirtualNetwork.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureContainerGroupDeployedIntoVirtualNetwork(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Container group is deployed into virtual network"
+ id = "CKV_AZURE_98"
+ supported_resources = ['azurerm_container_group']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'network_profile_id'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AzureContainerGroupDeployedIntoVirtualNetwork()
diff --git a/checkov/terraform/checks/resource/azure/AzureDataExplorerDoubleEncryptionEnabled.py b/checkov/terraform/checks/resource/azure/AzureDataExplorerDoubleEncryptionEnabled.py
new file mode 100644
index 0000000000..8522506857
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDataExplorerDoubleEncryptionEnabled.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureDataExplorerDoubleEncryptionEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Data Explorer uses double encryption"
+ id = "CKV_AZURE_75"
+ supported_resources = ['azurerm_kusto_cluster']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "double_encryption_enabled"
+
+ def get_expected_value(self):
+ return True
+
+
+check = AzureDataExplorerDoubleEncryptionEnabled()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnAppServices.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnAppServices.py
new file mode 100644
index 0000000000..138c548138
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnAppServices.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnAppServices(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for App Service"
+ id = "CKV_AZURE_61"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'AppServices' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnAppServices()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnContainerRegistry.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnContainerRegistry.py
new file mode 100644
index 0000000000..67fe1f67a1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnContainerRegistry.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnContainerRegistry(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Container Registries"
+ id = "CKV_AZURE_86"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'ContainerRegistry' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnContainerRegistry()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnKeyVaults.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnKeyVaults.py
new file mode 100644
index 0000000000..5f6af4f51c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnKeyVaults.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnKeyVaults(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Key Vault"
+ id = "CKV_AZURE_87"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'KeyVaults' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnKeyVaults()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnKubernetes.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnKubernetes.py
new file mode 100644
index 0000000000..a491488d3b
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnKubernetes.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnKubernetes(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Kubernetes"
+ id = "CKV_AZURE_85"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'KubernetesService' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnKubernetes()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnServers.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnServers.py
new file mode 100644
index 0000000000..0b8214b3ec
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnServers.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnServers(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Servers"
+ id = "CKV_AZURE_55"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'VirtualMachines' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnServers()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServerVMS.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServerVMS.py
new file mode 100644
index 0000000000..6895036543
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServerVMS.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnSqlServersVMS(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for SQL servers on machines"
+ id = "CKV_AZURE_79"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'SqlServerVirtualMachines' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnSqlServersVMS()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServers.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServers.py
new file mode 100644
index 0000000000..2880f3ed2a
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnSqlServers.py
@@ -0,0 +1,19 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnSqlServers(BaseResourceCheck):
+
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Azure SQL database servers"
+ id = "CKV_AZURE_69"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'SqlServers' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnSqlServers()
diff --git a/checkov/terraform/checks/resource/azure/AzureDefenderOnStorage.py b/checkov/terraform/checks/resource/azure/AzureDefenderOnStorage.py
new file mode 100644
index 0000000000..e0d26ce803
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureDefenderOnStorage.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureDefenderOnStorage(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Defender is set to On for Storage"
+ id = "CKV_AZURE_84"
+ supported_resources = ['azurerm_security_center_subscription_pricing']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ return CheckResult.PASSED if conf.get('resource_type', [None])[0] != 'StorageAccounts' \
+ or conf.get('tier', [None])[0] == 'Standard' else CheckResult.FAILED
+
+
+check = AzureDefenderOnStorage()
diff --git a/checkov/terraform/checks/resource/azure/AzureFrontDoorEnablesWAF.py b/checkov/terraform/checks/resource/azure/AzureFrontDoorEnablesWAF.py
new file mode 100644
index 0000000000..435d45350b
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureFrontDoorEnablesWAF.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureFrontDoorEnablesWAF(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Front Door enables WAF"
+ id = "CKV_AZURE_121"
+ supported_resources = ['azurerm_frontdoor']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "web_application_firewall_policy_link_id"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AzureFrontDoorEnablesWAF()
diff --git a/checkov/terraform/checks/resource/azure/AzureInstanceExtensions.py b/checkov/terraform/checks/resource/azure/AzureInstanceExtensions.py
new file mode 100644
index 0000000000..dd4d06951e
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureInstanceExtensions.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureInstanceExtensions(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Virtual Machine Extensions are not Installed"
+ id = "CKV_AZURE_50"
+ supported_resources = ['azurerm_virtual_machine', 'azurerm_linux_virtual_machine']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'allow_extension_operations'
+
+ def get_expected_value(self):
+ return False
+
+
+check = AzureInstanceExtensions()
diff --git a/checkov/terraform/checks/resource/azure/AzureManagedDiscEncryption.py b/checkov/terraform/checks/resource/azure/AzureManagedDiscEncryption.py
deleted file mode 100644
index 261dd0a617..0000000000
--- a/checkov/terraform/checks/resource/azure/AzureManagedDiscEncryption.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-
-
-class AzureManagedDiscEncryption(BaseResourceCheck):
- def __init__(self):
- name = "Ensure Azure managed disk have encryption enabled"
- id = "CKV_AZURE_2"
- supported_resources = ['azurerm_managed_disk']
- categories = [CheckCategories.ENCRYPTION]
- super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-
- def scan_resource_conf(self, conf):
- """
- Looks for password configuration at azure_instance:
- https://www.terraform.io/docs/providers/azure/r/instance.html
- :param conf: azure_instance configuration
- :return:
- """
- if 'encryption_settings' in conf.keys():
- config = conf['encryption_settings'][0]
- if isinstance(config, dict) and not config['enabled'][0]:
- return CheckResult.FAILED
- return CheckResult.PASSED
-
-
-check = AzureManagedDiscEncryption()
diff --git a/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryption.py b/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryption.py
new file mode 100644
index 0000000000..40c22e685c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryption.py
@@ -0,0 +1,22 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureManagedDiskEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Azure managed disk has encryption enabled"
+ id = "CKV_AZURE_2"
+ supported_resources = ['azurerm_managed_disk']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'disk_encryption_set_id' in conf:
+ return CheckResult.PASSED
+ if 'encryption_settings' in conf:
+ if isinstance(conf['encryption_settings'][0], dict):
+ return CheckResult.PASSED if conf['encryption_settings'][0]['enabled'][0] else CheckResult.FAILED
+ return CheckResult.PASSED # enabled by default
+
+
+check = AzureManagedDiskEncryption()
diff --git a/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryptionSet.py b/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryptionSet.py
new file mode 100644
index 0000000000..9199263b8f
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureManagedDiskEncryptionSet.py
@@ -0,0 +1,22 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureManagedDiskEncryptionSet(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that managed disks use a specific set of disk encryption sets for the " \
+ "customer-managed key encryption"
+ id = "CKV_AZURE_93"
+ supported_resources = ['azurerm_managed_disk']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'disk_encryption_set_id'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = AzureManagedDiskEncryptionSet()
diff --git a/checkov/terraform/checks/resource/azure/AzureScaleSetPassword.py b/checkov/terraform/checks/resource/azure/AzureScaleSetPassword.py
new file mode 100644
index 0000000000..fe0e0b7035
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureScaleSetPassword.py
@@ -0,0 +1,27 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureScaleSetPassword(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Azure linux scale set does not use basic authentication(Use SSH Key Instead)"
+ id = "CKV_AZURE_49"
+ supported_resources = ['azurerm_linux_virtual_machine_scale_set']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Looks for password configuration at azure_instance:
+ https://www.terraform.io/docs/providers/azurerm/r/linux_virtual_machine_scale_set.html
+ :param conf: azurerm_linux_virtual_machine_scale_set configuration
+ :return:
+ """
+ if 'disable_password_authentication' in conf.keys():
+ if conf.get('disable_password_authentication', [True])[0]:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+
+check = AzureScaleSetPassword()
diff --git a/checkov/terraform/checks/resource/azure/AzureSearchPublicNetworkAccessDisabled.py b/checkov/terraform/checks/resource/azure/AzureSearchPublicNetworkAccessDisabled.py
new file mode 100644
index 0000000000..7955c5f3a0
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureSearchPublicNetworkAccessDisabled.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class AzureSearchPublicNetworkAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Cognitive Search disables public network access"
+ id = "CKV_AZURE_124"
+ supported_resources = ['azurerm_search_service']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = AzureSearchPublicNetworkAccessDisabled()
diff --git a/checkov/terraform/checks/resource/azure/AzureServiceFabricClusterProtectionLevel.py b/checkov/terraform/checks/resource/azure/AzureServiceFabricClusterProtectionLevel.py
new file mode 100644
index 0000000000..6841a058fc
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/AzureServiceFabricClusterProtectionLevel.py
@@ -0,0 +1,25 @@
+from typing import Dict, List, Any
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.common.util.type_forcers import force_list
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AzureServiceFabricClusterProtectionLevel(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensures that Service Fabric use three levels of protection available"
+ id = "CKV_AZURE_125"
+ supported_resources = ['azurerm_service_fabric_cluster']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ for setting in force_list(conf.get('fabric_settings')):
+ if setting and setting.get('name') == ['Security']:
+ params = setting.get('parameters', [{}])[0]
+ if params.get('name') == 'ClusterProtectionLevel' and params.get('value') == 'EncryptAndSign':
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = AzureServiceFabricClusterProtectionLevel()
diff --git a/checkov/terraform/checks/resource/azure/CosmosDBAccountsRestrictedAccess.py b/checkov/terraform/checks/resource/azure/CosmosDBAccountsRestrictedAccess.py
new file mode 100644
index 0000000000..6e9013ed82
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/CosmosDBAccountsRestrictedAccess.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class CosmosDBAccountsRestrictedAccess(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Cosmos DB accounts have restricted access"
+ id = "CKV_AZURE_99"
+ supported_resources = ['azurerm_cosmosdb_account']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'public_network_access_enabled' not in conf or conf['public_network_access_enabled'][0]:
+ if 'is_virtual_network_filter_enabled' in conf and conf['is_virtual_network_filter_enabled'][0]:
+ if ('virtual_network_rule' in conf and conf['virtual_network_rule'][0]) \
+ or ('ip_range_filter' in conf and conf['ip_range_filter'][0]):
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = CosmosDBAccountsRestrictedAccess()
diff --git a/checkov/terraform/checks/resource/azure/CosmosDBDisablesPublicNetwork.py b/checkov/terraform/checks/resource/azure/CosmosDBDisablesPublicNetwork.py
new file mode 100644
index 0000000000..8c465c9bea
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/CosmosDBDisablesPublicNetwork.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class CosmosDBDisablesPublicNetwork(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Cosmos DB disables public network access"
+ id = "CKV_AZURE_101"
+ supported_resources = ['azurerm_cosmosdb_account']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = CosmosDBDisablesPublicNetwork()
diff --git a/checkov/terraform/checks/resource/azure/CosmosDBHaveCMK.py b/checkov/terraform/checks/resource/azure/CosmosDBHaveCMK.py
new file mode 100644
index 0000000000..1b097306b9
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/CosmosDBHaveCMK.py
@@ -0,0 +1,21 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class CosmosDBHaveCMK(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Cosmos DB accounts have customer-managed keys to encrypt data at rest"
+ id = "CKV_AZURE_100"
+ supported_resources = ['azurerm_cosmosdb_account']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'key_vault_key_id'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = CosmosDBHaveCMK()
diff --git a/checkov/terraform/checks/resource/azure/CutsomRoleDefinitionSubscriptionOwner.py b/checkov/terraform/checks/resource/azure/CutsomRoleDefinitionSubscriptionOwner.py
index 513f4f567e..e3aae925c2 100644
--- a/checkov/terraform/checks/resource/azure/CutsomRoleDefinitionSubscriptionOwner.py
+++ b/checkov/terraform/checks/resource/azure/CutsomRoleDefinitionSubscriptionOwner.py
@@ -1,6 +1,5 @@
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
-import re
class CustomRoleDefinitionSubscriptionOwner(BaseResourceCheck):
@@ -12,10 +11,8 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
- subscription = re.compile(r"\/|\/subscriptions\/[\w\d-]+$|\${(data|resource)\.azurerm_subscription\.")
- if any(re.match(subscription, scope) for scope in conf['assignable_scopes'][0]):
- if 'actions' in conf['permissions'][0] and '*' in conf['permissions'][0]['actions'][0]:
- return CheckResult.FAILED
+ if 'actions' in conf['permissions'][0] and '*' in conf['permissions'][0]['actions'][0]:
+ return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/azure/DataExplorerUsesDiskEncryption.py b/checkov/terraform/checks/resource/azure/DataExplorerUsesDiskEncryption.py
new file mode 100644
index 0000000000..8aa5a9608d
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/DataExplorerUsesDiskEncryption.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class DataExplorerUsesDiskEncryption(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Data Explorer uses disk encryption"
+ id = "CKV_AZURE_74"
+ supported_resources = ['azurerm_kusto_cluster']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'enable_disk_encryption'
+
+
+check = DataExplorerUsesDiskEncryption()
diff --git a/checkov/terraform/checks/resource/azure/DataFactoryNoPublicNetworkAccess.py b/checkov/terraform/checks/resource/azure/DataFactoryNoPublicNetworkAccess.py
new file mode 100644
index 0000000000..2b44897586
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/DataFactoryNoPublicNetworkAccess.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class DataFactoryNoPublicNetworkAccess(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Data factory public network access is disabled"
+ id = "CKV_AZURE_104"
+ supported_resources = ['azurerm_data_factory']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = DataFactoryNoPublicNetworkAccess()
\ No newline at end of file
diff --git a/checkov/terraform/checks/resource/azure/DataFactoryUsesGitRepository.py b/checkov/terraform/checks/resource/azure/DataFactoryUsesGitRepository.py
new file mode 100644
index 0000000000..07773af1b9
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/DataFactoryUsesGitRepository.py
@@ -0,0 +1,25 @@
+from typing import Dict, List, Any
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class DataFactoryUsesGitRepository(BaseResourceCheck):
+ def __init__(self) -> None:
+ name = "Ensure that Azure Data Factory uses Git repository for source control"
+ id = "CKV_AZURE_103"
+ supported_resources = ["azurerm_data_factory"]
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ github = conf.get("github_configuration", [{}])[0]
+ if github.get("repository_name"):
+ return CheckResult.PASSED
+ vsts = conf.get("vsts_configuration", [{}])[0]
+ if vsts.get("repository_name"):
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = DataFactoryUsesGitRepository()
diff --git a/checkov/terraform/checks/resource/azure/DataLakeStoreEncryption.py b/checkov/terraform/checks/resource/azure/DataLakeStoreEncryption.py
new file mode 100644
index 0000000000..7b4f40bbbf
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/DataLakeStoreEncryption.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class DataLakeStoreEncryption(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Data Lake Store accounts enables encryption"
+ id = "CKV_AZURE_105"
+ supported_resources = ['azurerm_data_lake_store']
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'encryption_state'
+
+ def get_expected_value(self):
+ return "Enabled"
+
+
+check = DataLakeStoreEncryption()
diff --git a/checkov/terraform/checks/resource/azure/EventgridDomainNetworkAccess.py b/checkov/terraform/checks/resource/azure/EventgridDomainNetworkAccess.py
new file mode 100644
index 0000000000..4e7975ee37
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/EventgridDomainNetworkAccess.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class EventgridDomainNetworkAccess(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Event Grid Domain public network access is disabled"
+ id = "CKV_AZURE_106"
+ supported_resources = ['azurerm_eventgrid_domain']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, )
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = EventgridDomainNetworkAccess()
diff --git a/checkov/terraform/checks/resource/azure/FrontdoorUseWAFMode.py b/checkov/terraform/checks/resource/azure/FrontdoorUseWAFMode.py
new file mode 100644
index 0000000000..c6bb2bded1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/FrontdoorUseWAFMode.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class FrontdoorUseWAFMode(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Azure Front Door uses WAF in \"Detection\" or \"Prevention\" modes"
+ id = "CKV_AZURE_123"
+ supported_resources = ['azurerm_frontdoor_firewall_policy']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'policy_settings' in conf and conf['policy_settings'][0]:
+ policy_settings = conf['policy_settings'][0]
+ if 'enabled' in policy_settings and not policy_settings['enabled'][0]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = FrontdoorUseWAFMode()
diff --git a/checkov/terraform/checks/resource/azure/FunctionAppDisallowCORS.py b/checkov/terraform/checks/resource/azure/FunctionAppDisallowCORS.py
new file mode 100644
index 0000000000..7b798361dd
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/FunctionAppDisallowCORS.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+
+
+class FunctionAppDisallowCORS(BaseResourceNegativeValueCheck):
+ def __init__(self):
+ name = "Ensure function apps are not accessible from all regions"
+ id = "CKV_AZURE_62"
+ supported_resources = ['azurerm_function_app']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, missing_attribute_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'site_config/[0]/cors/[0]/allowed_origins'
+
+ def get_forbidden_values(self):
+ return [['*']]
+
+
+check = FunctionAppDisallowCORS()
diff --git a/checkov/terraform/checks/resource/azure/FunctionAppHttpVersionLatest.py b/checkov/terraform/checks/resource/azure/FunctionAppHttpVersionLatest.py
new file mode 100644
index 0000000000..bd5e2a2624
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/FunctionAppHttpVersionLatest.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class FunctionAppHttpVersionLatest(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that 'HTTP Version' is the latest, if used to run the Function app"
+ id = "CKV_AZURE_67"
+ supported_resources = ['azurerm_function_app']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'site_config/[0]/http2_enabled'
+
+
+check = FunctionAppHttpVersionLatest()
diff --git a/checkov/terraform/checks/resource/azure/FunctionAppsAccessibleOverHttps.py b/checkov/terraform/checks/resource/azure/FunctionAppsAccessibleOverHttps.py
new file mode 100644
index 0000000000..07b253a4b6
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/FunctionAppsAccessibleOverHttps.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class FunctionAppsAccessibleOverHttps(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Function apps is only accessible over HTTPS"
+ id = "CKV_AZURE_70"
+ supported_resources = ['azurerm_function_app']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'https_only'
+
+
+check = FunctionAppsAccessibleOverHttps()
diff --git a/checkov/terraform/checks/resource/azure/FunctionAppsEnableAuthentication.py b/checkov/terraform/checks/resource/azure/FunctionAppsEnableAuthentication.py
new file mode 100644
index 0000000000..dccd09a20a
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/FunctionAppsEnableAuthentication.py
@@ -0,0 +1,18 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class FunctionAppsEnableAuthentication(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure that function apps enables Authentication"
+ id = "CKV_AZURE_56"
+ supported_resources = ['azurerm_function_app']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'auth_settings/[0]/enabled'
+
+
+check = FunctionAppsEnableAuthentication()
diff --git a/checkov/terraform/checks/resource/azure/IoTNoPublicNetworkAccess.py b/checkov/terraform/checks/resource/azure/IoTNoPublicNetworkAccess.py
new file mode 100644
index 0000000000..e932dc8647
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/IoTNoPublicNetworkAccess.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class IoTNoPublicNetworkAccess(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure IoT Hub disables public network access"
+ id = "CKV_AZURE_108"
+ supported_resources = ['azurerm_iothub']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = IoTNoPublicNetworkAccess()
diff --git a/checkov/terraform/checks/resource/azure/KeyBackedByHSM.py b/checkov/terraform/checks/resource/azure/KeyBackedByHSM.py
new file mode 100644
index 0000000000..335a4c70a3
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/KeyBackedByHSM.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class KeyBackedByHSM(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that key vault key is backed by HSM"
+ id = "CKV_AZURE_112"
+ supported_resources = ['azurerm_key_vault_key']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'key_type'
+
+ def get_expected_value(self):
+ return 'RSA-HSM'
+
+ def get_expected_values(self):
+ return [self.get_expected_value(), 'EC-HSM']
+
+
+check = KeyBackedByHSM()
diff --git a/checkov/terraform/checks/resource/azure/KeyVaultEnablesFirewallRulesSettings.py b/checkov/terraform/checks/resource/azure/KeyVaultEnablesFirewallRulesSettings.py
new file mode 100644
index 0000000000..062943fddc
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/KeyVaultEnablesFirewallRulesSettings.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class KeyVaultEnablesFirewallRulesSettings(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that key vault allows firewall rules settings"
+ id = "CKV_AZURE_109"
+ supported_resources = ['azurerm_key_vault']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "network_acls/[0]/default_action"
+
+ def get_expected_value(self):
+ return "Deny"
+
+
+check = KeyVaultEnablesFirewallRulesSettings()
diff --git a/checkov/terraform/checks/resource/azure/KeyVaultEnablesPurgeProtection.py b/checkov/terraform/checks/resource/azure/KeyVaultEnablesPurgeProtection.py
new file mode 100644
index 0000000000..acbdb26373
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/KeyVaultEnablesPurgeProtection.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class KeyVaultEnablesPurgeProtection(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that key vault enables purge protection"
+ id = "CKV_AZURE_110"
+ supported_resources = ['azurerm_key_vault']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "purge_protection_enabled"
+
+
+check = KeyVaultEnablesPurgeProtection()
diff --git a/checkov/terraform/checks/resource/azure/KeyVaultEnablesSoftDelete.py b/checkov/terraform/checks/resource/azure/KeyVaultEnablesSoftDelete.py
new file mode 100644
index 0000000000..7eaa162cc1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/KeyVaultEnablesSoftDelete.py
@@ -0,0 +1,19 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.enums import CheckResult
+
+
+class KeyVaultEnablesSoftDelete(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that key vault enables soft delete"
+ id = "CKV_AZURE_111"
+ supported_resources = ['azurerm_key_vault']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return "soft_delete_enabled"
+
+
+check = KeyVaultEnablesSoftDelete()
diff --git a/checkov/terraform/checks/resource/azure/MSSQLServerMinTLSVersion.py b/checkov/terraform/checks/resource/azure/MSSQLServerMinTLSVersion.py
new file mode 100644
index 0000000000..936f6fab52
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MSSQLServerMinTLSVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MSSQLServerMinTLSVersion(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure MSSQL is using the latest version of TLS encryption"
+ id = "CKV_AZURE_52"
+ supported_resources = ['azurerm_mssql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+ def get_inspected_key(self):
+ return "minimum_tls_version"
+
+ def get_expected_value(self):
+ return "1.2"
+
+check = MSSQLServerMinTLSVersion()
diff --git a/checkov/terraform/checks/resource/azure/MariaDBGeoBackupEnabled.py b/checkov/terraform/checks/resource/azure/MariaDBGeoBackupEnabled.py
new file mode 100644
index 0000000000..1a6205c537
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MariaDBGeoBackupEnabled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MariaDBGeoBackupEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that MariaDB server enables geo-redundant backups"
+ id = "CKV_AZURE_129"
+ supported_resources = ['azurerm_mariadb_server']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+ def get_inspected_key(self):
+ return "geo_redundant_backup_enabled"
+
+check = MariaDBGeoBackupEnabled()
diff --git a/checkov/terraform/checks/resource/azure/MariaDBPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/MariaDBPublicAccessDisabled.py
new file mode 100644
index 0000000000..aee5836466
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MariaDBPublicAccessDisabled.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class MariaDBPublicAccessDisabled(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure 'public network access enabled' is set to 'False' for MariaDB servers"
+ id = "CKV_AZURE_48"
+ supported_resources = ['azurerm_mariadb_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ #Whether or not public network access is allowed for this server. Defaults to true. Which is not optimal
+ if 'public_network_access_enabled' not in conf:
+ return CheckResult.FAILED
+ else:
+ if conf['public_network_access_enabled'][0]:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.PASSED
+
+check = MariaDBPublicAccessDisabled()
diff --git a/checkov/terraform/checks/resource/azure/MariaDBSSLEnforcementEnabled.py b/checkov/terraform/checks/resource/azure/MariaDBSSLEnforcementEnabled.py
new file mode 100644
index 0000000000..247120d2f8
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MariaDBSSLEnforcementEnabled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MariaDBSSLEnforcementEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure 'Enforce SSL connection' is set to 'ENABLED' for MariaDB servers"
+ id = "CKV_AZURE_47"
+ supported_resources = ['azurerm_mariadb_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'ssl_enforcement_enabled'
+
+
+check = MariaDBSSLEnforcementEnabled()
diff --git a/checkov/terraform/checks/resource/azure/MonitorLogProfileCategories.py b/checkov/terraform/checks/resource/azure/MonitorLogProfileCategories.py
index 42692868ec..01010ff8a3 100644
--- a/checkov/terraform/checks/resource/azure/MonitorLogProfileCategories.py
+++ b/checkov/terraform/checks/resource/azure/MonitorLogProfileCategories.py
@@ -1,6 +1,5 @@
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
-from checkov.common.util.type_forcers import force_int
class MonitorLogProfileRetentionDays(BaseResourceCheck):
@@ -13,7 +12,9 @@ def __init__(self):
def scan_resource_conf(self, conf):
categories = ['Write', 'Delete', 'Action']
- if all(category in conf['categories'][0] for category in categories):
+ res_categories = conf.get('categories')
+ if isinstance(res_categories, list) and res_categories and \
+ all(category in conf['categories'][0] for category in categories):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/azure/MonitorLogProfileRetentionDays.py b/checkov/terraform/checks/resource/azure/MonitorLogProfileRetentionDays.py
index aa2ad377fa..323555c619 100644
--- a/checkov/terraform/checks/resource/azure/MonitorLogProfileRetentionDays.py
+++ b/checkov/terraform/checks/resource/azure/MonitorLogProfileRetentionDays.py
@@ -12,6 +12,8 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
+ if not conf.get('retention_policy'):
+ return CheckResult.FAILED
if conf['retention_policy'][0]['enabled'][0]:
if 'days' in conf['retention_policy'][0] and force_int(conf['retention_policy'][0]['days'][0]) >= 365:
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/azure/MySQLEncryptionEnaled.py b/checkov/terraform/checks/resource/azure/MySQLEncryptionEnaled.py
new file mode 100644
index 0000000000..738a3c9a38
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLEncryptionEnaled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLEncryptionEnaled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that MySQL server enables infrastructure encryption"
+ id = "CKV_AZURE_96"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'infrastructure_encryption_enabled'
+
+
+check = MySQLEncryptionEnaled()
diff --git a/checkov/terraform/checks/resource/azure/MySQLGeoBackupEnabled.py b/checkov/terraform/checks/resource/azure/MySQLGeoBackupEnabled.py
new file mode 100644
index 0000000000..ca0e40b44c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLGeoBackupEnabled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLGeoBackupEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that My SQL server enables geo-redundant backups"
+ id = "CKV_AZURE_94"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'geo_redundant_backup_enabled'
+
+
+check = MySQLGeoBackupEnabled()
diff --git a/checkov/terraform/checks/resource/azure/MySQLPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/MySQLPublicAccessDisabled.py
new file mode 100644
index 0000000000..9b78d1d05f
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLPublicAccessDisabled.py
@@ -0,0 +1,24 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLPublicAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure 'public network access enabled' is set to 'False' for mySQL servers"
+ id = "CKV_AZURE_53"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ """
+ Returns the default expected value, governed by provider best practices
+ """
+ return False
+
+
+check = MySQLPublicAccessDisabled()
+
diff --git a/checkov/terraform/checks/resource/azure/MySQLServerMinTLSVersion.py b/checkov/terraform/checks/resource/azure/MySQLServerMinTLSVersion.py
new file mode 100644
index 0000000000..9f40bf8bc3
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLServerMinTLSVersion.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLServerMinTLSVersion(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure MySQL is using the latest version of TLS encryption"
+ id = "CKV_AZURE_54"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+ def get_inspected_key(self):
+ return "ssl_minimal_tls_version_enforced"
+
+ def get_expected_value(self):
+ return "TLS1_2"
+
+check = MySQLServerMinTLSVersion()
diff --git a/checkov/terraform/checks/resource/azure/MySQLServerPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/MySQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..7d2da21cef
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLServerPublicAccessDisabled.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLServerHasPublicAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that MySQL server disables public network access"
+ id = "CKV_AZURE_90"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def scan_resource_conf(self, conf):
+ public_access = conf.get('public_network_access_enabled', [True])
+ if public_access[0]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = MySQLServerHasPublicAccessDisabled()
diff --git a/checkov/terraform/checks/resource/azure/MySQLTreatDetectionEnabled.py b/checkov/terraform/checks/resource/azure/MySQLTreatDetectionEnabled.py
new file mode 100644
index 0000000000..d6f22aa008
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/MySQLTreatDetectionEnabled.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class MySQLTreatDetectionEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that My SQL server enables Threat detection policy"
+ id = "CKV_AZURE_127"
+ supported_resources = ['azurerm_mysql_server']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+ def get_inspected_key(self):
+ return "threat_detection_policy/enabled"
+
+
+check = MySQLTreatDetectionEnabled()
diff --git a/checkov/terraform/checks/resource/azure/NSGRulePortAccessRestricted.py b/checkov/terraform/checks/resource/azure/NSGRulePortAccessRestricted.py
index a1c19b055a..e19d033568 100644
--- a/checkov/terraform/checks/resource/azure/NSGRulePortAccessRestricted.py
+++ b/checkov/terraform/checks/resource/azure/NSGRulePortAccessRestricted.py
@@ -4,7 +4,7 @@
import re
INTERNET_ADDRESSES = ["*", "0.0.0.0", "/0", "/0", "internet", "any"] # nosec
-PORT_RANGE = re.compile('\d+-\d+')
+PORT_RANGE = re.compile(r'\d+-\d+')
class NSGRulePortAccessRestricted(BaseResourceCheck):
diff --git a/checkov/terraform/checks/resource/azure/NSGRuleUDPAccessRestricted.py b/checkov/terraform/checks/resource/azure/NSGRuleUDPAccessRestricted.py
new file mode 100644
index 0000000000..5c4b371667
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/NSGRuleUDPAccessRestricted.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.azure.NSGRulePortAccessRestricted import INTERNET_ADDRESSES
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class NSGRuleUDPAccessRestricted(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that UDP Services are restricted from the Internet "
+ id = "CKV_AZURE_77"
+ supported_resources = ['azurerm_network_security_group', 'azurerm_network_security_rule']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ rule_confs = [conf]
+ if 'security_rule' in conf:
+ rule_confs = conf['security_rule']
+ for rule_conf in rule_confs:
+ if 'protocol' in rule_conf and rule_conf['protocol'][0].lower() == 'udp' \
+ and 'direction' in rule_conf and rule_conf['direction'][0].lower() == 'inbound' \
+ and 'access' in rule_conf and rule_conf['access'][0].lower() == 'allow' \
+ and 'source_address_prefix' in rule_conf \
+ and rule_conf['source_address_prefix'][0].lower() in INTERNET_ADDRESSES:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = NSGRuleUDPAccessRestricted()
diff --git a/checkov/terraform/checks/resource/azure/NetworkInterfaceEnableIPForwarding.py b/checkov/terraform/checks/resource/azure/NetworkInterfaceEnableIPForwarding.py
new file mode 100644
index 0000000000..2db4f3aaaa
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/NetworkInterfaceEnableIPForwarding.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class NetworkInterfaceEnableIPForwarding(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Network Interfaces disable IP forwarding"
+ id = "CKV_AZURE_118"
+ supported_resources = ['azurerm_network_interface']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return 'enable_ip_forwarding'
+
+ def get_expected_value(self):
+ return False
+
+
+check = NetworkInterfaceEnableIPForwarding()
diff --git a/checkov/terraform/checks/resource/azure/NetworkInterfacePublicIPAddressId.py b/checkov/terraform/checks/resource/azure/NetworkInterfacePublicIPAddressId.py
new file mode 100644
index 0000000000..dfcc0e590b
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/NetworkInterfacePublicIPAddressId.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class NetworkInterfacePublicIPAddressId(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Network Interfaces don't use public IPs"
+ id = "CKV_AZURE_119"
+ supported_resources = ['azurerm_network_interface']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ ip_configurations = conf.get('ip_configuration', [])
+ for ip_configuration in ip_configurations:
+ if 'public_ip_address_id' in ip_configuration:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = NetworkInterfacePublicIPAddressId()
diff --git a/checkov/terraform/checks/resource/azure/NetworkWatcherFlowLogPeriod.py b/checkov/terraform/checks/resource/azure/NetworkWatcherFlowLogPeriod.py
index 7ee6f6d067..ea6b13581c 100644
--- a/checkov/terraform/checks/resource/azure/NetworkWatcherFlowLogPeriod.py
+++ b/checkov/terraform/checks/resource/azure/NetworkWatcherFlowLogPeriod.py
@@ -16,7 +16,7 @@ def scan_resource_conf(self, conf):
retention_block = conf['retention_policy'][0]
if retention_block['enabled'][0]:
retention_in_days = force_int(retention_block['days'][0])
- if retention_in_days and retention_in_days >= 90:
+ if retention_in_days is not None and (retention_in_days == 0 or retention_in_days >= 90):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/azure/PostgersSQLEncryptionEnaled.py b/checkov/terraform/checks/resource/azure/PostgersSQLEncryptionEnaled.py
new file mode 100644
index 0000000000..51cd66c9b2
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/PostgersSQLEncryptionEnaled.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class PostgersSQLEncryptionEnaled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that PostgreSQL server enables infrastructure encryption"
+ id = "CKV_AZURE_130"
+ supported_resources = ['azurerm_postgresql_server']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'infrastructure_encryption_enabled'
+
+
+
+check = PostgersSQLEncryptionEnaled()
diff --git a/checkov/terraform/checks/resource/azure/PostgreSQLServerPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/PostgreSQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..0ed1ee2714
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/PostgreSQLServerPublicAccessDisabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class PostgreSQLServerHasPublicAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that PostgreSQL server disables public network access"
+ id = "CKV_AZURE_68"
+ supported_resources = ['azurerm_postgresql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.FAILED)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = PostgreSQLServerHasPublicAccessDisabled()
diff --git a/checkov/terraform/checks/resource/azure/PostgresSQLTreatDetectionEnabled.py b/checkov/terraform/checks/resource/azure/PostgresSQLTreatDetectionEnabled.py
new file mode 100644
index 0000000000..ebd9bb399d
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/PostgresSQLTreatDetectionEnabled.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class PostgresSQLTreatDetectionEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that PostgreSQL server enables Threat detection policy"
+ id = "CKV_AZURE_128"
+ supported_resources = ['azurerm_postgresql_server']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+
+ def get_inspected_key(self):
+ return "threat_detection_policy/enabled"
+
+
+check = PostgresSQLTreatDetectionEnabled()
diff --git a/checkov/terraform/checks/resource/azure/PostgressSQLGeoBackupEnabled.py b/checkov/terraform/checks/resource/azure/PostgressSQLGeoBackupEnabled.py
new file mode 100644
index 0000000000..7bab11fb67
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/PostgressSQLGeoBackupEnabled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class PostgressSQLGeoBackupEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that PostgreSQL server enables geo-redundant backups"
+ id = "CKV_AZURE_102"
+ supported_resources = ['azurerm_postgresql_server']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'geo_redundant_backup_enabled'
+
+
+check = PostgressSQLGeoBackupEnabled()
diff --git a/checkov/terraform/checks/resource/azure/RedisCacheEnableNonSSLPort.py b/checkov/terraform/checks/resource/azure/RedisCacheEnableNonSSLPort.py
new file mode 100644
index 0000000000..21035118db
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/RedisCacheEnableNonSSLPort.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RedisCacheEnableNonSSLPort(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that only SSL are enabled for Cache for Redis"
+ id = "CKV_AZURE_91"
+ supported_resources = ['azurerm_redis_cache']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return "enable_non_ssl_port"
+
+ def get_expected_value(self):
+ return False
+
+
+check = RedisCacheEnableNonSSLPort()
diff --git a/checkov/terraform/checks/resource/azure/RedisCachePublicNetworkAccessEnabled.py b/checkov/terraform/checks/resource/azure/RedisCachePublicNetworkAccessEnabled.py
new file mode 100644
index 0000000000..7f3c2fbb3a
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/RedisCachePublicNetworkAccessEnabled.py
@@ -0,0 +1,20 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RedisCachePublicNetworkAccessEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Cache for Redis disables public network access"
+ id = "CKV_AZURE_89"
+ supported_resources = ['azurerm_redis_cache']
+ categories = [CheckCategories.IAM]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = RedisCachePublicNetworkAccessEnabled()
diff --git a/checkov/terraform/checks/resource/azure/RemoteDebggingNotEnabled.py b/checkov/terraform/checks/resource/azure/RemoteDebggingNotEnabled.py
new file mode 100644
index 0000000000..47fc9439a9
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/RemoteDebggingNotEnabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class RemoteDebuggingNotEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that remote debugging is not enabled for app services"
+ id = "CKV_AZURE_72"
+ supported_resources = ['azurerm_app_service']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return "remote_debugging_enabled"
+
+ def get_expected_value(self):
+ return False
+
+
+check = RemoteDebuggingNotEnabled()
diff --git a/checkov/terraform/checks/resource/azure/SQLServerAuditPolicyRetentionPeriod.py b/checkov/terraform/checks/resource/azure/SQLServerAuditPolicyRetentionPeriod.py
new file mode 100644
index 0000000000..03a4fa6f2c
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/SQLServerAuditPolicyRetentionPeriod.py
@@ -0,0 +1,25 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_int
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class SQLServerAuditPolicyRetentionPeriod(BaseResourceCheck):
+ def __init__(self):
+ name = "Specifies a retention period of less than 90 days."
+ id = "CKV_AZURE_46"
+ supported_resources = ['azurerm_mssql_database_extended_auditing_policy']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'retention_in_days' in conf:
+ retention=force_int(conf['retention_in_days'][0])
+ if retention:
+ if retention < 90:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = SQLServerAuditPolicyRetentionPeriod()
diff --git a/checkov/terraform/checks/resource/azure/SQLServerNoPublicAccess.py b/checkov/terraform/checks/resource/azure/SQLServerNoPublicAccess.py
index 65f75df3b1..f8b7450183 100644
--- a/checkov/terraform/checks/resource/azure/SQLServerNoPublicAccess.py
+++ b/checkov/terraform/checks/resource/azure/SQLServerNoPublicAccess.py
@@ -2,7 +2,7 @@
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
import re
-PORT_RANGE = re.compile('\d+-\d+')
+PORT_RANGE = re.compile(r'\d+-\d+')
class SQLServerNoPublicAccess(BaseResourceCheck):
@@ -15,7 +15,8 @@ def __init__(self):
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
- if 'start_ip_address' in conf and conf['start_ip_address'][0] in ['0.0.0.0', '0.0.0.0/0']: # nosec
+ if ('start_ip_address' in conf and conf['start_ip_address'][0] in ['0.0.0.0', '0.0.0.0/0'] and # nosec
+ 'end_ip_address' in conf and conf['end_ip_address'][0] == '255.255.255.255'):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/azure/SQLServerPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/SQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..78403b7b21
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/SQLServerPublicAccessDisabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class SQLServerHasPublicAccessDisabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that SQL server disables public network access"
+ id = "CKV_AZURE_113"
+ supported_resources = ['azurerm_mssql_server']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.FAILED)
+
+ def get_inspected_key(self):
+ return 'public_network_access_enabled'
+
+ def get_expected_value(self):
+ return False
+
+
+check = SQLServerHasPublicAccessDisabled()
diff --git a/checkov/terraform/checks/resource/azure/SecretContentType.py b/checkov/terraform/checks/resource/azure/SecretContentType.py
new file mode 100644
index 0000000000..35d7792809
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/SecretContentType.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class SecretContentType(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that key vault secrets have \"content_type\" set"
+ id = "CKV_AZURE_114"
+ supported_resources = ['azurerm_key_vault_secret']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'content_type'
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = SecretContentType()
diff --git a/checkov/terraform/checks/resource/azure/SecurityCenterContactEmails.py b/checkov/terraform/checks/resource/azure/SecurityCenterContactEmails.py
new file mode 100644
index 0000000000..4602568225
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/SecurityCenterContactEmails.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.consts import ANY_VALUE
+
+
+class SecurityCenterContactEmails(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that 'Security contact emails' is set"
+ id = "CKV_AZURE_131"
+ supported_resources = ['azurerm_security_center_contact']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "email"
+
+ def get_expected_value(self):
+ return ANY_VALUE
+
+
+check = SecurityCenterContactEmails()
diff --git a/checkov/terraform/checks/resource/azure/StorageAccountAzureServicesAccessEnabled.py b/checkov/terraform/checks/resource/azure/StorageAccountAzureServicesAccessEnabled.py
index e15a7d3ac1..807194bef5 100644
--- a/checkov/terraform/checks/resource/azure/StorageAccountAzureServicesAccessEnabled.py
+++ b/checkov/terraform/checks/resource/azure/StorageAccountAzureServicesAccessEnabled.py
@@ -19,7 +19,7 @@ def scan_resource_conf(self, conf):
# have access --> Pass
if network_conf[0]['default_action'][0] == 'Allow':
return CheckResult.PASSED
- elif 'bypass' in network_conf[0]:
+ elif network_conf[0].get('bypass'):
if 'AzureServices' in network_conf[0]['bypass'][0]:
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/azure/StorageAccountDisablePublicAccess.py b/checkov/terraform/checks/resource/azure/StorageAccountDisablePublicAccess.py
new file mode 100644
index 0000000000..c0dae38146
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/StorageAccountDisablePublicAccess.py
@@ -0,0 +1,20 @@
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck
+
+
+class StorageAccountDisablePublicAccess(BaseResourceNegativeValueCheck):
+ def __init__(self):
+ name = "Ensure that Storage accounts disallow public access"
+ id = "CKV_AZURE_59"
+ supported_resources = ['azurerm_storage_account']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "allow_blob_public_access"
+
+ def get_forbidden_values(self):
+ return [True]
+
+check = StorageAccountDisablePublicAccess()
diff --git a/checkov/terraform/checks/resource/azure/StorageAccountEnablesSecureTransfer.py b/checkov/terraform/checks/resource/azure/StorageAccountEnablesSecureTransfer.py
new file mode 100644
index 0000000000..f662c815b1
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/StorageAccountEnablesSecureTransfer.py
@@ -0,0 +1,18 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class StorageAccountEnablesSecureTransfer(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that storage account enables secure transfer"
+ id = "CKV_AZURE_60"
+ supported_resources = ['azurerm_storage_account']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.PASSED)
+
+ def get_inspected_key(self):
+ return "enable_https_traffic_only"
+
+
+check = StorageAccountEnablesSecureTransfer()
diff --git a/checkov/terraform/checks/resource/azure/StorageAccountName.py b/checkov/terraform/checks/resource/azure/StorageAccountName.py
index 834242ea2f..fa3b5a8de2 100644
--- a/checkov/terraform/checks/resource/azure/StorageAccountName.py
+++ b/checkov/terraform/checks/resource/azure/StorageAccountName.py
@@ -21,7 +21,7 @@ def scan_resource_conf(self, conf):
:param conf: azurerm_storage_account configuration
:return:
"""
- return CheckResult.PASSED if conf.get('name') and re.match(STO_NAME_REGEX, conf['name'][0]) else CheckResult.FAILED
+ return CheckResult.PASSED if conf.get('name') and re.findall(STO_NAME_REGEX, str(conf['name'][0])) else CheckResult.FAILED
check = StorageAccountName()
diff --git a/checkov/terraform/checks/resource/azure/StorageSyncPublicAccessDisabled.py b/checkov/terraform/checks/resource/azure/StorageSyncPublicAccessDisabled.py
new file mode 100644
index 0000000000..2fc1262305
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/StorageSyncPublicAccessDisabled.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class NetworkInterfaceEnableIPForwarding(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure File Sync disables public network access"
+ id = "CKV_AZURE_64"
+ supported_resources = ['azurerm_storage_sync']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources,
+ missing_block_result=CheckResult.FAILED)
+
+ def get_inspected_key(self):
+ return 'incoming_traffic_policy'
+
+ def get_expected_value(self):
+ return 'AllowVirtualNetworksOnly'
+
+
+check = NetworkInterfaceEnableIPForwarding()
diff --git a/checkov/terraform/checks/resource/azure/SynapseWorkspaceEnablesManagedVirtualNetworks.py b/checkov/terraform/checks/resource/azure/SynapseWorkspaceEnablesManagedVirtualNetworks.py
new file mode 100644
index 0000000000..7ae2af123f
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/SynapseWorkspaceEnablesManagedVirtualNetworks.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class SynapseWorkspaceEnablesManagedVirtualNetworks(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Azure Synapse workspaces enables managed virtual networks"
+ id = "CKV_AZURE_58"
+ supported_resources = ['azurerm_synapse_workspace']
+ categories = [CheckCategories.NETWORKING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'managed_virtual_network_enabled'
+
+
+check = SynapseWorkspaceEnablesManagedVirtualNetworks()
diff --git a/checkov/terraform/checks/resource/azure/VMEncryptionAtHostEnabled.py b/checkov/terraform/checks/resource/azure/VMEncryptionAtHostEnabled.py
new file mode 100644
index 0000000000..28ac3395c8
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/VMEncryptionAtHostEnabled.py
@@ -0,0 +1,17 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class VMEncryptionAtHostEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure that Virtual machine scale sets have encryption at host enabled"
+ id = "CKV_AZURE_97"
+ supported_resources = ['azurerm_linux_virtual_machine_scale_set', 'azurerm_windows_virtual_machine_scale_set']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'encryption_at_host_enabled'
+
+
+check = VMEncryptionAtHostEnabled()
diff --git a/checkov/terraform/checks/resource/azure/VMScaleSetsAutoOSImagePatchingEnabled.py b/checkov/terraform/checks/resource/azure/VMScaleSetsAutoOSImagePatchingEnabled.py
new file mode 100644
index 0000000000..9a26ced5ca
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/VMScaleSetsAutoOSImagePatchingEnabled.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class VMScaleSetsAutoOSImagePatchingEnabled(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that automatic OS image patching is enabled for Virtual Machine Scale Sets"
+ id = "CKV_AZURE_95"
+ supported_resources = ['azurerm_virtual_machine_scale_set']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'automatic_os_upgrade' in conf and conf['automatic_os_upgrade'][0]:
+ if 'os_profile_windows_config' in conf and conf['os_profile_windows_config'][0]:
+ os_profile_windows_config = conf['os_profile_windows_config'][0]
+ if 'enable_automatic_upgrades' in os_profile_windows_config \
+ and os_profile_windows_config['enable_automatic_upgrades'][0]:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+check = VMScaleSetsAutoOSImagePatchingEnabled()
diff --git a/checkov/terraform/checks/resource/azure/VMStorageOsDisk.py b/checkov/terraform/checks/resource/azure/VMStorageOsDisk.py
new file mode 100644
index 0000000000..a5bbad2e97
--- /dev/null
+++ b/checkov/terraform/checks/resource/azure/VMStorageOsDisk.py
@@ -0,0 +1,23 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
+
+class VMStorageOsDisk(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure that Virtual Machines use managed disks"
+ id = "CKV_AZURE_92"
+ supported_resources = ['azurerm_linux_virtual_machine', 'azurerm_windows_virtual_machine']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ storage_os_disk = conf.get('storage_os_disk')
+ storage_data_disk = conf.get('storage_data_disk')
+ if storage_os_disk and 'vhd_uri' in storage_os_disk[0]:
+ return CheckResult.FAILED
+ if storage_data_disk and 'vhd_uri' in storage_data_disk[0]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+check = VMStorageOsDisk()
diff --git a/checkov/terraform/checks/resource/base_registry.py b/checkov/terraform/checks/resource/base_registry.py
index 1a19ce3b27..4679f4b732 100644
--- a/checkov/terraform/checks/resource/base_registry.py
+++ b/checkov/terraform/checks/resource/base_registry.py
@@ -1,12 +1,10 @@
+from typing import Dict, Any, Tuple
+
from checkov.common.checks.base_check_registry import BaseCheckRegistry
class Registry(BaseCheckRegistry):
-
- def __init__(self):
- super().__init__()
-
- def extract_entity_details(self, entity):
+ def extract_entity_details(self, entity: Dict[str, Any]) -> Tuple[str, str, Dict[str, Any]]:
resource_type = list(entity.keys())[0]
resource_name = list(list(entity.values())[0].keys())[0]
resource_object = entity[resource_type]
diff --git a/checkov/terraform/checks/resource/base_resource_check.py b/checkov/terraform/checks/resource/base_resource_check.py
index 1775c09d49..a71b358efd 100644
--- a/checkov/terraform/checks/resource/base_resource_check.py
+++ b/checkov/terraform/checks/resource/base_resource_check.py
@@ -1,34 +1,41 @@
+import json
from abc import abstractmethod
+from typing import Dict, List, Any
from checkov.common.checks.base_check import BaseCheck
-from checkov.common.multi_signature import multi_signature
+from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.registry import resource_registry
class BaseResourceCheck(BaseCheck):
- def __init__(self, name, id, categories, supported_resources):
- super().__init__(name=name, id=id, categories=categories, supported_entities=supported_resources,
- block_type="resource")
+ def __init__(self, name: str, id: str, categories: List[CheckCategories], supported_resources: List[str]) -> None:
+ super().__init__(
+ name=name, id=id, categories=categories, supported_entities=supported_resources, block_type="resource"
+ )
self.supported_resources = supported_resources
resource_registry.register(self)
- def scan_entity_conf(self, conf, entity_type):
- return self.scan_resource_conf(conf, entity_type)
+ def scan_entity_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
+ if conf.get("count") == [0]:
+ return CheckResult.UNKNOWN
+
+ self.handle_dynamic_values(conf)
+ return self.scan_resource_conf(conf)
- @multi_signature()
@abstractmethod
- def scan_resource_conf(self, conf, entity_type):
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
"""
self.evaluated_keys should be set with a JSONPath of the attribute inspected.
If not relevant it should be set to an empty array so the previous check's value gets overridden in the report.
"""
raise NotImplementedError()
- @classmethod
- @scan_resource_conf.add_signature(args=["self", "conf"])
- def _scan_resource_conf_self_conf(cls, wrapped):
- def wrapper(self, conf, entity_type=None):
- # keep default argument for entity_type so old code, that doesn't set it, will work.
- return wrapped(self, conf)
-
- return wrapper
+ def handle_dynamic_values(self, conf: Dict[str, List[Any]]) -> None:
+ for dynamic_element in conf.get("dynamic", {}):
+ if isinstance(dynamic_element, str):
+ try:
+ dynamic_element = json.loads(dynamic_element)
+ except Exception:
+ dynamic_element = {}
+ for element_name in dynamic_element.keys():
+ conf[element_name] = dynamic_element[element_name].get("content", [])
diff --git a/checkov/terraform/checks/resource/base_resource_negative_value_check.py b/checkov/terraform/checks/resource/base_resource_negative_value_check.py
index d48e73f28a..2c050626d2 100644
--- a/checkov/terraform/checks/resource/base_resource_negative_value_check.py
+++ b/checkov/terraform/checks/resource/base_resource_negative_value_check.py
@@ -1,18 +1,30 @@
from abc import abstractmethod
+from typing import List, Dict, Any, Optional
import dpath
from checkov.common.models.consts import ANY_VALUE
-from checkov.common.models.enums import CheckResult
+from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.common.util.type_forcers import force_list
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.terraform.graph_builder.utils import get_referenced_vertices_in_value
class BaseResourceNegativeValueCheck(BaseResourceCheck):
- def __init__(self, name, id, categories, supported_resources):
+ def __init__(
+ self,
+ name: str,
+ id: str,
+ categories: List[CheckCategories],
+ supported_resources: List[str],
+ missing_attribute_result: CheckResult = CheckResult.PASSED,
+ ) -> None:
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+ self.missing_attribute_result = missing_attribute_result
+
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ self.handle_dynamic_values(conf)
- def scan_resource_conf(self, conf):
excluded_key = self.get_excluded_key()
if excluded_key is not None:
if dpath.search(conf, excluded_key) != {}:
@@ -28,37 +40,42 @@ def scan_resource_conf(self, conf):
value = dpath.get(conf, inspected_key)
if isinstance(value, list) and len(value) == 1:
value = value[0]
+ if get_referenced_vertices_in_value(value=value, aliases={}, resources_types=[]):
+ # we don't provide resources_types as we want to stay provider agnostic
+ return CheckResult.UNKNOWN
if value in bad_values or ANY_VALUE in bad_values:
return CheckResult.FAILED
+ else:
+ return CheckResult.PASSED
- return CheckResult.PASSED
+ return self.missing_attribute_result
@abstractmethod
- def get_inspected_key(self):
+ def get_inspected_key(self) -> str:
"""
:return: JSONPath syntax path of the checked attribute
"""
raise NotImplementedError()
@abstractmethod
- def get_forbidden_values(self):
+ def get_forbidden_values(self) -> List[Any]:
"""
Returns a list of vulnerable values for the inspected key, governed by provider best practices
"""
raise NotImplementedError()
- def get_excluded_key(self):
+ def get_excluded_key(self) -> Optional[str]:
"""
:return: JSONPath syntax path of the an attribute that provides exclusion condition for the inspected key
"""
return None
- def check_excluded_condition(self, value):
+ def check_excluded_condition(self, value: str) -> bool:
"""
:param: value: value for excluded_key
:return: True if the value should exclude the check from failing if the inspected key has a bad value
"""
return False
- def get_evaluated_keys(self):
+ def get_evaluated_keys(self) -> List[str]:
return force_list(self.get_inspected_key())
diff --git a/checkov/terraform/checks/resource/base_resource_value_check.py b/checkov/terraform/checks/resource/base_resource_value_check.py
index e9085c9bd2..334168c870 100644
--- a/checkov/terraform/checks/resource/base_resource_value_check.py
+++ b/checkov/terraform/checks/resource/base_resource_value_check.py
@@ -1,36 +1,51 @@
from abc import abstractmethod
+from typing import List, Dict, Any
+
import dpath.util
import re
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.models.enums import CheckResult
+from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.common.models.consts import ANY_VALUE
from checkov.common.util.type_forcers import force_list
+from checkov.terraform.graph_builder.utils import get_referenced_vertices_in_value
+from checkov.terraform.parser_utils import find_var_blocks
-VARIABLE_DEPENDANT_REGEX = r'(?:local|var)\.[^\s]+'
class BaseResourceValueCheck(BaseResourceCheck):
- def __init__(self, name, id, categories, supported_resources, missing_block_result=CheckResult.FAILED):
+ def __init__(
+ self,
+ name: str,
+ id: str,
+ categories: List[CheckCategories],
+ supported_resources: List[str],
+ missing_block_result: CheckResult = CheckResult.FAILED,
+ ) -> None:
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
self.missing_block_result = missing_block_result
@staticmethod
- def _filter_key_path(path):
+ def _filter_key_path(path: str) -> List[str]:
"""
Filter an attribute path to contain only named attributes by dropping array indices from the path)
:param path: valid JSONPath of an attribute
:return: List of named attributes with respect to the input JSONPath order
"""
- return [x for x in path.split("/") if not re.search(r'^\[?\d+\]?$', x)]
+ return [x for x in path.split("/") if not re.search(r"^\[?\d+]?$", x)]
@staticmethod
- def _is_variable_dependant(value):
- if isinstance(value, str) and re.match(VARIABLE_DEPENDANT_REGEX, value):
+ def _is_variable_dependant(value: Any) -> bool:
+ if not isinstance(value, str):
+ return False
+ if "${" not in value:
+ return False
+
+ if find_var_blocks(value):
return True
return False
@staticmethod
- def _is_nesting_key(inspected_attributes, key):
+ def _is_nesting_key(inspected_attributes: List[str], key: List[str]) -> bool:
"""
Resolves whether a key is a subset of the inspected nesting attributes
:param inspected_attributes: list of nesting attributes
@@ -39,28 +54,32 @@ def _is_nesting_key(inspected_attributes, key):
"""
return any([x in key for x in inspected_attributes])
- def scan_resource_conf(self, conf):
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ self.handle_dynamic_values(conf)
inspected_key = self.get_inspected_key()
expected_values = self.get_expected_values()
if dpath.search(conf, inspected_key) != {}:
# Inspected key exists
- if ANY_VALUE in expected_values:
- # Key is found on the configuration - if it accepts any value, the check is PASSED
- return CheckResult.PASSED
value = dpath.get(conf, inspected_key)
if isinstance(value, list) and len(value) == 1:
value = value[0]
+ if ANY_VALUE in expected_values and value is not None:
+ # Key is found on the configuration - if it accepts any value, the check is PASSED
+ return CheckResult.PASSED
if self._is_variable_dependant(value):
# If the tested attribute is variable-dependant, then result is PASSED
return CheckResult.PASSED
if value in expected_values:
return CheckResult.PASSED
+ if get_referenced_vertices_in_value(value=value, aliases={}, resources_types=[]):
+ # we don't provide resources_types as we want to stay provider agnostic
+ return CheckResult.UNKNOWN
return CheckResult.FAILED
else:
# Look for the configuration in a bottom-up fashion
inspected_attributes = self._filter_key_path(inspected_key)
for attribute in reversed(inspected_attributes):
- for sub_key, sub_conf in dpath.search(conf, f'**/{attribute}', yielded=True):
+ for sub_key, sub_conf in dpath.search(conf, f"**/{attribute}", yielded=True):
filtered_sub_key = self._filter_key_path(sub_key)
# Only proceed with check if full path for key is similar - not partial match
if inspected_attributes == filtered_sub_key:
@@ -76,13 +95,13 @@ def scan_resource_conf(self, conf):
return self.missing_block_result
@abstractmethod
- def get_inspected_key(self):
+ def get_inspected_key(self) -> str:
"""
:return: JSONPath syntax path of the checked attribute
"""
raise NotImplementedError()
- def get_expected_values(self):
+ def get_expected_values(self) -> List[Any]:
"""
Override the method with the list of acceptable values if the check has more than one possible expected value, given
the inspected key
@@ -90,11 +109,11 @@ def get_expected_values(self):
"""
return [self.get_expected_value()]
- def get_expected_value(self):
+ def get_expected_value(self) -> Any:
"""
Returns the default expected value, governed by provider best practices
"""
return True
- def get_evaluated_keys(self):
+ def get_evaluated_keys(self) -> List[str]:
return force_list(self.get_inspected_key())
diff --git a/checkov/terraform/checks/resource/gcp/AbsGoogleComputeFirewallUnrestrictedIngress.py b/checkov/terraform/checks/resource/gcp/AbsGoogleComputeFirewallUnrestrictedIngress.py
index 080382e275..865f203ead 100644
--- a/checkov/terraform/checks/resource/gcp/AbsGoogleComputeFirewallUnrestrictedIngress.py
+++ b/checkov/terraform/checks/resource/gcp/AbsGoogleComputeFirewallUnrestrictedIngress.py
@@ -24,7 +24,11 @@ def scan_resource_conf(self, conf):
return CheckResult.PASSED
def _is_port_in_range(self, ports_list):
- for port_range in ports_list[0]:
+ if len(ports_list) == 0:
+ return False
+ if isinstance(ports_list[0], list):
+ ports_list = ports_list[0]
+ for port_range in ports_list:
port = force_int(port_range)
if port and self.port == port:
return True
diff --git a/checkov/terraform/checks/resource/gcp/AbsGoogleIAMMemberDefaultServiceAccount.py b/checkov/terraform/checks/resource/gcp/AbsGoogleIAMMemberDefaultServiceAccount.py
index 707cb0b822..0274897209 100644
--- a/checkov/terraform/checks/resource/gcp/AbsGoogleIAMMemberDefaultServiceAccount.py
+++ b/checkov/terraform/checks/resource/gcp/AbsGoogleIAMMemberDefaultServiceAccount.py
@@ -5,7 +5,7 @@
# Default Compute -compute@developer.gserviceaccount.com
# Default App Spot @appspot.gserviceaccount.com
-DEFAULT_SA = re.compile(".*-compute@developer\.gserviceaccount\.com|.*@appspot\.gserviceaccount\.com")
+DEFAULT_SA = re.compile(r".*-compute@developer\.gserviceaccount\.com|.*@appspot\.gserviceaccount\.com")
class AbsGoogleIAMMemberDefaultServiceAccount(BaseResourceCheck):
@@ -14,6 +14,6 @@ def __init__(self, name, id, categories, supported_resources):
def scan_resource_conf(self, conf):
members_conf = conf['members'][0] if 'members' in conf else conf.get('member', [])
- if any(re.match(DEFAULT_SA, member) for member in members_conf):
+ if any(re.match(DEFAULT_SA, str(member)) for member in members_conf):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/gcp/AbsGoogleImpersonationRoles.py b/checkov/terraform/checks/resource/gcp/AbsGoogleImpersonationRoles.py
index 94e01b2597..8b59bed0d7 100644
--- a/checkov/terraform/checks/resource/gcp/AbsGoogleImpersonationRoles.py
+++ b/checkov/terraform/checks/resource/gcp/AbsGoogleImpersonationRoles.py
@@ -23,9 +23,6 @@
class AbsGoogleImpersonationRoles(BaseResourceCheck):
- def __init__(self, name, id, categories, supported_resources):
- super().__init__(name, id, categories, supported_resources)
-
def scan_resource_conf(self, conf):
if 'role' in conf and conf['role'][0] in IMPERSONATION_ROLES:
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/gcp/CloudStorageLogging.py b/checkov/terraform/checks/resource/gcp/CloudStorageLogging.py
new file mode 100644
index 0000000000..1ab6943954
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/CloudStorageLogging.py
@@ -0,0 +1,29 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class CloudStorageLogging(BaseResourceCheck):
+ def __init__(self):
+ name = "Bucket should log access"
+ id = "CKV_GCP_62"
+ supported_resources = ['google_storage_bucket']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ #check for logging
+ if 'logging' in conf:
+ if conf['logging'][0]:
+ log_bucket_name = conf['logging'][0]['log_bucket']
+ if log_bucket_name != None:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.FAILED
+
+check = CloudStorageLogging()
diff --git a/checkov/terraform/checks/resource/gcp/CloudStorageSelfLogging.py b/checkov/terraform/checks/resource/gcp/CloudStorageSelfLogging.py
new file mode 100644
index 0000000000..c9b814218d
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/CloudStorageSelfLogging.py
@@ -0,0 +1,28 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class CloudStorageSelfLogging(BaseResourceCheck):
+ def __init__(self):
+ name = "Bucket should not log to itself"
+ id = "CKV_GCP_63"
+ supported_resources = ['google_storage_bucket']
+ categories = [CheckCategories.LOGGING]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ bucket_name = conf['name']
+ #check for logging
+ if 'logging' in conf:
+ if conf['logging'][0]:
+ log_bucket_name = conf['logging'][0]['log_bucket']
+ if log_bucket_name != bucket_name:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ return CheckResult.FAILED
+ return CheckResult.FAILED
+ return CheckResult.UNKNOWN
+
+check = CloudStorageSelfLogging()
diff --git a/checkov/terraform/checks/resource/gcp/GKEBinaryAuthorization.py b/checkov/terraform/checks/resource/gcp/GKEBinaryAuthorization.py
new file mode 100644
index 0000000000..54567020af
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEBinaryAuthorization.py
@@ -0,0 +1,16 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class GKEBinaryAuthorization(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure use of Binary Authorization"
+ id = "CKV_GCP_66"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'enable_binary_authorization'
+
+check = GKEBinaryAuthorization()
diff --git a/checkov/terraform/checks/resource/gcp/GKEEnableShieldedNodes.py b/checkov/terraform/checks/resource/gcp/GKEEnableShieldedNodes.py
new file mode 100644
index 0000000000..8a48637083
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEEnableShieldedNodes.py
@@ -0,0 +1,18 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+class GKEEnableShieldedNodes(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Shielded GKE Nodes are Enabled"
+ id = "CKV_GCP_71"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'enable_shielded_nodes'
+
+ def get_expected_value(self):
+ return True
+
+check = GKEEnableShieldedNodes()
diff --git a/checkov/terraform/checks/resource/gcp/GKEEnableVPCFlowLogs.py b/checkov/terraform/checks/resource/gcp/GKEEnableVPCFlowLogs.py
new file mode 100644
index 0000000000..68c7b7d78e
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEEnableVPCFlowLogs.py
@@ -0,0 +1,19 @@
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+class GKEEnableVPCFlowLogs(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Enable VPC Flow Logs and Intranode Visibility"
+ id = "CKV_GCP_61"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'enable_intranode_visibility'
+
+ def get_expected_values(self):
+ return [True]
+
+
+check = GKEEnableVPCFlowLogs()
diff --git a/checkov/terraform/checks/resource/gcp/GKEEnsureIntegrityMonitoring.py b/checkov/terraform/checks/resource/gcp/GKEEnsureIntegrityMonitoring.py
new file mode 100644
index 0000000000..a1b43388ec
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEEnsureIntegrityMonitoring.py
@@ -0,0 +1,30 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class GKEEnsureIntegrityMonitoring(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled"
+ check_id = "CKV_GCP_72"
+ supported_resources = ['google_container_cluster', 'google_container_node_pool']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=check_id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'node_config' in conf.keys():
+ node = conf["node_config"][0]
+ if isinstance(node, dict) and 'shielded_instance_config' in node.keys():
+ monitor = node["shielded_instance_config"][0]
+ if monitor.get("enable_integrity_monitoring") == [True]:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ # as default is true this is a pass
+ return CheckResult.PASSED
+ else:
+ # no config is valid it could be in the the node_pool
+ return CheckResult.UNKNOWN
+
+
+check = GKEEnsureIntegrityMonitoring()
diff --git a/checkov/terraform/checks/resource/gcp/GKEKubernetesRBACGoogleGroups.py b/checkov/terraform/checks/resource/gcp/GKEKubernetesRBACGoogleGroups.py
new file mode 100644
index 0000000000..3283e5fb22
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEKubernetesRBACGoogleGroups.py
@@ -0,0 +1,21 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class GKEKubernetesRBACGoogleGroups(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Manage Kubernetes RBAC users with Google Groups for GKE"
+ id = "CKV_GCP_65"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'authenticator_groups_config/[0]/security_group'
+
+ def get_expected_values(self):
+ return [ANY_VALUE]
+
+
+check = GKEKubernetesRBACGoogleGroups()
diff --git a/checkov/terraform/checks/resource/gcp/GKELegacyInstanceMetadataDisabled.py b/checkov/terraform/checks/resource/gcp/GKELegacyInstanceMetadataDisabled.py
new file mode 100644
index 0000000000..b78250f5d1
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKELegacyInstanceMetadataDisabled.py
@@ -0,0 +1,36 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.common.util.type_forcers import force_float
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class GKELegacyInstanceMetadataDisabled(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure legacy Compute Engine instance metadata APIs are Disabled"
+ id = "CKV_GCP_67"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ looks for min_master_version =1.12 which ensures that legacy metadata endpoints are disabled
+ https://www.terraform.io/docs/providers/google/r/compute_ssl_policy.html
+ :param conf: google_container_cluster configuration
+ :return:
+ """
+ if 'min_master_version' in conf:
+ min_master_version = force_float(conf.get('min_master_version')[0])
+ if min_master_version and min_master_version >= 1.12:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+ def get_inspected_key(self):
+ return 'min_master_version'
+
+ def get_expected_value(self):
+ return "1.12"
+
+
+check = GKELegacyInstanceMetadataDisabled()
diff --git a/checkov/terraform/checks/resource/gcp/GKEMetadataServerisEnabled.py b/checkov/terraform/checks/resource/gcp/GKEMetadataServerisEnabled.py
new file mode 100644
index 0000000000..aba8ead263
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEMetadataServerisEnabled.py
@@ -0,0 +1,20 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class GKEMetadataServerisEnabled(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure the GKE Metadata Server is Enabled"
+ id = "CKV_GCP_69"
+ supported_resources = ['google_container_cluster','google_container_node_pool']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'node_config/[0]/workload_metadata_config/[0]/node_metadata'
+
+ def get_expected_value(self):
+ return "GKE_METADATA_SERVER"
+
+
+check = GKEMetadataServerisEnabled()
diff --git a/checkov/terraform/checks/resource/gcp/GoogleStorageBucketEncryption.py b/checkov/terraform/checks/resource/gcp/GKEPrivateNodes.py
similarity index 55%
rename from checkov/terraform/checks/resource/gcp/GoogleStorageBucketEncryption.py
rename to checkov/terraform/checks/resource/gcp/GKEPrivateNodes.py
index 69eb6864dd..4397bdc80c 100644
--- a/checkov/terraform/checks/resource/gcp/GoogleStorageBucketEncryption.py
+++ b/checkov/terraform/checks/resource/gcp/GKEPrivateNodes.py
@@ -1,21 +1,21 @@
-from checkov.common.models.consts import ANY_VALUE
-from checkov.common.models.enums import CheckCategories
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
-class GoogleStorageBucketEncryption(BaseResourceValueCheck):
+class GKEPrivateNodes(BaseResourceValueCheck):
def __init__(self):
- name = "Ensure Google storage bucket have encryption enabled"
- id = "CKV_GCP_5"
- supported_resources = ['google_storage_bucket']
- categories = [CheckCategories.ENCRYPTION]
+ name = "Ensure clusters are created with Private Nodes"
+ id = "CKV_GCP_64"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def get_inspected_key(self):
- return 'encryption/[0]/default_kms_key_name'
+ return 'private_cluster_config'
def get_expected_values(self):
return [ANY_VALUE]
-check = GoogleStorageBucketEncryption()
+check = GKEPrivateNodes()
diff --git a/checkov/terraform/checks/resource/gcp/GKEReleaseChannel.py b/checkov/terraform/checks/resource/gcp/GKEReleaseChannel.py
new file mode 100644
index 0000000000..64c64b6d59
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKEReleaseChannel.py
@@ -0,0 +1,21 @@
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+from checkov.common.models.consts import ANY_VALUE
+
+
+class ReleaseChannel(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure the GKE Release Channel is set"
+ id = "CKV_GCP_70"
+ supported_resources = ['google_container_cluster']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return 'release_channel/[0]/channel'
+
+ def get_expected_values(self):
+ return [ANY_VALUE]
+
+
+check = ReleaseChannel()
diff --git a/checkov/terraform/checks/resource/gcp/GKESecureBootforShieldedNodes.py b/checkov/terraform/checks/resource/gcp/GKESecureBootforShieldedNodes.py
new file mode 100644
index 0000000000..06aa7bca02
--- /dev/null
+++ b/checkov/terraform/checks/resource/gcp/GKESecureBootforShieldedNodes.py
@@ -0,0 +1,35 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class GKESecureBootforShieldedNodes(BaseResourceValueCheck):
+ def __init__(self):
+ name = "Ensure Secure Boot for Shielded GKE Nodes is Enabled"
+ id = "CKV_GCP_68"
+ supported_resources = ['google_container_cluster', 'google_container_node_pool']
+ categories = [CheckCategories.KUBERNETES]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if 'node_config' in conf.keys():
+ node = conf["node_config"][0]
+ if isinstance(node, dict) and 'shielded_instance_config' in node:
+ monitor = node["shielded_instance_config"][0]
+ if monitor.get("enable_secure_boot", None) == [True]:
+ return CheckResult.PASSED
+ else:
+ return CheckResult.FAILED
+ else:
+ # as default is true this is a pass
+ return CheckResult.FAILED
+ else:
+ return CheckResult.UNKNOWN
+
+ def get_inspected_key(self):
+ return 'node_config/[0]/shielded_instance_config/[0]/enable_secure_boot'
+
+ def get_expected_values(self):
+ return [True]
+
+
+check = GKESecureBootforShieldedNodes()
diff --git a/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccount.py b/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccount.py
index 8716d1c24a..8b1e79ecda 100644
--- a/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccount.py
+++ b/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccount.py
@@ -2,7 +2,7 @@
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
import re
-DEFAULT_SERVICE_ACCOUNT = re.compile('\d+-compute@developer\.gserviceaccount\.com')
+DEFAULT_SERVICE_ACCOUNT = re.compile(r'\d+-compute@developer\.gserviceaccount\.com')
class GoogleComputeDefaultServiceAccount(BaseResourceCheck):
@@ -24,7 +24,7 @@ def scan_resource_conf(self, conf):
if 'email' in conf['service_account'][0]:
if not re.match(DEFAULT_SERVICE_ACCOUNT, conf['service_account'][0]['email'][0]):
return CheckResult.PASSED
- if conf['name'][0].startswith('gke-'):
+ if 'name' in conf and conf['name'][0].startswith('gke-'):
return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccountFullAccess.py b/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccountFullAccess.py
index cb7746be57..b9c13fb3c7 100644
--- a/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccountFullAccess.py
+++ b/checkov/terraform/checks/resource/gcp/GoogleComputeDefaultServiceAccountFullAccess.py
@@ -2,7 +2,7 @@
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
import re
-DEFAULT_SERVICE_ACCOUNT = re.compile('\d+-compute@developer\.gserviceaccount\.com')
+DEFAULT_SERVICE_ACCOUNT = re.compile(r'\d+-compute@developer\.gserviceaccount\.com')
FULL_ACCESS_API = 'https://www.googleapis.com/auth/cloud-platform'
@@ -22,7 +22,7 @@ def scan_resource_conf(self, conf):
:param conf: google_compute_instance configuration
:return:
"""
- if conf['name'][0].startswith('gke-'):
+ if 'name' in conf and conf['name'][0].startswith('gke-'):
return CheckResult.PASSED
if 'service_account' in conf.keys():
service_account_conf = conf['service_account'][0]
diff --git a/checkov/terraform/checks/resource/gcp/GoogleProjectAdminServiceAccount.py b/checkov/terraform/checks/resource/gcp/GoogleProjectAdminServiceAccount.py
index cd8de93342..d607823708 100644
--- a/checkov/terraform/checks/resource/gcp/GoogleProjectAdminServiceAccount.py
+++ b/checkov/terraform/checks/resource/gcp/GoogleProjectAdminServiceAccount.py
@@ -2,7 +2,7 @@
from checkov.common.models.enums import CheckResult, CheckCategories
import re
-USER_MANAGED_SERVICE_ACCOUNT = re.compile ('.*@.*\.iam\.gserviceaccount\.com$')
+USER_MANAGED_SERVICE_ACCOUNT = re.compile (r'.*@.*\.iam\.gserviceaccount\.com$')
ADMIN_ROLE = re.compile ('.*(.*Admin|.*admin|editor|owner)')
@@ -16,8 +16,8 @@ def __init__(self):
def scan_resource_conf(self, conf):
if 'member' in conf.keys():
- if re.match(USER_MANAGED_SERVICE_ACCOUNT, conf['member'][0]):
- if re.match(ADMIN_ROLE, conf['role'][0]):
+ if re.match(USER_MANAGED_SERVICE_ACCOUNT, str(conf['member'][0])):
+ if re.match(ADMIN_ROLE, str(conf['role'][0])):
return CheckResult.FAILED
return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/github/PrivateRepo.py b/checkov/terraform/checks/resource/github/PrivateRepo.py
index a4250de838..c593f2e99e 100644
--- a/checkov/terraform/checks/resource/github/PrivateRepo.py
+++ b/checkov/terraform/checks/resource/github/PrivateRepo.py
@@ -1,21 +1,22 @@
+from typing import Dict, List, Any
+
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
+
class PrivateRepo(BaseResourceCheck):
- def __init__(self):
+ def __init__(self) -> None:
name = "Ensure Repository is Private"
id = "CKV_GIT_1"
- supported_resources = ['github_repository']
+ supported_resources = ["github_repository"]
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
- def scan_resource_conf(self, conf):
- if 'private' in conf:
- if conf['private'] == [True]:
- return CheckResult.PASSED
- elif 'visibility' in conf:
- if conf['visibility'] == ['private'] or conf['visibility'] == ['internal']:
- return CheckResult.PASSED
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ if conf.get("private") == [True]:
+ return CheckResult.PASSED
+ elif conf.get("visibility") in [["private"], ["internal"]]:
+ return CheckResult.PASSED
return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/github/__init__.py b/checkov/terraform/checks/resource/github/__init__.py
index 1cb91d4ca0..bfe83e75bf 100644
--- a/checkov/terraform/checks/resource/github/__init__.py
+++ b/checkov/terraform/checks/resource/github/__init__.py
@@ -1,4 +1,4 @@
-from os.path import dirname, basename, isfile, join
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
\ No newline at end of file
+from pathlib import Path
+
+modules = Path(__file__).parent.glob("*.py")
+__all__ = [f.stem for f in modules if f.is_file() and not f.stem == "__init__"]
diff --git a/checkov/terraform/checks/resource/linode/__init__.py b/checkov/terraform/checks/resource/linode/__init__.py
index 1cb91d4ca0..bfe83e75bf 100644
--- a/checkov/terraform/checks/resource/linode/__init__.py
+++ b/checkov/terraform/checks/resource/linode/__init__.py
@@ -1,4 +1,4 @@
-from os.path import dirname, basename, isfile, join
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
\ No newline at end of file
+from pathlib import Path
+
+modules = Path(__file__).parent.glob("*.py")
+__all__ = [f.stem for f in modules if f.is_file() and not f.stem == "__init__"]
diff --git a/checkov/terraform/checks/resource/linode/authorized_keys.py b/checkov/terraform/checks/resource/linode/authorized_keys.py
index 48690ece22..710b68b098 100644
--- a/checkov/terraform/checks/resource/linode/authorized_keys.py
+++ b/checkov/terraform/checks/resource/linode/authorized_keys.py
@@ -1,19 +1,21 @@
+from typing import Dict, List, Any
+
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceCheck
-class authorized_keys(BaseResourceCheck):
- def __init__(self):
+
+class AuthorizedKeys(BaseResourceCheck):
+ def __init__(self) -> None:
name = "Ensure SSH key set in authorized_keys"
id = "CKV_LIN_2"
- supported_resources = ['linode_instance']
+ supported_resources = ["linode_instance"]
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
- def scan_resource_conf(self, conf):
- if 'authorized_keys' in conf:
- if conf['authorized_keys']:
- return CheckResult.PASSED
+ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+ if conf.get("authorized_keys"):
+ return CheckResult.PASSED
return CheckResult.FAILED
-check = authorized_keys()
\ No newline at end of file
+check = AuthorizedKeys()
diff --git a/checkov/terraform/checks/utils/dependency_path_handler.py b/checkov/terraform/checks/utils/dependency_path_handler.py
new file mode 100644
index 0000000000..82ddcdc572
--- /dev/null
+++ b/checkov/terraform/checks/utils/dependency_path_handler.py
@@ -0,0 +1,7 @@
+from typing import List
+
+PATH_SEPARATOR = "->"
+
+
+def unify_dependency_path(dependency_path: List[str]) -> str:
+ return PATH_SEPARATOR.join(dependency_path)
diff --git a/checkov/terraform/checks/utils/iam_terraform_document_to_policy_converter.py b/checkov/terraform/checks/utils/iam_terraform_document_to_policy_converter.py
new file mode 100644
index 0000000000..580c94dd16
--- /dev/null
+++ b/checkov/terraform/checks/utils/iam_terraform_document_to_policy_converter.py
@@ -0,0 +1,25 @@
+import copy
+from typing import Dict, List, Any
+
+
+def convert_terraform_conf_to_iam_policy(conf: Dict[str, List[Dict[str, Any]]]) -> Dict[str, List[Dict[str, Any]]]:
+ """
+ converts terraform parsed configuration to iam policy document
+ """
+ result = copy.deepcopy(conf)
+ if "statement" in result.keys():
+ result["Statement"] = result.pop("statement")
+ for statement in result["Statement"]:
+ if "actions" in statement:
+ statement["Action"] = statement.pop("actions")[0]
+ if "resources" in statement:
+ statement["Resource"] = statement.pop("resources")[0]
+ if "not_actions" in statement:
+ statement["NotAction"] = statement.pop("not_actions")[0]
+ if "not_resources" in statement:
+ statement["NotResource"] = statement.pop("not_resources")[0]
+ if "effect" in statement:
+ statement["Effect"] = statement.pop("effect")[0]
+ if "effect" not in statement and "Effect" not in statement:
+ statement["Effect"] = "Allow"
+ return result
diff --git a/checkov/terraform/context_parsers/base_parser.py b/checkov/terraform/context_parsers/base_parser.py
index f130ae9752..5fdfddeb4d 100644
--- a/checkov/terraform/context_parsers/base_parser.py
+++ b/checkov/terraform/context_parsers/base_parser.py
@@ -2,25 +2,30 @@
import re
from abc import ABC, abstractmethod
from itertools import islice
+from pathlib import Path
+from typing import List, Dict, Any, Tuple, Optional
import dpath.util
+from checkov.common.bridgecrew.platform_integration import bc_integration
from checkov.common.comment.enum import COMMENT_REGEX
from checkov.common.models.enums import ContextCategories
from checkov.terraform.context_parsers.registry import parser_registry
-from checkov.common.bridgecrew.platform_integration import bc_integration
-OPEN_CURLY = '{'
-CLOSE_CURLY = '}'
+OPEN_CURLY = "{"
+CLOSE_CURLY = "}"
class BaseContextParser(ABC):
definition_type = ""
tf_file = ""
- file_lines = []
- context = {}
+ tf_file_path: Optional[Path] = None
+ file_lines: List[Tuple[int, str]] = []
+ filtered_lines: List[Tuple[int, str]] = []
+ context: Dict[str, Any] = {}
- def __init__(self, definition_type):
+ def __init__(self, definition_type: str) -> None:
+ # bc_integration.setup_http_manager()
self.logger = logging.getLogger("{}".format(self.__module__))
if definition_type.upper() not in ContextCategories.__members__:
self.logger.error("Terraform context parser type not supported yet")
@@ -29,7 +34,7 @@ def __init__(self, definition_type):
parser_registry.register(self)
@abstractmethod
- def get_entity_context_path(self, entity_block):
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
"""
returns the entity's path in the context parser
:param entity_block: entity definition block
@@ -37,7 +42,7 @@ def get_entity_context_path(self, entity_block):
"""
raise NotImplementedError
- def _is_block_signature(self, line_num, line_tokens, entity_context_path):
+ def _is_block_signature(self, line_num: int, line_tokens: List[str], entity_context_path: List[str]) -> bool:
"""
Determine if the given tokenized line token is the entity signature line
:param line_num: The line number in the file
@@ -49,48 +54,77 @@ def _is_block_signature(self, line_num, line_tokens, entity_context_path):
return all(x in line_tokens for x in [block_type] + entity_context_path)
@staticmethod
- def _trim_whitespaces_linebreaks(text):
+ def _trim_whitespaces_linebreaks(text: str) -> str:
return text.strip()
- def _filter_file_lines(self):
+ def _filter_file_lines(self) -> List[Tuple[int, str]]:
parsed_file_lines = [(ind, self._trim_whitespaces_linebreaks(line)) for (ind, line) in self.file_lines]
self.filtered_lines = [(ind, line) for (ind, line) in parsed_file_lines if line]
return self.filtered_lines
- def _read_file_lines(self):
- with(open(self.tf_file, 'r')) as file:
+ def _read_file_lines(self) -> List[Tuple[int, str]]:
+ with (open(self.tf_file, "r")) as file:
file.seek(0)
- file_lines = [(ind + 1, line) for (ind, line) in
- list(enumerate(file.readlines()))]
+ file_lines = [(ind + 1, line) for (ind, line) in list(enumerate(file.readlines()))]
return file_lines
- def _collect_skip_comments(self, definition_blocks):
+ @staticmethod
+ def is_optional_comment_line(line: str) -> bool:
+ line_without_whitespace = "".join(line.split())
+ return 'checkov:skip=' in line_without_whitespace or 'bridgecrew:skip=' in line_without_whitespace
+
+ def _collect_skip_comments(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Collects checkov skip comments to all definition blocks
:param definition_blocks: parsed definition blocks
:return: context enriched with with skipped checks per skipped entity
"""
bc_id_mapping = bc_integration.get_id_mapping()
+ ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
parsed_file_lines = self.filtered_lines
- comments = [(line_num, {"id": re.search(COMMENT_REGEX, x).group(2),
- "suppress_comment": re.search(COMMENT_REGEX, x).group(3)[1:] if re.search(COMMENT_REGEX,
- x).group(3)
- else "No comment provided"}) for (line_num, x) in
- parsed_file_lines if re.search(COMMENT_REGEX, x)]
+ optional_comment_lines = [
+ line for line in parsed_file_lines
+ if self.is_optional_comment_line(line[1])
+ ]
+ comments = [
+ (
+ line_num,
+ {
+ "id": match.group(2),
+ "suppress_comment": match.group(3)[1:] if match.group(3) else "No comment provided",
+ },
+ )
+ for (line_num, x) in optional_comment_lines
+ for match in [re.search(COMMENT_REGEX, x)]
+ if match
+ ]
for entity_block in definition_blocks:
skipped_checks = []
entity_context_path = self.get_entity_context_path(entity_block)
- context_search = dpath.search(self.context, entity_context_path, yielded=True)
- for _, entity_context in context_search:
- for (skip_check_line_num, skip_check) in comments:
- if entity_context['start_line'] < skip_check_line_num < entity_context['end_line']:
- if bc_id_mapping and skip_check['id'] in bc_id_mapping:
- skip_check['id'] = bc_id_mapping[skip_check['id']]
- skipped_checks.append(skip_check)
- dpath.new(self.context, entity_context_path + ['skipped_checks'], skipped_checks)
+ entity_context = self.context
+ found = True
+ for k in entity_context_path:
+ if k in entity_context:
+ entity_context = entity_context[k]
+ else:
+ logging.warning(f'Failed to find context for {".".join(entity_context_path)}')
+ found = False
+ break
+ if not found:
+ continue
+ for (skip_check_line_num, skip_check) in comments:
+ if entity_context["start_line"] < skip_check_line_num < entity_context["end_line"]:
+ # No matter which ID was used to skip, save the pair of IDs in the appropriate fields
+ if bc_id_mapping and skip_check["id"] in bc_id_mapping:
+ skip_check["bc_id"] = skip_check["id"]
+ skip_check["id"] = bc_id_mapping[skip_check["id"]]
+ elif ckv_to_bc_id_mapping:
+ skip_check["bc_id"] = ckv_to_bc_id_mapping.get(skip_check["id"])
+ skipped_checks.append(skip_check)
+ dpath.new(self.context, entity_context_path + ["skipped_checks"], skipped_checks)
return self.context
- def _compute_definition_end_line(self, start_line_num):
+ def _compute_definition_end_line(self, start_line_num: int) -> int:
""" Given the code block's start line, compute the block's end line
:param start_line_num: code block's first line number (the signature line)
:return: the code block's last line number
@@ -109,13 +143,17 @@ def _compute_definition_end_line(self, start_line_num):
break
return end_line_num
- def run(self, tf_file, definition_blocks, collect_skip_comments=True):
+ def run(
+ self, tf_file: str, definition_blocks: List[Dict[str, Any]], collect_skip_comments: bool = True
+ ) -> Dict[str, Any]:
# TF files for loaded modules have this formation: [#]
# Chop off everything after the file name for our purposes here
if tf_file.endswith("]") and "[" in tf_file:
- self.tf_file = tf_file[:tf_file.index("[")]
+ self.tf_file = tf_file[: tf_file.index("[")]
+ self.tf_file_path = Path(tf_file[: tf_file.index("[")])
else:
self.tf_file = tf_file
+ self.tf_file_path = Path(tf_file)
self.context = {}
self.file_lines = self._read_file_lines()
self.context = self.enrich_definition_block(definition_blocks)
@@ -123,29 +161,40 @@ def run(self, tf_file, definition_blocks, collect_skip_comments=True):
self.context = self._collect_skip_comments(definition_blocks)
return self.context
- def get_block_type(self):
+ def get_block_type(self) -> str:
return self.definition_type
- def enrich_definition_block(self, definition_blocks):
+ @staticmethod
+ def _clean_line(line: str):
+ res = line.replace('"', ' ')
+ if '"{' in res:
+ res = res.split('{')[0]
+ return res
+
+ def enrich_definition_block(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Enrich the context of a Terraform block
:param definition_blocks: Terraform block, key-value dictionary
:return: Enriched block context
"""
parsed_file_lines = self._filter_file_lines()
- potential_block_start_lines = [(ind, line) for (ind, line) in parsed_file_lines if line.startswith(self.get_block_type())]
+ potential_block_start_lines = [
+ (ind, line) for (ind, line) in parsed_file_lines if line.startswith(self.get_block_type())
+ ]
for i, entity_block in enumerate(definition_blocks):
entity_context_path = self.get_entity_context_path(entity_block)
for line_num, line in potential_block_start_lines:
- line_tokens = [x.replace('"', "") for x in line.split()]
+ line_str = self._clean_line(line)
+ line_tokens = line_str.split()
if self._is_block_signature(line_num, line_tokens, entity_context_path):
logging.debug(f'created context for {" ".join(entity_context_path)}')
start_line = line_num
end_line = self._compute_definition_end_line(line_num)
dpath.new(self.context, entity_context_path + ["start_line"], start_line)
dpath.new(self.context, entity_context_path + ["end_line"], end_line)
- dpath.new(self.context, entity_context_path + ["code_lines"],
- self.file_lines[start_line - 1: end_line])
+ dpath.new(
+ self.context, entity_context_path + ["code_lines"], self.file_lines[start_line - 1: end_line]
+ )
potential_block_start_lines.remove((line_num, line))
break
return self.context
diff --git a/checkov/terraform/context_parsers/parsers/__init__.py b/checkov/terraform/context_parsers/parsers/__init__.py
index 1cb91d4ca0..bfe83e75bf 100644
--- a/checkov/terraform/context_parsers/parsers/__init__.py
+++ b/checkov/terraform/context_parsers/parsers/__init__.py
@@ -1,4 +1,4 @@
-from os.path import dirname, basename, isfile, join
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
\ No newline at end of file
+from pathlib import Path
+
+modules = Path(__file__).parent.glob("*.py")
+__all__ = [f.stem for f in modules if f.is_file() and not f.stem == "__init__"]
diff --git a/checkov/terraform/context_parsers/parsers/data_context_parser.py b/checkov/terraform/context_parsers/parsers/data_context_parser.py
index 30a2b47416..9566624690 100644
--- a/checkov/terraform/context_parsers/parsers/data_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/data_context_parser.py
@@ -1,14 +1,16 @@
+from typing import Dict, Any, List
+
from checkov.terraform.context_parsers.base_parser import BaseContextParser
class DataContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "data"
super().__init__(definition_type=definition_type)
- def get_entity_context_path(self, entity_block):
- entity_type = next(iter(entity_block.keys()))
- entity_name = next(iter(entity_block[entity_type]))
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
+ entity_type, entity_value = next(iter(entity_block.items()))
+ entity_name = next(iter(entity_value))
return [entity_type, entity_name]
diff --git a/checkov/terraform/context_parsers/parsers/locals_context_parser.py b/checkov/terraform/context_parsers/parsers/locals_context_parser.py
index e095e15fef..d39f05b15e 100644
--- a/checkov/terraform/context_parsers/parsers/locals_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/locals_context_parser.py
@@ -1,29 +1,28 @@
+from typing import Dict, Any, List
+
from checkov.terraform.context_parsers.base_parser import BaseContextParser
import dpath.util
class LocalsContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "locals"
super().__init__(definition_type=definition_type)
- def _collect_local_values(self, local_block):
- if isinstance(local_block,dict):
- for local_name, local_value in local_block.items():
- local_value = local_value[0]
- if type(local_value) in (int, float, bool, str):
- dpath.new(self.context, ['assignments', local_name], local_value)
-
- def get_block_type(self):
- return self.definition_type
+ def _collect_local_values(self, local_block: Dict[str, Any]) -> None:
+ for local_name, local_value in local_block.items():
+ local_value = local_value[0] if isinstance(local_value, list) and len(local_value) > 0 else local_value
+ if type(local_value) in (int, float, bool, str, dict):
+ dpath.new(self.context, ["assignments", local_name], local_value)
- def get_entity_context_path(self, entity_block):
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
return []
- def enrich_definition_block(self, definition_blocks):
+ def enrich_definition_block(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
self.context = super().enrich_definition_block(definition_blocks)
- for i, locals_block in enumerate(definition_blocks):
- self._collect_local_values(locals_block)
+ for locals_block in definition_blocks:
+ if isinstance(locals_block, dict):
+ self._collect_local_values(locals_block)
return self.context
diff --git a/checkov/terraform/context_parsers/parsers/module_context_parser.py b/checkov/terraform/context_parsers/parsers/module_context_parser.py
index 0f87965fa4..d838fe42f8 100644
--- a/checkov/terraform/context_parsers/parsers/module_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/module_context_parser.py
@@ -1,12 +1,14 @@
+from typing import Dict, Any, List
+
from checkov.terraform.context_parsers.base_parser import BaseContextParser
class ModuleContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "module"
super().__init__(definition_type=definition_type)
- def get_entity_context_path(self, entity_block):
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
entity_name = next(iter(entity_block.keys()))
return ["module", entity_name]
diff --git a/checkov/terraform/context_parsers/parsers/provider_context_parser.py b/checkov/terraform/context_parsers/parsers/provider_context_parser.py
index 2ec310ab81..247b69bd43 100644
--- a/checkov/terraform/context_parsers/parsers/provider_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/provider_context_parser.py
@@ -1,29 +1,35 @@
+from typing import Dict, Any, List
+
import hcl2
from checkov.terraform.context_parsers.base_parser import BaseContextParser
class ProviderContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "provider"
super().__init__(definition_type=definition_type)
- def get_entity_context_path(self, entity_block):
- entity_type = next(iter(entity_block))
- return [entity_type] + entity_block[entity_type].get('alias', ['default'])
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
+ entity_type, entity_value = next(iter(entity_block.items()))
+ return [entity_type, entity_value.get("alias", ["default"])[0]]
- def _is_block_signature(self, line_num, line_tokens, entity_context_path):
+ def _is_block_signature(self, line_num: int, line_tokens: List[str], entity_context_path: List[str]) -> bool:
# Ignore the alias as it is not part of the signature
is_provider = super()._is_block_signature(line_num, line_tokens, entity_context_path[0:-1])
- if not is_provider or '=' in line_tokens or line_tokens[0] != 'provider':
+ if not is_provider or "=" in line_tokens or line_tokens[0] != "provider":
# The line provider = alias is not a provider block although it has the correct words
# Also skips comments that include words like provider and aws
return False
end_line = self._compute_definition_end_line(line_num)
provider_type = entity_context_path[0]
- provider_obj = hcl2.loads("\n".join(map(lambda obj: obj[1], self.file_lines[line_num - 1:end_line if end_line > line_num else line_num])))['provider'][0]
- alias = provider_obj[provider_type].get('alias', ['default'])
+ provider_obj = hcl2.loads(
+ "\n".join(
+ map(lambda obj: obj[1], self.file_lines[line_num - 1 : end_line if end_line > line_num else line_num])
+ )
+ )["provider"][0]
+ alias = provider_obj[provider_type].get("alias", ["default"])
return super()._is_block_signature(line_num, line_tokens + alias, entity_context_path)
diff --git a/checkov/terraform/context_parsers/parsers/resource_context_parser.py b/checkov/terraform/context_parsers/parsers/resource_context_parser.py
index bc9dfbef05..0665988a2e 100644
--- a/checkov/terraform/context_parsers/parsers/resource_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/resource_context_parser.py
@@ -1,12 +1,14 @@
+from typing import Dict, Any, List
+
from checkov.terraform.context_parsers.base_parser import BaseContextParser
class ResourceContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "resource"
super().__init__(definition_type=definition_type)
- def get_entity_context_path(self, entity_block):
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
entity_type = next(iter(entity_block.keys()))
entity_name = next(iter(entity_block[entity_type]))
return [entity_type, entity_name]
diff --git a/checkov/terraform/context_parsers/parsers/variable_context_parser.py b/checkov/terraform/context_parsers/parsers/variable_context_parser.py
index 1f84debcb0..c867161405 100644
--- a/checkov/terraform/context_parsers/parsers/variable_context_parser.py
+++ b/checkov/terraform/context_parsers/parsers/variable_context_parser.py
@@ -1,32 +1,35 @@
-from checkov.terraform.context_parsers.base_parser import BaseContextParser
-import os
+from typing import Dict, Any, List
+
import dpath.util
+from checkov.terraform.context_parsers.base_parser import BaseContextParser
+
+
class VariableContextParser(BaseContextParser):
- def __init__(self):
+ def __init__(self) -> None:
definition_type = "variable"
super().__init__(definition_type=definition_type)
- def _collect_default_variables_values(self, variable_block):
- (variable_folder, _) = os.path.split(self.tf_file)
- if isinstance(variable_block,dict):
- for variable_name, values in variable_block.items():
- if 'default' in values.keys():
- for key, value in values.items():
- if isinstance(value, list) and len(value) == 1:
- value = values['default'][0]
- if type(value) in (int, float, bool, str):
- dpath.new(self.context, ['assignments', variable_name], value)
-
- def get_entity_context_path(self, entity_block):
+ def _collect_default_variables_values(self, variable_block: Dict[str, Dict[str, Any]]) -> None:
+ for variable_name, values in variable_block.items():
+ default_value = values.get("default")
+ if (
+ isinstance(default_value, list)
+ and len(default_value) == 1
+ and type(default_value[0]) in (int, float, bool, str)
+ ):
+ dpath.new(self.context, ["assignments", variable_name], default_value[0])
+
+ def get_entity_context_path(self, entity_block: Dict[str, Dict[str, Any]]) -> List[str]:
entity_name = next(iter(entity_block.keys()))
return [entity_name]
- def enrich_definition_block(self, definition_blocks):
+ def enrich_definition_block(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
self.context = super().enrich_definition_block(definition_blocks)
- for i, variable_block in enumerate(definition_blocks):
- self._collect_default_variables_values(variable_block)
+ for variable_block in definition_blocks:
+ if isinstance(variable_block, dict):
+ self._collect_default_variables_values(variable_block)
return self.context
diff --git a/checkov/terraform/context_parsers/registry.py b/checkov/terraform/context_parsers/registry.py
index 70e025e26a..435fb98943 100644
--- a/checkov/terraform/context_parsers/registry.py
+++ b/checkov/terraform/context_parsers/registry.py
@@ -1,21 +1,28 @@
import logging
+from typing import Dict, TYPE_CHECKING, Tuple, List, Any
+
import dpath.util
+if TYPE_CHECKING:
+ from checkov.terraform.context_parsers.base_parser import BaseContextParser
+
class ParserRegistry:
- context_parsers = {}
- definitions_context = {}
+ context_parsers: Dict[str, "BaseContextParser"] = {}
+ definitions_context: Dict[str, Dict[str, Dict[str, Any]]] = {}
- def __init__(self):
+ def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
- def register(self, parser):
+ def register(self, parser: "BaseContextParser") -> None:
self.context_parsers[parser.definition_type] = parser
- def reset_definitions_context(self):
+ def reset_definitions_context(self) -> None:
self.definitions_context = {}
- def enrich_definitions_context(self, definitions, collect_skip_comments=True):
+ def enrich_definitions_context(
+ self, definitions: Tuple[str, Dict[str, List[Dict[str, Any]]]], collect_skip_comments: bool = True
+ ) -> Dict[str, Dict[str, Dict[str, Any]]]:
supported_definitions = [parser_type for parser_type in self.context_parsers.keys()]
(tf_file, definition_blocks_types) = definitions
if definition_blocks_types:
@@ -25,7 +32,9 @@ def enrich_definitions_context(self, definitions, collect_skip_comments=True):
dpath.new(self.definitions_context, [tf_file, definition_type], {})
context_parser = self.context_parsers[definition_type]
definition_blocks = definition_blocks_types[definition_type]
- self.definitions_context[tf_file][definition_type] = context_parser.run(tf_file, definition_blocks, collect_skip_comments)
+ self.definitions_context[tf_file][definition_type] = context_parser.run(
+ tf_file, definition_blocks, collect_skip_comments
+ )
return self.definitions_context
diff --git a/checkov/terraform/context_parsers/tf_plan/__init__.py b/checkov/terraform/context_parsers/tf_plan/__init__.py
index d340625011..c1236b95fd 100644
--- a/checkov/terraform/context_parsers/tf_plan/__init__.py
+++ b/checkov/terraform/context_parsers/tf_plan/__init__.py
@@ -1,3 +1,4 @@
+import json
import logging
from checkov.terraform.context_parsers.tf_plan.node import dict_node
@@ -21,6 +22,11 @@ def parse(filename):
(template, template_lines) = tf_plan_json.load(filename)
except tf_plan_json.JSONDecodeError:
pass
+ except json.decoder.JSONDecodeError:
+ # Most parsing errors will get caught by the exception above. But, if the file is totally empty, and perhaps in
+ # other specific cases, the json library will not even begin parsing with our custom logic that throws the
+ # exception above, and will fail with this exception instead.
+ pass
if template is not None and isinstance(template, dict_node) and 'terraform_version' in template and 'planned_values' in template:
return template, template_lines
diff --git a/checkov/terraform/evaluation/base_variable_evaluation.py b/checkov/terraform/evaluation/base_variable_evaluation.py
index 94b2a90744..5a2a6dd727 100644
--- a/checkov/terraform/evaluation/base_variable_evaluation.py
+++ b/checkov/terraform/evaluation/base_variable_evaluation.py
@@ -2,25 +2,29 @@
import logging
import os
import re
+from typing import Tuple, Dict, Any, List
+
import dpath.util
-TF_DEFINITIONS_STRIP_WORDS = r'\b(?!\d)([^\/]+)'
-NON_PATH_WORDS_REGEX = r'\b(?!output)[^ .]+'
-DEFINITION_TYPES_REGEX_MAPPING = {
- 'variable': 'var',
- 'locals': 'local'
-}
+TF_DEFINITIONS_STRIP_WORDS = r"\b(?!\d)([^\/]+)"
+NON_PATH_WORDS_REGEX = r"\b(?!output)[^ .]+"
+DEFINITION_TYPES_REGEX_MAPPING = {"variable": "var", "locals": "local"}
class BaseVariableEvaluation(ABC):
- def __init__(self, root_folder, tf_definitions, definitions_context):
+ def __init__(
+ self,
+ root_folder: str,
+ tf_definitions: Dict[str, Dict[str, Any]],
+ definitions_context: Dict[str, Dict[str, Any]],
+ ) -> None:
self.logger = logging.getLogger("{}".format(self.__module__))
self.root_folder = root_folder
self.tf_definitions = tf_definitions
self.definitions_context = definitions_context
@abstractmethod
- def evaluate_variables(self):
+ def evaluate_variables(self) -> Any:
"""
evaluate variables of tf_definitions entities
:return:
@@ -28,17 +32,22 @@ def evaluate_variables(self):
raise NotImplementedError()
@staticmethod
- def _generate_evaluation_regex(definition_type, var_name):
- return r'((?:\$\{)?' + re.escape(DEFINITION_TYPES_REGEX_MAPPING[definition_type]) + \
- r'[.]' + re.escape(var_name) + r'(?:\})?)'
+ def _generate_evaluation_regex(definition_type: str, var_name: str) -> str:
+ return (
+ r"((?:\$\{)?"
+ + re.escape(DEFINITION_TYPES_REGEX_MAPPING[definition_type])
+ + r"[.]"
+ + re.escape(var_name)
+ + r"(?:\})?)"
+ )
@staticmethod
- def _is_variable_only_expression(assignment_regex, entry_expression):
- exact_assignment_regex = r'^' + assignment_regex + r'$'
+ def _is_variable_only_expression(assignment_regex: str, entry_expression: str) -> bool:
+ exact_assignment_regex = r"^" + assignment_regex + r"$"
return len(re.findall(exact_assignment_regex, entry_expression)) > 0
@staticmethod
- def extract_context_path(definition_path):
+ def extract_context_path(definition_path: str) -> Tuple[str, str]:
"""
Converts a JSONPath (dpath library standard) definition entry path into valid context parser path
:param definition_path: entity's JSONPath syntax path in tf_definitions
@@ -47,24 +56,26 @@ def extract_context_path(definition_path):
return os.path.split("/".join(re.findall(TF_DEFINITIONS_STRIP_WORDS, definition_path)))
@staticmethod
- def reduce_entity_evaluations(variables_evaluations, entity_context_path):
+ def reduce_entity_evaluations(
+ variables_evaluations: Dict[str, Dict[str, Any]], entity_context_path: List[str]
+ ) -> Dict[str, Any]:
"""
Reduce variable evaluations only to variables that are included in the entity's code block
:param variables_evaluations:
:param entity_context_path:
:return: the variable evaluations of the entity
"""
- entity_evaluations = {}
+ entity_evaluations: Dict[str, Any] = {}
for var_name, variable_evaluations in variables_evaluations.items():
entity_definitions = []
- for var_definition in variable_evaluations['definitions']:
- var_context_path, _ = BaseVariableEvaluation.extract_context_path(var_definition['definition_path'])
+ for var_definition in variable_evaluations["definitions"]:
+ var_context_path, _ = BaseVariableEvaluation.extract_context_path(var_definition["definition_path"])
variable_context_path = var_context_path.split("/")
# This is due to inconsistency in order of Terraform entity naming conventions
if set(variable_context_path) == set(entity_context_path):
entity_definitions.append(var_definition)
if entity_definitions:
entity_evaluation = variables_evaluations[var_name]
- entity_evaluation['definitions'] = entity_definitions
+ entity_evaluation["definitions"] = entity_definitions
dpath.new(entity_evaluations, var_name, entity_evaluation)
return entity_evaluations
diff --git a/checkov/terraform/graph_builder/EncryptionCalculation.md b/checkov/terraform/graph_builder/EncryptionCalculation.md
new file mode 100644
index 0000000000..462b536ae7
--- /dev/null
+++ b/checkov/terraform/graph_builder/EncryptionCalculation.md
@@ -0,0 +1,45 @@
+# Custom Encryption Attribute Calculation
+When building queries, we noticed we were constantly asking a common question - _Is the resource encrypted?_
+
+To avoid having to know the correct configuration of each resource, and to make queries more concise, we decided to add
+`encryption` as a custom attribute to the relevant resource types. You can skip directly to:
+1. [Overview](#overview)
+2. [Example usage](#example-usage)
+3. [Contributing](#contributing-to-extend-coverage)
+
+## Overview
+To support the different configurations of the different terraform resources with encryption, we've created 2 important
+objects defined [here](./graph_components/generic_resource_encryption.py):
+1. `GenericResourceEncryption` - this class executes the configuration logic based on the constructor parameters
+ to decide whether the resource is encrypted or not.
+2. `ENCRYPTION_BY_RESOURCE_TYPE` - a map of >. This means that
+ for every resource type (i.e. `aws_s3_bucket`, `aws_rds_cluster` etc) there's either an entry in this map or there
+ isn't. If there is an entry - the GenericResourceEncryption class will decide whether it is encrypted according to
+ the [calculation logic](#calculation-logic). If there is no entry - the attribute will not exist for that resource
+ type.
+
+
+### Calculation Logic
+ENCRYPTION_BY_RESOURCE_TYPE receives as a second parameter a dict, consisting of the attribute paths as keys & the
+possible matching values as value list, i.e.:
+```python
+{
+ "encrypt_at_rest.enabled": [True],
+ "kms_key_id": [],
+ "node_to_node_encryption.enabled": [True]
+}
+```
+Please note the empty list is supported - it means ANY. So in the case above, if the attribute `kms_key_id` exists in
+the resource it will be marked as encrypted, no matter what the actual value is for `kms_key_id`. However, we do expect
+`encrypt_at_rest.enabled` to be set to `True` - otherwise it will be marked as unencrypted.
+
+##Example Usage
+This field can be leveraged in policies, i.e. query the field `encryption_` for the strings "ENCRYPTED" / "UNENCRYPTED".
+Example [query](../../../tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/EncryptedResources.yaml),
+and the expected resources can be found in the matching [test-case on line 22 here](../../../tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/test_solver.py).
+
+## Contributing to Extend Coverage
+To add support for a new resource type, for example `foo_bar`, a new entry needs to be added to
+`ENCRYPTION_BY_RESOURCE_TYPE`, which maps the resource type string to an instance of `GenericResourceEncryption`.
+The constructor first parameter is the resource type, and the second is the dict as described in the
+[calculation logic](#calculation-logic)
diff --git a/checkov/terraform/graph_builder/__init__.py b/checkov/terraform/graph_builder/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/terraform/graph_builder/graph_components/__init__.py b/checkov/terraform/graph_builder/graph_components/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/terraform/graph_builder/graph_components/attribute_names.py b/checkov/terraform/graph_builder/graph_components/attribute_names.py
new file mode 100644
index 0000000000..9300fae217
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_components/attribute_names.py
@@ -0,0 +1,12 @@
+from dataclasses import dataclass
+
+from checkov.common.graph.graph_builder import CustomAttributes as CommonCustomAttributes, props
+
+
+@dataclass
+class CustomAttributes(CommonCustomAttributes):
+ ENCRYPTION = "encryption_"
+ ENCRYPTION_DETAILS = "encryption_details_"
+
+
+reserved_attribute_names = props(CustomAttributes)
diff --git a/checkov/terraform/graph_builder/graph_components/block_types.py b/checkov/terraform/graph_builder/graph_components/block_types.py
new file mode 100644
index 0000000000..bbfba73fe5
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_components/block_types.py
@@ -0,0 +1,15 @@
+from checkov.common.graph.graph_builder.graph_components.block_types import BlockType as CommonBlockType
+from dataclasses import dataclass
+
+
+@dataclass
+class BlockType(CommonBlockType):
+ DATA = "data"
+ LOCALS = "locals"
+ MODULE = "module"
+ OUTPUT = "output"
+ PROVIDER = "provider"
+ TERRAFORM = "terraform"
+ TF_VARIABLE = "tfvar"
+ VARIABLE = "variable"
+ CUSTOM = "custom"
diff --git a/checkov/terraform/graph_builder/graph_components/blocks.py b/checkov/terraform/graph_builder/graph_components/blocks.py
new file mode 100644
index 0000000000..b1263e37bc
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_components/blocks.py
@@ -0,0 +1,65 @@
+import os
+from typing import Union, Dict, Any, List, Optional, Set
+
+from checkov.common.graph.graph_builder.graph_components.blocks import Block
+from checkov.common.util.consts import RESOLVED_MODULE_ENTRY_NAME
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.utils import remove_module_dependency_in_path
+
+
+class TerraformBlock(Block):
+ def __init__(self, name: str, config: Dict[str, Any], path: str, block_type: BlockType, attributes: Dict[str, Any],
+ id: str = "", source: str = "") -> None:
+ """
+ :param name: unique name given to the terraform block, for example: 'aws_vpc.example_name'
+ :param config: the section in tf_definitions that belong to this block
+ :param path: the file location of the block
+ :param block_type: BlockType
+ :param attributes: dictionary of the block's original attributes in the terraform file
+ """
+ super(TerraformBlock, self).__init__(name, config, path, block_type, attributes, id, source)
+ self.module_dependency = ""
+ self.module_dependency_num = ""
+ if path:
+ self.path, module_dependency, num = remove_module_dependency_in_path(path)
+ self.path = os.path.realpath(self.path)
+ if module_dependency:
+ self.module_dependency = module_dependency
+ self.module_dependency_num = num
+ if attributes.get(RESOLVED_MODULE_ENTRY_NAME):
+ del attributes[RESOLVED_MODULE_ENTRY_NAME]
+ self.attributes = attributes
+ self.module_connections: Dict[str, List[int]] = {}
+ self.source_module: Set[int] = set()
+
+ def add_module_connection(self, attribute_key: str, vertex_id: int) -> None:
+ self.module_connections.setdefault(attribute_key, []).append(vertex_id)
+
+ def find_attribute(self, attribute: Optional[Union[str, List[str]]]) -> Optional[str]:
+ """
+ :param attribute: key to search in self.attribute
+ The function searches for attribute in self.attribute. It might not exist if the block is variable or output,
+ or its search path might be different if its a resource.
+ :return: the actual attribute key or None
+ """
+ if not attribute:
+ return None
+
+ if self.attributes.get(attribute[0]):
+ return attribute[0]
+
+ if self.block_type == BlockType.VARIABLE:
+ return "default" if self.attributes.get("default") else None
+
+ if self.block_type == BlockType.OUTPUT:
+ return "value" if self.attributes.get("value") else None
+
+ if self.block_type == BlockType.RESOURCE and len(attribute) > 1:
+ # handle cases where attribute_at_dest == ['aws_s3_bucket.template_bucket', 'acl']
+ if self.name == attribute[0] and self.attributes.get(attribute[1]):
+ return attribute[1]
+
+ return None
+
+
diff --git a/checkov/terraform/graph_builder/graph_components/generic_resource_encryption.py b/checkov/terraform/graph_builder/graph_components/generic_resource_encryption.py
new file mode 100644
index 0000000000..636e3371ff
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_components/generic_resource_encryption.py
@@ -0,0 +1,151 @@
+from typing import Dict, List, Union, Tuple, Any
+
+from checkov.common.graph.graph_builder import EncryptionTypes
+
+
+class GenericResourceEncryption:
+ def __init__(
+ self,
+ resource_type: str,
+ attribute_values_map: Dict[str, Union[List[bool], List[str]]],
+ enabled_by_default: bool = False,
+ ) -> None:
+ """
+ :param resource_type: The resource type this checks. Only helps with debugging.
+ :param attribute_values_map: A dict that maps each attribute to its expected values. An attribute which
+ points to an empty array can hold any value, except None, and identify the
+ resource as an encrypted resource.
+ :param enabled_by_default: Some resources are encrypted by default, even if no configuration is present.
+ Some are not. This helps implement that logic
+ """
+ self.enabled_by_default = enabled_by_default
+ self.attribute_values_map = attribute_values_map
+ self.resource_type = resource_type
+
+ self.default_description = ""
+ if self.resource_type.startswith("aws_"):
+ self.default_description = EncryptionTypes.DEFAULT_KMS.value
+
+ def is_encrypted(self, atts_dict: Dict[str, Any]) -> Tuple[bool, str]:
+ result = True
+ result_description = ""
+ for att, expected_vals in self.attribute_values_map.items():
+ att_value = atts_dict.get(att)
+ if att_value:
+ result &= (len(expected_vals) == 0 and att_value is not None) or att_value in expected_vals
+ if result:
+ if att_value == EncryptionTypes.AES256.value:
+ result_description = att_value
+ elif "node_to_node_encryption" in att:
+ result_description = EncryptionTypes.NODE_TO_NODE.value
+ elif result_description == "":
+ result_description = EncryptionTypes.KMS_VALUE.value
+
+ if result_description == "" and result:
+ # No encryption config was found. Drop back to defaults:
+ result = self.enabled_by_default
+ result_description = self.default_description if self.enabled_by_default else ""
+
+ return result, result_description
+
+ def __str__(self) -> str:
+ return f"GenericResourceEncryption[{self.resource_type}]"
+
+
+# This map allows dynamically creating the check for each resource type based on GenericResourceEncryption.
+# Please check out the constructor to understand all the edge cases.
+ENCRYPTION_BY_RESOURCE_TYPE = {
+ "aws_ecr_repository": GenericResourceEncryption(
+ "aws_ecr_repository",
+ {
+ "encryption_configuration.encryption_type": [EncryptionTypes.AES256.value, EncryptionTypes.KMS_VALUE.value],
+ "encryption_configuration.kms_key": [],
+ },
+ ),
+ "aws_neptune_cluster": GenericResourceEncryption(
+ "aws_neptune_cluster", {"storage_encrypted": [True], "kms_key_arn": []}
+ ),
+ "aws_efs_file_system": GenericResourceEncryption("aws_efs_file_system", {"encrypted": [True], "kms_key_id": []}),
+ "aws_sagemaker_feature_group": GenericResourceEncryption(
+ "aws_sagemaker_feature_group", {"security_config.kms_key_id": []}
+ ),
+ "aws_ebs_volume": GenericResourceEncryption("aws_ebs_volume", {"encrypted": [True], "kms_key_id": []}),
+ "aws_elasticache_replication_group": GenericResourceEncryption(
+ "aws_elasticache_replication_group", {"at_rest_encryption_enabled": [True], "kms_key_id": ["arn"],}
+ ),
+ "aws_elasticsearch_domain": GenericResourceEncryption(
+ "aws_elasticsearch_domain",
+ {"encrypt_at_rest.enabled": [True], "kms_key_id": [], "node_to_node_encryption.enabled": [True]},
+ ),
+ "aws_msk_cluster": GenericResourceEncryption(
+ "aws_msk_cluster", {"encryption_info.encryption_at_rest_kms_key_arn": []}
+ ),
+ "aws_docdb_cluster": GenericResourceEncryption(
+ "aws_docdb_cluster", {"storage_encrypted": [True], "kms_key_arn": []}
+ ),
+ "aws_codebuild_project": GenericResourceEncryption("aws_codebuild_project", {"encryption_key": []}),
+ "aws_codebuild_report_group": GenericResourceEncryption(
+ "aws_codebuild_report_group",
+ {
+ "export_config.s3_destination.encryption_disabled": [False],
+ "export_config.s3_destination.encryption_key": [],
+ },
+ ),
+ "aws_athena_database": GenericResourceEncryption(
+ "aws_athena_database",
+ {
+ "encryption_configuration.encryption_option": ["SSE_S3", "SSE_KMS", "CSE_KMS"],
+ "encryption_configuration.kms_key": [],
+ },
+ ),
+ "aws_athena_workgroup": GenericResourceEncryption(
+ "aws_athena_workgroup",
+ {
+ "configuration.result_configuration.encryption_configuration.encryption_option": [
+ "SSE_S3",
+ "SSE_KMS",
+ "CSE_KMS",
+ ],
+ "configuration.result_configuration.encryption_configuration.kms_key_arn": [],
+ },
+ ),
+ "aws_kinesis_stream": GenericResourceEncryption(
+ "aws_kinesis_stream", {"encryption_type": [EncryptionTypes.KMS_VALUE.value], "kms_key_id": []}
+ ),
+ "aws_eks_cluster": GenericResourceEncryption("aws_eks_cluster", {"encryption_config.provider.key_arn": []}),
+ "aws_dynamodb_table": GenericResourceEncryption(
+ "aws_dynamodb_table",
+ {"server_side_encryption.enabled": [True], "server_side_encryption.kms_key_arn": []},
+ enabled_by_default=True,
+ ),
+ "aws_rds_cluster": GenericResourceEncryption("aws_rds_cluster", {"storage_encrypted": [True], "kms_key_id": []}),
+ "aws_rds_global_cluster": GenericResourceEncryption("aws_rds_global_cluster", {"storage_encrypted": [True]}),
+ "aws_s3_bucket": GenericResourceEncryption(
+ "aws_s3_bucket",
+ {
+ "server_side_encryption_configuration.rule.apply_server_side_encryption_by_default.sse_algorithm": [
+ EncryptionTypes.AWS_KMS_VALUE.value,
+ EncryptionTypes.AES256.value,
+ ],
+ "server_side_encryption_configuration.rule.apply_server_side_encryption_by_default.kms_master_key_id": [],
+ },
+ ),
+ "aws_s3_bucket_inventory": GenericResourceEncryption(
+ "aws_s3_bucket_inventory", {"destination.bucket.encryption": []}
+ ),
+ "aws_s3_bucket_object": GenericResourceEncryption(
+ "aws_s3_bucket_object",
+ {
+ "server_side_encryption": [EncryptionTypes.AWS_KMS_VALUE.value, EncryptionTypes.AES256.value],
+ "kms_key_id": [],
+ },
+ ),
+ "aws_cloudwatch_log_group": GenericResourceEncryption(
+ "aws_cloudwatch_log_group", {"kms_key_id": []}, enabled_by_default=True
+ ),
+ "aws_cloudtrail": GenericResourceEncryption("aws_cloudtrail", {"kms_key_id": []}),
+ "aws_dax_cluster": GenericResourceEncryption("aws_dax_cluster", {"server_side_encryption.enabled": [True]}),
+ "aws_redshift_cluster": GenericResourceEncryption("aws_redshift_cluster", {"encrypted": [True], "kms_key_id": []}),
+ "aws_sns_topic": GenericResourceEncryption("aws_sns_topic", {"kms_master_key_id": []}),
+ "aws_sqs_queue": GenericResourceEncryption("aws_sqs_queue", {"kms_master_key_id": []}),
+}
diff --git a/checkov/terraform/graph_builder/graph_components/module.py b/checkov/terraform/graph_builder/graph_components/module.py
new file mode 100644
index 0000000000..1ced34a290
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_components/module.py
@@ -0,0 +1,206 @@
+import os
+from copy import deepcopy
+from typing import List, Dict, Any, Set, Callable
+
+from checkov.terraform.checks.utils.dependency_path_handler import unify_dependency_path
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.blocks import TerraformBlock
+from checkov.common.graph.graph_builder.graph_components.blocks import get_inner_attributes
+
+
+class Module:
+ def __init__(
+ self,
+ source_dir: str,
+ module_dependency_map: Dict[str, List[List[str]]],
+ dep_index_mapping: Dict[str, str]
+ ) -> None:
+ self.dep_index_mapping = dep_index_mapping
+ self.module_dependency_map = module_dependency_map
+ self.path = ""
+ self.blocks: List[TerraformBlock] = []
+ self.customer_name = ""
+ self.account_id = ""
+ self.source = ""
+ self.resources_types: Set[str] = set()
+ self.source_dir = source_dir
+
+ def add_blocks(
+ self, block_type: BlockType, blocks: List[Dict[str, Dict[str, Any]]], path: str, source: str
+ ) -> None:
+ self.source = source
+ if self._block_type_to_func.get(block_type):
+ self._block_type_to_func[block_type](self, blocks, path)
+
+ def _add_to_blocks(self, block: TerraformBlock) -> None:
+ dependencies = [dep_trail for dep_trail in self.module_dependency_map.get(os.path.dirname(block.path), [])]
+ module_dependency_num = ""
+ if not dependencies:
+ dependencies = [[]]
+ else:
+ module_dependency_num = self.dep_index_mapping.get(block.path, "")
+ for dep_trail in dependencies:
+ block = deepcopy(block)
+ block.module_dependency = unify_dependency_path(dep_trail)
+ block.module_dependency_num = module_dependency_num
+ self.blocks.append(block)
+
+ def _add_provider(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for provider_dict in blocks:
+ for name in provider_dict:
+ attributes = provider_dict[name]
+ provider_name = name
+ alias = attributes.get("alias")
+ if alias:
+ provider_name = f"{provider_name}.{alias[0]}"
+ provider_block = TerraformBlock(
+ block_type=BlockType.PROVIDER,
+ name=provider_name,
+ config=provider_dict,
+ path=path,
+ attributes=attributes,
+ source=self.source
+ )
+ self._add_to_blocks(provider_block)
+
+ def _add_variable(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for variable_dict in blocks:
+ for name in variable_dict:
+ attributes = variable_dict[name]
+ variable_block = TerraformBlock(
+ block_type=BlockType.VARIABLE,
+ name=name,
+ config=variable_dict,
+ path=path,
+ attributes=attributes,
+ source=self.source
+ )
+ self._add_to_blocks(variable_block)
+
+ def _add_locals(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for blocks_section in blocks:
+ for name in blocks_section:
+ local_block = TerraformBlock(
+ block_type=BlockType.LOCALS,
+ name=name,
+ config={name: blocks_section[name]},
+ path=path,
+ attributes={name: blocks_section[name]},
+ source=self.source
+ )
+ self._add_to_blocks(local_block)
+
+ def _add_output(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for output_dict in blocks:
+ for name in output_dict:
+ if type(output_dict[name]) is not dict:
+ continue
+ output_block = TerraformBlock(
+ block_type=BlockType.OUTPUT,
+ name=name,
+ config=output_dict,
+ path=path,
+ attributes={"value": output_dict[name].get("value")},
+ source=self.source
+ )
+ self._add_to_blocks(output_block)
+
+ def _add_module(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for module_dict in blocks:
+ for name in module_dict:
+ module_block = TerraformBlock(
+ block_type=BlockType.MODULE,
+ name=name,
+ config=module_dict,
+ path=path,
+ attributes=module_dict[name],
+ source=self.source
+ )
+ self._add_to_blocks(module_block)
+
+ def _add_resource(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for resource_dict in blocks:
+ for resource_type, resources in resource_dict.items():
+ self.resources_types.add(resource_type)
+ for name, resource_conf in resources.items():
+ attributes = deepcopy(resource_conf)
+ provisioner = attributes.get("provisioner")
+ if provisioner:
+ self._handle_provisioner(provisioner, attributes)
+ attributes["resource_type"] = [resource_type]
+ resource_block = TerraformBlock(
+ block_type=BlockType.RESOURCE,
+ name=f"{resource_type}.{name}",
+ config=resource_dict,
+ path=path,
+ attributes=attributes,
+ id=f"{resource_type}.{name}",
+ source=self.source
+ )
+ self._add_to_blocks(resource_block)
+
+ def _add_data(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for data_dict in blocks:
+ for data_type in data_dict:
+ for name in data_dict[data_type]:
+ data_block = TerraformBlock(
+ block_type=BlockType.DATA,
+ name=data_type + "." + name,
+ config=data_dict,
+ path=path,
+ attributes=data_dict.get(data_type, {}).get(name, {}),
+ id=data_type + "." + name,
+ source=self.source
+ )
+ self._add_to_blocks(data_block)
+
+ def _add_terraform_block(self, blocks: List[Dict[str, Dict[str, Any]]], path: str) -> None:
+ for terraform_dict in blocks:
+ for name in terraform_dict:
+ terraform_block = TerraformBlock(
+ block_type=BlockType.TERRAFORM,
+ name=name,
+ config=terraform_dict,
+ path=path,
+ attributes={},
+ source=self.source
+ )
+ self._add_to_blocks(terraform_block)
+
+ def _add_tf_var(self, blocks: Dict[str, Dict[str, Any]], path: str) -> None:
+ for tf_var_name, attributes in blocks.items():
+ tfvar_block = TerraformBlock(
+ block_type=BlockType.TF_VARIABLE,
+ name=tf_var_name,
+ config={tf_var_name: attributes},
+ path=path,
+ attributes=attributes,
+ source=self.source
+ )
+ self._add_to_blocks(tfvar_block)
+
+ @staticmethod
+ def _handle_provisioner(provisioner: List[Dict[str, Any]], attributes: Dict[str, Any]) -> None:
+ for pro in provisioner:
+ if pro.get("local-exec"):
+ inner_attributes = get_inner_attributes("provisioner/local-exec", pro["local-exec"])
+ attributes.update(inner_attributes)
+ elif pro.get("remote-exec"):
+ inner_attributes = get_inner_attributes("provisioner/remote-exec", pro["remote-exec"])
+ attributes.update(inner_attributes)
+ del attributes["provisioner"]
+
+ def get_resources_types(self) -> List[str]:
+ return list(self.resources_types)
+
+ _block_type_to_func: Dict[BlockType, Callable[["Module", List[Dict[str, Dict[str, Any]]], str], None]] = {
+ BlockType.DATA: _add_data,
+ BlockType.LOCALS: _add_locals,
+ BlockType.MODULE: _add_module,
+ BlockType.OUTPUT: _add_output,
+ BlockType.PROVIDER: _add_provider,
+ BlockType.RESOURCE: _add_resource,
+ BlockType.TERRAFORM: _add_terraform_block,
+ BlockType.TF_VARIABLE: _add_tf_var,
+ BlockType.VARIABLE: _add_variable,
+ }
diff --git a/checkov/terraform/graph_builder/graph_to_tf_definitions.py b/checkov/terraform/graph_builder/graph_to_tf_definitions.py
new file mode 100644
index 0000000000..6e3dc4086f
--- /dev/null
+++ b/checkov/terraform/graph_builder/graph_to_tf_definitions.py
@@ -0,0 +1,33 @@
+import os
+from typing import List, Dict, Any, Tuple
+
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.blocks import TerraformBlock
+
+
+def convert_graph_vertices_to_tf_definitions(
+ vertices: List[TerraformBlock], root_folder: str
+) -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
+ tf_definitions: Dict[str, Dict[str, Any]] = {}
+ breadcrumbs: Dict[str, Dict[str, Any]] = {}
+ for vertex in vertices:
+ block_path = vertex.path
+ if not os.path.isfile(block_path):
+ print(f"tried to convert vertex to tf_definitions but its path doesnt exist: {vertex}")
+ continue
+ tf_path = block_path
+ if vertex.module_dependency:
+ tf_path = f"{block_path}[{vertex.module_dependency}#{vertex.module_dependency_num}]"
+ block_type = vertex.block_type
+ if block_type == BlockType.TF_VARIABLE:
+ continue
+ tf_definitions.setdefault(tf_path, {}).setdefault(block_type, []).append(vertex.config)
+ relative_block_path = f"/{os.path.relpath(block_path, root_folder)}"
+ add_breadcrumbs(vertex, breadcrumbs, relative_block_path)
+ return tf_definitions, breadcrumbs
+
+
+def add_breadcrumbs(vertex: TerraformBlock, breadcrumbs: Dict[str, Dict[str, Any]], relative_block_path: str) -> None:
+ vertex_breadcrumbs = vertex.breadcrumbs
+ if vertex_breadcrumbs:
+ breadcrumbs.setdefault(relative_block_path, {})[vertex.name] = vertex_breadcrumbs
diff --git a/checkov/terraform/graph_builder/local_graph.py b/checkov/terraform/graph_builder/local_graph.py
new file mode 100644
index 0000000000..7812e50645
--- /dev/null
+++ b/checkov/terraform/graph_builder/local_graph.py
@@ -0,0 +1,491 @@
+import logging
+import os
+from collections import defaultdict
+from copy import deepcopy
+from pathlib import Path
+from typing import List, Optional, Union, Any, Dict, Set, Callable
+
+from typing_extensions import TypedDict
+
+from checkov.common.graph.graph_builder import Edge
+from checkov.common.graph.graph_builder import reserved_attribute_names, EncryptionValues
+from checkov.common.graph.graph_builder.local_graph import LocalGraph
+from checkov.common.graph.graph_builder.utils import calculate_hash, join_trimmed_strings
+from checkov.terraform.checks.utils.dependency_path_handler import unify_dependency_path
+from checkov.terraform.graph_builder.utils import (
+ get_referenced_vertices_in_value,
+ update_dictionary_attribute,
+ filter_sub_keys,
+ attribute_has_nested_attributes, remove_index_pattern_from_str,
+)
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.blocks import TerraformBlock
+from checkov.terraform.graph_builder.graph_components.generic_resource_encryption import ENCRYPTION_BY_RESOURCE_TYPE
+from checkov.terraform.graph_builder.graph_components.module import Module
+from checkov.terraform.graph_builder.utils import is_local_path
+from checkov.terraform.variable_rendering.renderer import VariableRenderer
+
+MODULE_RESERVED_ATTRIBUTES = ("source", "version")
+
+
+class Undetermined(TypedDict):
+ module_vertex_id: int
+ attribute_name: str
+ variable_vertex_id: int
+
+
+class TerraformLocalGraph(LocalGraph):
+ def __init__(self, module: Module, module_dependency_map: Dict[str, List[List[str]]]) -> None:
+ super(TerraformLocalGraph, self).__init__()
+ self.module = module
+ self.module_dependency_map = module_dependency_map
+ self.map_path_to_module: Dict[str, List[int]] = {}
+ self.relative_paths_cache = {}
+ self.abspath_cache: Dict[str, str] = {}
+ self.dirname_cache: Dict[str, str] = {}
+ self.vertices_by_module_dependency_by_name: Dict[str, Dict[BlockType, Dict[str, List[int]]]] = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
+ self.vertices_by_module_dependency: Dict[str, Dict[BlockType, List[int]]] = defaultdict(lambda: defaultdict(list))
+
+ def build_graph(self, render_variables: bool) -> None:
+ self._create_vertices()
+ self._build_edges()
+ self.calculate_encryption_attribute()
+ if render_variables:
+ logging.info(f"Rendering variables, graph has {len(self.vertices)} vertices and {len(self.edges)} edges")
+ renderer = VariableRenderer(self)
+ renderer.render_variables_from_local_graph()
+ self.update_vertices_breadcrumbs_and_module_connections()
+
+ def _create_vertices(self) -> None:
+ logging.info("Creating vertices")
+ self.vertices: List[TerraformBlock] = [None] * len(self.module.blocks) # type: ignore
+ for i, block in enumerate(self.module.blocks):
+ self.vertices[i] = block
+
+ self.vertices_by_block_type[block.block_type].append(i)
+ self.vertices_block_name_map[block.block_type][block.name].append(i)
+
+ if block.block_type == BlockType.MODULE:
+ # map between file paths and module vertices indexes from that file
+ self.map_path_to_module.setdefault(block.path, []).append(i)
+
+ self.vertices_by_module_dependency[block.module_dependency][block.block_type].append(i)
+ self.vertices_by_module_dependency_by_name[block.module_dependency][block.block_type][block.name].append(i)
+
+ self.in_edges[i] = []
+ self.out_edges[i] = []
+
+ def _set_variables_values_from_modules(self) -> List[Undetermined]:
+ undetermined_values: List[Undetermined] = []
+ for module_vertex_id in self.vertices_by_block_type.get(BlockType.MODULE, []):
+ module_vertex = self.vertices[module_vertex_id]
+ for attribute_name, attribute_value in module_vertex.attributes.items():
+ matching_variables = self.vertices_block_name_map.get(BlockType.VARIABLE, {}).get(attribute_name, [])
+ for variable_vertex_id in matching_variables:
+ variable_dir = os.path.dirname(self.vertices[variable_vertex_id].path)
+ # TODO: module_vertex.path is always a string and the retrieved dict value is a nested list
+ # therefore this condition is always false. Fixing it results in some variables not being rendered.
+ # see test: tests.graph.terraform.variable_rendering.test_render_scenario.TestRendererScenarios.test_account_dirs_and_modules
+ if module_vertex.path in self.module_dependency_map.get(variable_dir, []):
+ has_var_reference = get_referenced_vertices_in_value(
+ value=attribute_value, aliases={}, resources_types=self.get_resources_types_in_graph()
+ )
+ if has_var_reference:
+ undetermined_values.append(
+ {
+ "module_vertex_id": module_vertex_id,
+ "attribute_name": attribute_name,
+ "variable_vertex_id": variable_vertex_id,
+ }
+ )
+ var_default_value = self.vertices[variable_vertex_id].attributes.get("default")
+ if (
+ not has_var_reference
+ or not var_default_value
+ or get_referenced_vertices_in_value(
+ value=var_default_value, aliases={}, resources_types=self.get_resources_types_in_graph()
+ )
+ ):
+ self.update_vertex_attribute(
+ variable_vertex_id, "default", attribute_value, module_vertex_id, attribute_name
+ )
+ return undetermined_values
+
+ def process_undetermined_values(self, undetermined_values: List[Undetermined]) -> None:
+ for undetermined in undetermined_values:
+ module_vertex = self.vertices[undetermined["module_vertex_id"]]
+ value = module_vertex.attributes.get(undetermined["attribute_name"])
+ if not get_referenced_vertices_in_value(
+ value=value, aliases={}, resources_types=self.get_resources_types_in_graph()
+ ):
+ self.update_vertex_attribute(
+ undetermined["variable_vertex_id"],
+ "default",
+ value,
+ undetermined["module_vertex_id"],
+ undetermined["attribute_name"],
+ )
+
+ def _get_aliases(self) -> Dict[str, Dict[str, BlockType]]:
+ """
+ :return aliases: map between alias names that are found inside the blocks and the block type their aliased to.
+ """
+ return {
+ vertex.name: {CustomAttributes.BLOCK_TYPE: vertex.block_type}
+ for vertex in self.vertices
+ if "alias" in vertex.attributes
+ }
+
+ def get_module_vertices_mapping(self) -> None:
+ """
+ For each vertex, if it's originated in a module import, add to the vertex the index of the
+ matching module vertex as 'source_module'
+ """
+ block_dirs_to_modules: Dict[str, Set[int]] = {}
+ for dir_name, paths_to_modules in self.module_dependency_map.items():
+ # for each directory, find the module vertex that imported it
+ if block_dirs_to_modules.get(dir_name):
+ continue
+ for path_to_module in paths_to_modules:
+ if not path_to_module:
+ continue
+ path_to_module_str = unify_dependency_path(path_to_module)
+ module_list = self.map_path_to_module.get(path_to_module_str, [])
+ for module_index in module_list:
+ module_vertex = self.vertices[module_index]
+ module_vertex_dir = self.get_dirname(module_vertex.path)
+ module_source = module_vertex.attributes.get("source", [""])[0]
+ if self._get_dest_module_path(module_vertex_dir, module_source) == dir_name:
+ block_dirs_to_modules.setdefault(dir_name, set()).add(module_index)
+
+ for vertex in self.vertices:
+ # match the right module vertex according to the vertex path directory
+ module_indices = block_dirs_to_modules.get(self.get_dirname(vertex.path), set())
+ if module_indices:
+ vertex.source_module = module_indices
+
+ def _build_edges(self) -> None:
+ logging.info("Creating edges")
+ self.get_module_vertices_mapping()
+ aliases = self._get_aliases()
+ for origin_node_index, vertex in enumerate(self.vertices):
+ for attribute_key in vertex.attributes:
+ if attribute_key in reserved_attribute_names or attribute_has_nested_attributes(
+ attribute_key, vertex.attributes
+ ):
+ continue
+ referenced_vertices = get_referenced_vertices_in_value(
+ value=vertex.attributes[attribute_key],
+ aliases=aliases,
+ resources_types=self.get_resources_types_in_graph(),
+ )
+ for vertex_reference in referenced_vertices:
+ # for certain blocks such as data and resource, the block name is composed from several parts.
+ # the purpose of the loop is to avoid not finding the node if the name has several parts
+ sub_values = [remove_index_pattern_from_str(sub_value) for sub_value in vertex_reference.sub_parts]
+ for i in range(len(sub_values)):
+ reference_name = join_trimmed_strings(char_to_join=".", str_lst=sub_values, num_to_trim=i)
+ if vertex.module_dependency:
+ dest_node_index = self._find_vertex_index_relative_to_path(
+ vertex_reference.block_type, reference_name, vertex.path, vertex.module_dependency
+ )
+ if dest_node_index == -1:
+ dest_node_index = self._find_vertex_index_relative_to_path(
+ vertex_reference.block_type, reference_name, vertex.path, vertex.path
+ )
+ else:
+ dest_node_index = self._find_vertex_index_relative_to_path(
+ vertex_reference.block_type, reference_name, vertex.path, vertex.module_dependency
+ )
+ if dest_node_index > -1 and origin_node_index > -1:
+ if vertex_reference.block_type == BlockType.MODULE:
+ try:
+ self._connect_module(
+ sub_values, attribute_key, self.vertices[dest_node_index], origin_node_index
+ )
+ except Exception as e:
+ logging.warning(
+ f"Module {self.vertices[dest_node_index]} does not have source attribute, skipping"
+ )
+ logging.warning(e, stack_info=True)
+ else:
+ self._create_edge(origin_node_index, dest_node_index, attribute_key)
+ break
+
+ if vertex.block_type == BlockType.MODULE and vertex.attributes.get('source'):
+ target_path = vertex.path
+ if vertex.module_dependency != "":
+ target_path = unify_dependency_path([vertex.module_dependency, vertex.path])
+ dest_module_path = self._get_dest_module_path(self.get_dirname(vertex.path), vertex.attributes['source'][0])
+ target_variables = list(filter(lambda index: self.get_dirname(self.vertices[index].path) == dest_module_path,
+ self.vertices_by_module_dependency.get(target_path, {}).get(BlockType.VARIABLE, [])))
+ for attribute, value in vertex.attributes.items():
+ if attribute in MODULE_RESERVED_ATTRIBUTES:
+ continue
+ target_variable = next((v for v in target_variables if self.vertices[v].name == attribute), None)
+ if target_variable is not None:
+ self._create_edge(target_variable, origin_node_index, "default")
+ elif vertex.block_type == BlockType.TF_VARIABLE:
+ # Assuming the tfvars file is in the same directory as the variables file (best practice)
+ target_variables = list(
+ filter(lambda index: self.get_dirname(self.vertices[index].path) == self.get_dirname(vertex.path),
+ self.vertices_block_name_map.get(BlockType.VARIABLE, {}).get(vertex.name, [])))
+ if len(target_variables) == 1:
+ self._create_edge(target_variables[0], origin_node_index, "default")
+
+ def _create_edge(self, origin_vertex_index: int, dest_vertex_index: int, label: str) -> None:
+ if origin_vertex_index == dest_vertex_index:
+ return
+ edge = Edge(origin_vertex_index, dest_vertex_index, label)
+ self.edges.append(edge)
+ self.out_edges[origin_vertex_index].append(edge)
+ self.in_edges[dest_vertex_index].append(edge)
+
+ def _connect_module(
+ self, sub_values: List[str], attribute_key: str, module_node: TerraformBlock, origin_node_index: int
+ ) -> None:
+ """
+ :param sub_values: list of sub values of the attribute value.
+ example: given 'instance_type = module.child.myoutput',
+ then attribute_key = instance_type, sub_values = ['child', 'myoutput']
+ :param attribute_key: the name of the attribute that has module block as value
+ :param module_node: the graph node of the module
+
+ The function receives a node of a block of type BlockType.Module, and finds all the nodes of blocks that belong to this
+ module, and creates edges between them.
+ """
+ curr_module_dir = self.get_dirname(module_node.path)
+ dest_module_source = module_node.attributes["source"][0]
+ dest_module_path = self._get_dest_module_path(curr_module_dir, dest_module_source)
+
+ if len(sub_values) > 1:
+ block_name_in_other_module = sub_values[1]
+ output_blocks_with_name = self.vertices_block_name_map.get(BlockType.OUTPUT, {}).get(
+ block_name_in_other_module, []
+ )
+ for vertex_index in output_blocks_with_name:
+ vertex = self.vertices[vertex_index]
+ if (self.get_dirname(vertex.path) == dest_module_path) and (
+ vertex.module_dependency == module_node.module_dependency # The vertex is in the same file
+ or self.get_abspath(vertex.module_dependency)
+ == self.get_abspath(module_node.path) # The vertex is in the correct dependency path
+ ):
+ self._create_edge(origin_node_index, vertex_index, attribute_key)
+ self.vertices[origin_node_index].add_module_connection(attribute_key, vertex_index)
+ break
+
+ def _get_dest_module_path(self, curr_module_dir: str, dest_module_source: str) -> str:
+ """
+ :param curr_module_dir: current source directory
+ :param dest_module_source: the value of module.source
+ :return: the real path in the local file system of the dest module
+ """
+ dest_module_path = Path()
+ if is_local_path(curr_module_dir, dest_module_source):
+ dest_module_path = Path(curr_module_dir) / dest_module_source
+ else:
+ try:
+ if not self.relative_paths_cache.get(dest_module_source):
+ self.relative_paths_cache[dest_module_source] = list(Path(self.module.source_dir).rglob(dest_module_source))
+ dest_module_path = next(
+ (path for path in self.relative_paths_cache.get(dest_module_source)), dest_module_path
+ )
+ except NotImplementedError as e:
+ if 'Non-relative patterns are unsupported' in str(e):
+ return ""
+ raise e
+ return os.path.realpath(dest_module_path)
+
+ def _find_vertex_index_relative_to_path(
+ self, block_type: BlockType, name: str, block_path: str, module_path: str
+ ) -> int:
+ relative_vertices = []
+ possible_vertices = self.vertices_by_module_dependency_by_name.get(module_path, {}).get(block_type, {}).get(name, [])
+ for vertex_index in possible_vertices:
+ vertex = self.vertices[vertex_index]
+ if self.get_dirname(vertex.path) == self.get_dirname(block_path):
+ relative_vertices.append(vertex_index)
+
+ if len(relative_vertices) == 1:
+ relative_vertex = relative_vertices[0]
+ else:
+ relative_vertex = self._find_vertex_with_longest_path_match(relative_vertices, block_path)
+ return relative_vertex
+
+ def _find_vertex_with_longest_path_match(self, relevant_vertices_indexes: List[int], origin_path: str) -> int:
+ vertex_index_with_longest_common_prefix = -1
+ longest_common_prefix = ""
+ for vertex_index in relevant_vertices_indexes:
+ vertex = self.vertices[vertex_index]
+ common_prefix = os.path.commonpath([os.path.realpath(vertex.path), os.path.realpath(origin_path)])
+ if len(common_prefix) > len(longest_common_prefix):
+ vertex_index_with_longest_common_prefix = vertex_index
+ longest_common_prefix = common_prefix
+ return vertex_index_with_longest_common_prefix
+
+ def get_vertices_hash_codes_to_attributes_map(self) -> Dict[str, Dict[str, Any]]:
+ return {vertex.get_hash(): vertex.get_attribute_dict() for vertex in self.vertices}
+
+ def order_edges_by_hash_codes(self) -> Dict[str, Edge]:
+ edges = {}
+ for edge in self.edges:
+ edge_data = {
+ "edge_label": edge.label,
+ "from_vertex_hash": self.get_vertex_hash_by_index(vertex_index=edge.origin),
+ "to_vertex_hash": self.get_vertex_hash_by_index(vertex_index=edge.dest),
+ }
+ edge_hash = calculate_hash(edge_data)
+ edges[edge_hash] = edge
+ return edges
+
+ def get_vertex_attributes_by_index(self, index: int) -> Dict[str, Any]:
+ return self.vertices[index].get_attribute_dict()
+
+ def get_vertices_with_degrees_conditions(
+ self, out_degree_cond: Callable[[int], bool], in_degree_cond: Callable[[int], bool]
+ ) -> List[int]:
+ vertices_with_out_degree = {
+ vertex_index for vertex_index in self.out_edges.keys() if out_degree_cond(len(self.out_edges[vertex_index]))
+ }
+ vertices_with_in_degree = {
+ vertex_index for vertex_index in self.in_edges.keys() if in_degree_cond(len(self.in_edges[vertex_index]))
+ }
+
+ return list(vertices_with_in_degree.intersection(vertices_with_out_degree))
+
+ def get_vertex_hash_by_index(self, vertex_index: int) -> str:
+ return self.vertex_hash_cache.setdefault(vertex_index, self.vertices[vertex_index].get_hash())
+
+ def get_in_edges(self, end_vertices: List[int]) -> List[Edge]:
+ res = []
+ for vertex in end_vertices:
+ res.extend(self.in_edges.get(vertex, []))
+ return self.sort_edged_by_dest_out_degree(res)
+
+ def sort_edged_by_dest_out_degree(self, edges: List[Edge]) -> List[Edge]:
+ edged_by_out_degree: Dict[int, List[Edge]] = {}
+ for edge in edges:
+ dest_out_degree = len(self.out_edges[edge.dest])
+ edged_by_out_degree.setdefault(dest_out_degree, []).append(edge)
+ sorted_edges = []
+ for degree in sorted(edged_by_out_degree.keys()):
+ sorted_edges.extend(edged_by_out_degree[degree])
+ return sorted_edges
+
+ def update_vertex_attribute(
+ self,
+ vertex_index: int,
+ attribute_key: str,
+ attribute_value: Any,
+ change_origin_id: int,
+ attribute_at_dest: Optional[Union[str, List[str]]],
+ ) -> None:
+ previous_breadcrumbs = []
+ attribute_at_dest = self.vertices[change_origin_id].find_attribute(attribute_at_dest)
+ if attribute_at_dest:
+ previous_breadcrumbs = self.vertices[change_origin_id].changed_attributes.get(attribute_at_dest, [])
+ self.vertices[vertex_index].update_attribute(
+ attribute_key, attribute_value, change_origin_id, previous_breadcrumbs
+ )
+
+ def update_vertices_configs(self) -> None:
+ for vertex in self.vertices:
+ changed_attributes = list(vertex.changed_attributes.keys())
+ changed_attributes = filter_sub_keys(changed_attributes)
+ self.update_vertex_config(vertex, changed_attributes)
+
+ @staticmethod
+ def update_vertex_config(vertex: TerraformBlock, changed_attributes: Union[List[str], Dict[str, Any]]) -> None:
+ updated_config = deepcopy(vertex.config)
+ if vertex.block_type != BlockType.LOCALS:
+ parts = vertex.name.split(".")
+ start = 0
+ end = 1
+ while end <= len(parts):
+ cur_key = ".".join(parts[start:end])
+ if cur_key in updated_config:
+ updated_config = updated_config[cur_key]
+ start = end
+ end += 1
+ for changed_attribute in changed_attributes:
+ new_value = vertex.attributes.get(changed_attribute, None)
+ if new_value is not None:
+ if vertex.block_type == BlockType.LOCALS:
+ changed_attribute = changed_attribute.replace(vertex.name + ".", "")
+ updated_config = update_dictionary_attribute(updated_config, changed_attribute, new_value)
+
+ if len(changed_attributes) > 0:
+ if vertex.block_type == BlockType.LOCALS:
+ updated_config = updated_config.get(vertex.name)
+ update_dictionary_attribute(vertex.config, vertex.name, updated_config)
+
+ def get_resources_types_in_graph(self) -> List[str]:
+ return self.module.get_resources_types()
+
+ def update_vertices_breadcrumbs_and_module_connections(self) -> None:
+ """
+ The function processes each vertex's breadcrumbs:
+ 1. Get more data to each vertex in breadcrumb (name, path, hash and type)
+ 2. If a breadcrumb is originated in a different module, it will have 'module_connection'=True
+ 3. If a vertex has a 'source module' we will add a special breadcrumb for it
+ """
+ for vertex in self.vertices:
+ for attribute_key, breadcrumbs_list in vertex.changed_attributes.items():
+ hash_breadcrumbs = []
+ for vertex_id in breadcrumbs_list:
+ v = self.vertices[vertex_id]
+ breadcrumb = v.get_export_data()
+ breadcrumb["module_connection"] = self._determine_if_module_connection(breadcrumbs_list, v)
+ hash_breadcrumbs.append(breadcrumb)
+ vertex.breadcrumbs[attribute_key] = hash_breadcrumbs
+ if len(vertex.source_module) == 1:
+ m = self.vertices[list(vertex.source_module)[0]]
+ source_module_data = [m.get_export_data()]
+ while len(m.source_module) == 1:
+ m = self.vertices[list(m.source_module)[0]]
+ source_module_data.append(m.get_export_data())
+ source_module_data.reverse()
+ vertex.breadcrumbs[CustomAttributes.SOURCE_MODULE] = source_module_data
+
+ @staticmethod
+ def _determine_if_module_connection(breadcrumbs_list: List[int], vertex_in_breadcrumbs: TerraformBlock) -> bool:
+ """
+ :param breadcrumbs_list: list of vertex's breadcrumbs
+ :param vertex_in_breadcrumbs: one of the vertices in the breadcrumb list
+ :return: True if vertex_in_breadcrumbs has in its module_connections at least one of the vertices in breadcrumbs_list
+ """
+ if not vertex_in_breadcrumbs.module_connections:
+ return False
+ for connection_list in vertex_in_breadcrumbs.module_connections.values():
+ if any(i in breadcrumbs_list for i in connection_list):
+ return True
+ return False
+
+ def calculate_encryption_attribute(self) -> None:
+ for vertex_index in self.vertices_by_block_type.get(BlockType.RESOURCE, []):
+ vertex = self.vertices[vertex_index]
+ resource_type = vertex.id.split(".")[0]
+ encryption_conf = ENCRYPTION_BY_RESOURCE_TYPE.get(resource_type)
+ if encryption_conf:
+ attributes = vertex.get_attribute_dict()
+ is_encrypted, reason = encryption_conf.is_encrypted(attributes)
+ # TODO: Does not support possible dependency (i.e. S3 Object being encrypted due to S3 Bucket config)
+ vertex.attributes[CustomAttributes.ENCRYPTION] = (
+ EncryptionValues.ENCRYPTED.value if is_encrypted else EncryptionValues.UNENCRYPTED.value
+ )
+ vertex.attributes[CustomAttributes.ENCRYPTION_DETAILS] = reason
+
+ def get_dirname(self, path: str) -> str:
+ dir_name = self.dirname_cache.get(path)
+ if not dir_name:
+ dir_name = os.path.dirname(path)
+ self.dirname_cache[path] = dir_name
+ return dir_name
+
+ def get_abspath(self, path: str) -> str:
+ dir_name = self.abspath_cache.get(path)
+ if not dir_name:
+ dir_name = os.path.abspath(path)
+ self.abspath_cache[path] = dir_name
+ return dir_name
diff --git a/checkov/terraform/graph_builder/utils.py b/checkov/terraform/graph_builder/utils.py
new file mode 100644
index 0000000000..994efcf401
--- /dev/null
+++ b/checkov/terraform/graph_builder/utils.py
@@ -0,0 +1,302 @@
+from typing import Tuple
+import os
+import re
+from typing import Union, List, Any, Dict, Optional, Callable, overload
+
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+MODULE_DEPENDENCY_PATTERN_IN_PATH = r"\[.+\#.+\]"
+
+
+def is_local_path(root_dir: str, source: str) -> bool:
+ # https://www.terraform.io/docs/modules/sources.html#local-paths
+ return (
+ source.startswith("./")
+ or source.startswith("/./")
+ or source.startswith("../")
+ or source in os.listdir(root_dir)
+ )
+
+
+def remove_module_dependency_in_path(path: str) -> Tuple[str, str, str]:
+ """
+ :param path: path that looks like "dir/main.tf[other_dir/x.tf#0]
+ :return: separated path from module dependency: dir/main.tf, other_dir/x.tf
+ """
+ module_dependency = re.findall(MODULE_DEPENDENCY_PATTERN_IN_PATH, path)
+ if re.findall(MODULE_DEPENDENCY_PATTERN_IN_PATH, path):
+ path = re.sub(MODULE_DEPENDENCY_PATTERN_IN_PATH, "", path)
+ module_and_num = extract_module_dependency_path(module_dependency)
+ return path, module_and_num[0], module_and_num[1]
+
+
+def extract_module_dependency_path(module_dependency: List[str]) -> List[str]:
+ """
+ :param module_dependency: a list looking like ['[path_to_module.tf#0]']
+ :return: the path without enclosing array and index: 'path_to_module.tf'
+ """
+ if not module_dependency:
+ return ["", ""]
+ if isinstance(module_dependency, list) and len(module_dependency) > 0:
+ module_dependency = module_dependency[0]
+ return module_dependency[1:-1].split("#")
+
+BLOCK_TYPES_STRINGS = ["var", "local", "module", "data"]
+FUNC_CALL_PREFIX_PATTERN = r"([.a-zA-Z]+)\("
+INTERPOLATION_PATTERN = "[${}]"
+INTERPOLATION_EXPR = r"\$\{([^\}]*)\}"
+INDEX_PATTERN = r"\[([0-9]+)\]"
+MAP_ATTRIBUTE_PATTERN = r"\[\"([^\d\W]\w*)\"\]"
+
+
+class VertexReference:
+ def __init__(self, block_type: Union[str, BlockType], sub_parts: List[str], origin_value: str) -> None:
+ self.block_type: BlockType = block_type_str_to_enum(block_type) if isinstance(block_type, str) else block_type
+ self.sub_parts = sub_parts
+ self.origin_value = origin_value
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, VertexReference):
+ return NotImplemented
+ return (
+ self.block_type == other.block_type
+ and self.sub_parts == other.sub_parts
+ and self.origin_value == other.origin_value
+ )
+
+ def __str__(self) -> str:
+ return f"{self.block_type} sub_parts = {self.sub_parts}, origin = {self.origin_value}"
+
+
+def get_vertices_references(
+ str_value: str, aliases: Dict[str, Dict[str, BlockType]], resources_types: List[str]
+) -> List[VertexReference]:
+ vertices_references = []
+ words_in_str_value = str_value.split()
+
+ for word in words_in_str_value:
+ if word.startswith(".") or word.startswith("/."):
+ # check if word is a relative path
+ continue
+
+ interpolations = re.split(INTERPOLATION_EXPR, word)
+ for interpolation_content in interpolations:
+ for w in interpolation_content.split(","):
+ word_sub_parts = w.split(".")
+ if len(word_sub_parts) <= 1 or word_sub_parts[0].isnumeric():
+ # if the word doesn't contain a '.' char, or if the first part before the dot is a number
+ continue
+
+ suspected_block_type = word_sub_parts[0]
+ if suspected_block_type in BLOCK_TYPES_STRINGS:
+ # matching cases like 'var.x'
+ vertex_reference = VertexReference(
+ block_type=suspected_block_type, sub_parts=word_sub_parts[1:], origin_value=w
+ )
+ if vertex_reference not in vertices_references:
+ vertices_references.append(vertex_reference)
+ continue
+
+ vertex_reference_alias = get_vertex_reference_from_alias(suspected_block_type, aliases, word_sub_parts)
+ if vertex_reference_alias and vertex_reference_alias not in vertices_references:
+ vertex_reference_alias.origin_value = w
+ # matching cases where the word is referring an alias
+ vertices_references.append(vertex_reference_alias)
+ continue
+
+ # matching cases like 'aws_vpc.main'
+ if word_sub_parts[0] in resources_types:
+ block_name = word_sub_parts[0] + "." + word_sub_parts[1]
+ word_sub_parts = [block_name] + word_sub_parts[2:]
+ vertex_reference = VertexReference(
+ block_type=BlockType.RESOURCE, sub_parts=word_sub_parts, origin_value=w
+ )
+ if vertex_reference not in vertices_references:
+ vertices_references.append(vertex_reference)
+
+ return vertices_references
+
+
+def block_type_str_to_enum(block_type_str: str) -> BlockType:
+ if block_type_str == "var":
+ return BlockType.VARIABLE
+ if block_type_str == "local":
+ return BlockType.LOCALS
+ return BlockType().get(block_type_str)
+
+
+def block_type_enum_to_str(block_type: BlockType) -> str:
+ if block_type == BlockType.VARIABLE:
+ return "var"
+ if block_type == BlockType.LOCALS:
+ return "local"
+ return block_type
+
+
+def get_vertex_reference_from_alias(
+ block_type_str: str, aliases: Dict[str, Dict[str, BlockType]], val: List[str]
+) -> Optional[VertexReference]:
+ block_type = ""
+ if block_type_str in aliases:
+ block_type = aliases[block_type_str][CustomAttributes.BLOCK_TYPE]
+ aliased_provider = ".".join(val)
+ if aliased_provider in aliases:
+ block_type = aliases[aliased_provider][CustomAttributes.BLOCK_TYPE]
+ if block_type:
+ return VertexReference(block_type=block_type, sub_parts=val, origin_value="")
+ return None
+
+
+def remove_function_calls_from_str(str_value: str) -> str:
+ # remove start of function calls:: 'length(aws_vpc.main) > 0 ? aws_vpc.main[0].cidr_block : ${var.x}' --> 'aws_vpc.main) > 0 ? aws_vpc.main[0].cidr_block : ${var.x}'
+ str_value = re.sub(FUNC_CALL_PREFIX_PATTERN, "", str_value)
+ # remove ')'
+ return re.sub(r"[)]+", "", str_value)
+
+
+def remove_index_pattern_from_str(str_value: str) -> str:
+ str_value = re.sub(INDEX_PATTERN, "", str_value)
+ str_value = str_value.replace("[", " [ ")
+ str_value = str_value.replace("]", " ] ")
+ return str_value
+
+
+def remove_interpolation(str_value: str, replace_str: str = " ") -> str:
+ return re.sub(INTERPOLATION_PATTERN, replace_str, str_value)
+
+
+def replace_map_attribute_access_with_dot(str_value: str) -> str:
+ split_by_identifiers = re.split(MAP_ATTRIBUTE_PATTERN, str_value)
+ new_split = []
+ for split_part in split_by_identifiers:
+ if split_part.startswith("."):
+ split_part = split_part[1:]
+ if split_part.endswith("."):
+ split_part = split_part[:-1]
+ new_split.append(split_part)
+
+ return ".".join(new_split)
+
+
+DEFAULT_CLEANUP_FUNCTIONS: List[Callable[[str], str]] = [
+ remove_function_calls_from_str,
+ remove_index_pattern_from_str,
+ replace_map_attribute_access_with_dot,
+ remove_interpolation,
+]
+
+
+def get_referenced_vertices_in_value(
+ value: Union[str, List[str], Dict[str, str]],
+ aliases: Dict[str, Dict[str, BlockType]],
+ resources_types: List[str],
+ cleanup_functions: Optional[List[Callable[[str], str]]] = None,
+) -> List[VertexReference]:
+ if cleanup_functions is None:
+ cleanup_functions = DEFAULT_CLEANUP_FUNCTIONS
+ references_vertices = []
+
+ if isinstance(value, list):
+ for sub_value in value:
+ references_vertices += get_referenced_vertices_in_value(
+ sub_value, aliases, resources_types, cleanup_functions
+ )
+
+ if isinstance(value, dict):
+ for sub_value in value.values():
+ references_vertices += get_referenced_vertices_in_value(
+ sub_value, aliases, resources_types, cleanup_functions
+ )
+
+ if isinstance(value, str):
+ if cleanup_functions:
+ for func in cleanup_functions:
+ value = func(value)
+ references_vertices = get_vertices_references(value, aliases, resources_types)
+
+ return references_vertices
+
+
+@overload
+def update_dictionary_attribute(config: List[Any], key_to_update: str, new_value: Any) -> List[Any]:
+ ...
+
+
+@overload
+def update_dictionary_attribute(config: Dict[str, Any], key_to_update: str, new_value: Any) -> Dict[str, Any]:
+ ...
+
+
+def update_dictionary_attribute(
+ config: Union[List[Any], Dict[str, Any]], key_to_update: str, new_value: Any
+) -> Union[List[Any], Dict[str, Any]]:
+ key_parts = key_to_update.split(".")
+ if isinstance(config, dict):
+ if config.get(key_parts[0]) is not None:
+ key = key_parts[0]
+ if len(key_parts) == 1:
+ if isinstance(config[key], list) and not isinstance(new_value, list):
+ new_value = [new_value]
+ config[key] = new_value
+ return config
+ else:
+ config[key] = update_dictionary_attribute(config[key], ".".join(key_parts[1:]), new_value)
+ else:
+ for key in config:
+ config[key] = update_dictionary_attribute(config[key], key_to_update, new_value)
+ if isinstance(config, list):
+ for i in range(len(config)):
+ config[i] = update_dictionary_attribute(config[i], key_to_update, new_value)
+
+ return config
+
+
+def filter_sub_keys(key_list: List[str]) -> List[str]:
+ filtered_key_list = []
+ for key in key_list:
+ if not any(other_key != key and other_key.startswith(key) for other_key in key_list):
+ filtered_key_list.append(key)
+ return filtered_key_list
+
+
+def generate_possible_strings_from_wildcards(origin_string: str, max_entries: int = 10) -> List[str]:
+ max_entries = int(os.environ.get("MAX_WILDCARD_ARR_SIZE", max_entries))
+ generated_strings = [origin_string]
+ if not origin_string:
+ return []
+ if "*" not in origin_string:
+ return generated_strings
+
+ locations_of_wildcards = []
+ for i, char in enumerate(origin_string):
+ if char == "*":
+ locations_of_wildcards.append(i)
+ locations_of_wildcards.reverse()
+
+ for wildcard_index in locations_of_wildcards:
+ new_generated_strings = []
+ for s in generated_strings:
+ before_wildcard = s[:wildcard_index]
+ after_wildcard = s[wildcard_index + 1 :]
+ for i in range(max_entries):
+ new_generated_strings.append(before_wildcard + str(i) + after_wildcard)
+ generated_strings = new_generated_strings
+
+ # if origin_string == "ingress.*.cidr_blocks", check for "ingress.cidr_blocks" too
+ generated_strings.append("".join(origin_string.split(".*")))
+ return generated_strings
+
+
+def attribute_has_nested_attributes(attribute_key: str, attributes: Dict[str, Any]) -> bool:
+ """
+ :param attribute_key: key inside the `attributes` dictionary
+ :param attributes:
+ :return: True if attribute_key has inner attributes.
+ Example 1: if attributes.keys == [key1, key.key2], type(attributes[key1]) is dict and return True for key1
+ Example 2: if attributes.keys == [key1, key1.0], type(attributes[key1]) is list and return True for key1
+ """
+ prefixes_with_attribute_key = [a for a in attributes.keys() if a.startswith(attribute_key) and a != attribute_key]
+ if not any(re.findall(r"\.\d+", a) for a in prefixes_with_attribute_key):
+ # if there aro no numeric parts in the key such as key1.0.key2
+ return isinstance(attributes[attribute_key], dict)
+ return isinstance(attributes[attribute_key], list) or isinstance(attributes[attribute_key], dict)
diff --git a/checkov/terraform/graph_manager.py b/checkov/terraform/graph_manager.py
new file mode 100644
index 0000000000..8e422dcb40
--- /dev/null
+++ b/checkov/terraform/graph_manager.py
@@ -0,0 +1,31 @@
+import logging
+from typing import List
+
+from checkov.common.graph.graph_manager import GraphManager
+from checkov.terraform.graph_builder.local_graph import TerraformLocalGraph
+from checkov.terraform.parser import Parser
+
+
+class TerraformGraphManager(GraphManager):
+ def __init__(self, db_connector, source=''):
+ super().__init__(db_connector=db_connector, parser=Parser(), source=source)
+
+ def build_graph_from_source_directory(self, source_dir, render_variables=True, local_graph_class=TerraformLocalGraph,
+ parsing_errors=None, download_external_modules=False, excluded_paths: List[str]=None):
+ logging.info('Parsing HCL files in source dir')
+ module, module_dependency_map, tf_definitions = \
+ self.parser.parse_hcl_module(source_dir, self.source, download_external_modules, parsing_errors, excluded_paths=excluded_paths)
+
+ logging.info('Building graph from parsed module')
+ local_graph = local_graph_class(module, module_dependency_map)
+ local_graph.build_graph(render_variables=render_variables)
+
+ return local_graph, tf_definitions
+
+ def build_graph_from_definitions(self, definitions, render_variables=True):
+ module, module_dependency_map, _ = \
+ self.parser.parse_hcl_module_from_tf_definitions(definitions, '', self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph.build_graph(render_variables=render_variables)
+
+ return local_graph
diff --git a/checkov/terraform/module_loading/loaders/git_loader.py b/checkov/terraform/module_loading/loaders/git_loader.py
index c6e775cc5e..e8c7b1646b 100644
--- a/checkov/terraform/module_loading/loaders/git_loader.py
+++ b/checkov/terraform/module_loading/loaders/git_loader.py
@@ -13,13 +13,10 @@ def _is_matching_loader(self):
def _load_module(self) -> ModuleContent:
try:
- module_source = self.module_source.lstrip('git::')
- if module_source.startswith('ssh:'):
- return ModuleContent(dir=None, failed_url=self.module_source)
+ module_source = self.module_source.replace('git::', '')
git_getter = GitGetter(module_source, create_clone_and_result_dirs=False)
git_getter.temp_dir = self.dest_dir
- git_getter.do_get()
- return_dir = self.dest_dir
+ return_dir = git_getter.do_get()
if self.inner_module:
return_dir = os.path.join(self.dest_dir, self.inner_module)
return ModuleContent(dir=return_dir)
diff --git a/checkov/terraform/module_loading/loaders/local_path_loader.py b/checkov/terraform/module_loading/loaders/local_path_loader.py
index 8a8571b4cb..0d5e975626 100644
--- a/checkov/terraform/module_loading/loaders/local_path_loader.py
+++ b/checkov/terraform/module_loading/loaders/local_path_loader.py
@@ -11,7 +11,7 @@ def __init__(self) -> None:
def _is_matching_loader(self) -> bool:
return self.module_source.startswith("./") or self.module_source.startswith("../") \
- or self.module_source.startswith(self.current_dir) or self.module_source.startswith('/')
+ or self.module_source.startswith(self.current_dir)
def _load_module(self) -> ModuleContent:
module_path = os.path.normpath(os.path.join(self.current_dir, self.module_source))
diff --git a/checkov/terraform/module_loading/registry.py b/checkov/terraform/module_loading/registry.py
index 2fbaf661a7..a4fe833358 100644
--- a/checkov/terraform/module_loading/registry.py
+++ b/checkov/terraform/module_loading/registry.py
@@ -1,5 +1,6 @@
import logging
import os
+import hashlib
from typing import Optional, List
from checkov.common.util.consts import DEFAULT_EXTERNAL_MODULES_DIR
@@ -20,7 +21,13 @@ def load(self, current_dir: str, source: str, source_version: Optional[str]) ->
Search all registered loaders for the first one which is able to load the module source type. For more
information, see `loader.ModuleLoader.load`.
"""
- local_dir = os.path.join(current_dir, self.external_modules_folder_name, source)
+ if os.name == 'nt':
+ # For windows, due to limitations in the allowed characters for path names, the hash of the source is used.
+ # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
+ source_hash = hashlib.md5(source.encode()) # nosec
+ local_dir = os.path.join(current_dir, self.external_modules_folder_name, source_hash.hexdigest())
+ else:
+ local_dir = os.path.join(current_dir, self.external_modules_folder_name, source)
inner_module = ''
next_url = source
last_exception = None
diff --git a/checkov/terraform/parser.py b/checkov/terraform/parser.py
index ef17c2fc60..eaf358a374 100644
--- a/checkov/terraform/parser.py
+++ b/checkov/terraform/parser.py
@@ -1,45 +1,65 @@
+import copy
import json
import logging
import os
import re
+from copy import deepcopy
+import datetime
from pathlib import Path
-from typing import Mapping, Optional, Dict, Any, List, Callable, Tuple
+from typing import Optional, Dict, Mapping, Set, Tuple, Callable, Any, List
+from json import dumps, loads, JSONEncoder
import deep_merge
import hcl2
-import jmespath
-from dataclasses import dataclass
+from lark import Tree
-from checkov.common.runners.base_runner import filter_ignored_directories
+from checkov.common.runners.base_runner import filter_ignored_paths
from checkov.common.util.consts import DEFAULT_EXTERNAL_MODULES_DIR, RESOLVED_MODULE_ENTRY_NAME
-from checkov.common.util.type_forcers import convert_str_to_bool
-from checkov.common.variables.context import EvaluationContext, VarReference
-from checkov.terraform.module_loading.registry import ModuleLoaderRegistry
-from checkov.terraform.module_loading.registry import module_loader_registry as default_ml_registry
+from checkov.common.variables.context import EvaluationContext
+from checkov.terraform.checks.utils.dependency_path_handler import unify_dependency_path
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.module import Module
+from checkov.terraform.graph_builder.utils import remove_module_dependency_in_path
+from checkov.terraform.module_loading.registry import module_loader_registry as default_ml_registry, \
+ ModuleLoaderRegistry
+from checkov.terraform.parser_utils import eval_string, find_var_blocks
+external_modules_download_path = os.environ.get('EXTERNAL_MODULES_DIR', DEFAULT_EXTERNAL_MODULES_DIR)
-LOGGER = logging.getLogger(__name__)
-_SIMPLE_TYPES = frozenset(["string", "number", "bool"])
+class DefinitionsEncoder(JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, set):
+ return list(obj)
+ elif isinstance(obj, Tree):
+ return str(obj)
+ elif isinstance(obj, datetime.date):
+ return str(obj)
+ return super().default(obj)
-_RESOURCE_REF_PATTERN = re.compile(r'[\d\w]+(\.[\d\w]+)+')
-
-def _filter_ignored_directories(d_names):
- filter_ignored_directories(d_names)
- [d_names.remove(d) for d in list(d_names) if d in [default_ml_registry.external_modules_folder_name]]
+def _filter_ignored_paths(root, paths, excluded_paths):
+ filter_ignored_paths(root, paths, excluded_paths)
+ [paths.remove(path) for path in list(paths) if path in [default_ml_registry.external_modules_folder_name]]
class Parser:
- def __init__(self):
+ def __init__(self, module_class=Module):
+ self.module_class = module_class
self._parsed_directories = set()
+ # This ensures that we don't try to double-load modules
+ # Tuple is , , (see _load_modules)
+ self._loaded_modules: Set[Tuple[str, int, str]] = set()
+ self.external_variables_data = []
+
def _init(self, directory: str, out_definitions: Optional[Dict],
out_evaluations_context: Dict[str, Dict[str, EvaluationContext]],
out_parsing_errors: Dict[str, Exception],
env_vars: Mapping[str, str],
download_external_modules: bool,
- external_modules_download_path: str, evaluate_variables):
+ external_modules_download_path: str,
+ excluded_paths: Optional[List[str]] = None):
self.directory = directory
self.out_definitions = out_definitions
self.out_evaluations_context = out_evaluations_context
@@ -47,7 +67,6 @@ def _init(self, directory: str, out_definitions: Optional[Dict],
self.env_vars = env_vars
self.download_external_modules = download_external_modules
self.external_modules_download_path = external_modules_download_path
- self.evaluate_variables = evaluate_variables
if self.out_evaluations_context is None:
self.out_evaluations_context = {}
@@ -55,6 +74,7 @@ def _init(self, directory: str, out_definitions: Optional[Dict],
self.out_parsing_errors = {}
if self.env_vars is None:
self.env_vars = dict(os.environ)
+ self.excluded_paths = excluded_paths
def _check_process_dir(self, directory):
if directory not in self._parsed_directories:
@@ -68,12 +88,13 @@ def parse_directory(self, directory: str, out_definitions: Optional[Dict],
out_parsing_errors: Dict[str, Exception] = None,
env_vars: Mapping[str, str] = None,
download_external_modules: bool = False,
- external_modules_download_path: str = DEFAULT_EXTERNAL_MODULES_DIR, evaluate_variables=True):
- self._init(directory, out_definitions, out_evaluations_context, out_parsing_errors, env_vars, download_external_modules, external_modules_download_path, evaluate_variables)
+ external_modules_download_path: str = DEFAULT_EXTERNAL_MODULES_DIR,
+ excluded_paths: Optional[List[str]] = None):
+ self._init(directory, out_definitions, out_evaluations_context, out_parsing_errors, env_vars,
+ download_external_modules, external_modules_download_path, excluded_paths)
self._parsed_directories.clear()
default_ml_registry.download_external_modules = download_external_modules
default_ml_registry.external_modules_folder_name = external_modules_download_path
-
self._parse_directory(dir_filter=lambda d: self._check_process_dir(d))
@staticmethod
@@ -109,18 +130,28 @@ def _parse_directory(self, include_sub_dirs: bool = True,
True will allow processing. The argument will be the absolute path of
the directory.
"""
+ keys_referenced_as_modules: Set[str] = set()
if include_sub_dirs:
for sub_dir, d_names, f_names in os.walk(self.directory):
- _filter_ignored_directories(d_names)
+ _filter_ignored_paths(sub_dir, d_names, self.excluded_paths)
+ _filter_ignored_paths(sub_dir, f_names, self.excluded_paths)
if dir_filter(os.path.abspath(sub_dir)):
- self._internal_dir_load(sub_dir, module_loader_registry, dir_filter)
+ self._internal_dir_load(sub_dir, module_loader_registry, dir_filter,
+ keys_referenced_as_modules)
else:
- self._internal_dir_load(self.directory, module_loader_registry, dir_filter)
+ self._internal_dir_load(self.directory, module_loader_registry, dir_filter,
+ keys_referenced_as_modules)
+
+ # Ensure anything that was referenced as a module is removed
+ for key in keys_referenced_as_modules:
+ if key in self.out_definitions:
+ del self.out_definitions[key]
def _internal_dir_load(self, directory: str,
module_loader_registry: ModuleLoaderRegistry,
dir_filter: Callable[[str], bool],
+ keys_referenced_as_modules: Set[str],
specified_vars: Optional[Mapping[str, str]] = None,
module_load_context: Optional[str] = None):
"""
@@ -143,7 +174,7 @@ def _internal_dir_load(self, directory: str,
var_value_and_file_map: Dict[str, Tuple[Any, str]] = {}
hcl_tfvars: Optional[os.DirEntry] = None
json_tfvars: Optional[os.DirEntry] = None
- auto_vars_files: Optional[List[os.DirEntry]] = None # lazy creation
+ auto_vars_files: Optional[List[os.DirEntry]] = None # lazy creation
for file in os.scandir(directory):
# Ignore directories and hidden files
try:
@@ -169,12 +200,12 @@ def _internal_dir_load(self, directory: str,
continue
# Resource files
- if file.name.endswith(".tf.json") or file.name.endswith(".tf"):
+ if file.name.endswith(".tf"): # TODO: add support for .tf.json
data = _load_or_die_quietly(file, self.out_parsing_errors)
else:
continue
- if not data: # failed loads or empty files
+ if not data: # failed loads or empty files
continue
self.out_definitions[file.path] = data
@@ -192,6 +223,7 @@ def _internal_dir_load(self, directory: str,
default_value = var_definition.get("default")
if default_value is not None and isinstance(default_value, list):
+ self.external_variables_data.append((var_name, default_value[0], file.path))
var_value_and_file_map[var_name] = default_value[0], file.path
# Stage 2: Load vars in proper order:
@@ -205,25 +237,31 @@ def _internal_dir_load(self, directory: str,
# their filenames.
# Overriding everything else, variables form `specified_vars`, which are considered
# directly set.
- for key, value in self.env_vars.items(): # env vars
+ for key, value in self.env_vars.items(): # env vars
if not key.startswith("TF_VAR_"):
continue
var_value_and_file_map[key[7:]] = value, f"env:{key}"
- if hcl_tfvars: # terraform.tfvars
- data = _load_or_die_quietly(hcl_tfvars, self.out_parsing_errors)
+ self.external_variables_data.append((key[7:], value, f"env:{key}"))
+ if hcl_tfvars: # terraform.tfvars
+ data = _load_or_die_quietly(hcl_tfvars, self.out_parsing_errors,
+ clean_definitions=False)
if data:
- var_value_and_file_map.update({k: (v, hcl_tfvars.path) for k, v in data.items()})
- if json_tfvars: # terraform.tfvars.json
+ var_value_and_file_map.update({k: (_safe_index(v, 0), hcl_tfvars.path) for k, v in data.items()})
+ self.external_variables_data.extend([(k, _safe_index(v, 0), hcl_tfvars.path) for k, v in data.items()])
+ if json_tfvars: # terraform.tfvars.json
data = _load_or_die_quietly(json_tfvars, self.out_parsing_errors)
if data:
var_value_and_file_map.update({k: (v, json_tfvars.path) for k, v in data.items()})
- if auto_vars_files: # *.auto.tfvars / *.auto.tfvars.json
+ self.external_variables_data.extend([(k, v, json_tfvars.path) for k, v in data.items()])
+ if auto_vars_files: # *.auto.tfvars / *.auto.tfvars.json
for var_file in sorted(auto_vars_files, key=lambda e: e.name):
data = _load_or_die_quietly(var_file, self.out_parsing_errors)
if data:
var_value_and_file_map.update({k: (v, var_file.path) for k, v in data.items()})
- if specified_vars: # specified
+ self.external_variables_data.extend([(k, v, var_file.path) for k, v in data.items()])
+ if specified_vars: # specified
var_value_and_file_map.update({k: (v, "manual specification") for k, v in specified_vars.items()})
+ self.external_variables_data.extend([(k, v, "manual specification") for k, v in specified_vars.items()])
# IMPLEMENTATION NOTE: When resolving `module.` references, access to the entire data map is needed. It
# may be a little overboard, but I don't want to just pass the entire data map down
@@ -232,199 +270,67 @@ def _internal_dir_load(self, directory: str,
# map for a particular module reference. (Might be OCD, but...)
module_data_retrieval = lambda module_ref: self.out_definitions.get(module_ref)
- # Stage 3: Variable resolution round 1 - no modules yet
- if self.evaluate_variables:
- self._process_vars_and_locals(directory, var_value_and_file_map, module_data_retrieval)
-
# Stage 4: Load modules
- self._load_modules(self.directory, module_loader_registry, dir_filter, module_load_context)
-
- # Stage 5: Variable resolution round 2 - now with modules
- if self.evaluate_variables:
- self._process_vars_and_locals(directory, var_value_and_file_map, module_data_retrieval)
-
- def _process_vars_and_locals(self, directory: str,
- var_value_and_file_map: Dict[str, Tuple[Any, str]],
- module_data_retrieval: Callable[[str], Dict[str, Any]]):
- locals_values = {}
- for file_data in self.out_definitions.values():
- file_locals = file_data.get("locals")
- if not file_locals:
- continue
- for k, v in file_locals[0].items():
- locals_values[k] = v[0]
-
- # Processing is done in a loop to deal with chained references and the like.
- # Loop while the data is being changed, stop when no more changes are happening.
- # To ensure there's not some kind of oscillation, a cap of 25 passes is in place.
- # More than a couple loops isn't normally expected.
- # NOTE: If this approach proves to be a performance liability, a DAG will be needed.
- loop_count = 0
- for i in range(0, 25):
- loop_count += 1
-
- made_change = False
- # Put out file layer here so the context works inside the loop
- for file, file_data in self.out_definitions.items():
- eval_context_dict = self.out_evaluations_context.get(file)
- if eval_context_dict is None:
- eval_context_dict = {}
- self.out_evaluations_context[file] = eval_context_dict
- # out_evaluations_context[os.path.join(directory, file)] = {
- # var_name: EvaluationContext(os.path.relpath(file.path, directory))
- # }
-
- if self._process_vars_and_locals_loop(file_data,
- eval_context_dict,
- os.path.relpath(file, directory),
- var_value_and_file_map, locals_values,
- file_data.get("resource"),
- file_data.get("module"),
- module_data_retrieval,
- directory):
- made_change = True
-
- if len(eval_context_dict) == 0:
- del self.out_evaluations_context[file]
- if not made_change:
- break
-
- LOGGER.debug("Processing variables took %d loop iterations", loop_count)
-
- def _process_vars_and_locals_loop(self, out_definitions: Dict,
- eval_map_by_var_name: Dict[str, EvaluationContext], relative_file_path: str,
- var_value_and_file_map: Dict[str, Tuple[Any, str]],
- locals_values: Dict[str, Any],
- resource_list: Optional[List[Dict[str, Any]]],
- module_list: Optional[List[Dict[str, Any]]],
- module_data_retrieval: Callable[[str], Dict[str, Any]],
- root_directory: str,
- outer_context: str = "") -> bool:
-
- # Generic loop for handling a source of key/value tuples (e.g., enumerate() or .items())
- def process_items_helper(key_value_iterator, data_map, context, allow_str_bool_translation: bool):
- made_change = False
- for key, value in list(key_value_iterator()): # Copy to list to allow deletion
- new_context = f"{context}/{key}" if len(context) != 0 else key
-
- if isinstance(value, str):
- altered_value = value
-
- had_pattern_match = False
-
- for match in _find_var_blocks(value):
- var_base = match.var_only
-
- # Expressions such as (from variable definition):
- # type = string
- # are turned into:
- # "type = ${string}"
- if var_base in _SIMPLE_TYPES and match.full_str == value:
- altered_value = var_base
- had_pattern_match = True
- else:
- replaced = _handle_single_var_pattern(var_base, var_value_and_file_map,
- locals_values, resource_list,
- module_list, module_data_retrieval,
- eval_map_by_var_name,
- new_context, value, root_directory)
- if replaced != var_base:
- if match.full_str == value:
- altered_value = replaced
- else:
- altered_value = altered_value.replace(match.full_str, str(replaced))
- had_pattern_match = True
-
- if not had_pattern_match:
- # tomap is annoying because the curly braces in the string break the regex. Rather than
- # coming up with something that's significantly more complex, we'll special case this
- # check. Only do this when there wasn't a pattern match to make sure there are no
- # variables lingering in the map value.
- # https://www.terraform.io/docs/configuration/functions/tomap.html
- if value.startswith("${tomap(") and value.endswith(")}"):
- trimmed = value[8:-2]
- trimmed = trimmed.replace(":", "=") # converted to colons by parser #shrug
- altered_value = _eval_string(trimmed)
- if altered_value is None:
- altered_value = value
- continue
-
- if not isinstance(altered_value, dict):
- continue
-
- # If there is a string and anything else, convert to string
- had_string = False
- had_something_else = False
- for k, v in altered_value.items():
- if v == "${True}":
- altered_value[k] = True
- v = True
- elif v == "${False}":
- altered_value[k] = False
- v = False
-
- if isinstance(v, str):
- had_string = True
- if had_something_else:
- break
- else:
- had_something_else = True
- if had_string:
- break
- if had_string and had_something_else:
- altered_value = {k: _tostring(v) for k, v in altered_value.items()}
- # Same as above, regex can blow this up
- # (see parser scenario: tostring_function, INNER_CURLY
- elif value.startswith("${tostring(\"") and value.endswith("\")}"):
- altered_value = value[12:-3]
-
- # Support HCL 0.11 optional boolean syntax - evaluate "true" to true and "false" to false
- #
- # `allow_str_bool_translation` exists because we want to prevent conversion in a dict
- # which is a direct value. See the "MIXED_BOOL" variable in the "tomap_function" parser
- # scenario for a situation which worked incorrectly without this.
- # NOTE: This is probably not a big deal to be removed if this causes problems in other
- # places. The MIXED_BOOL test case is technically correct with the TF spec, but
- # isn't essential operation for Checkov.
- elif allow_str_bool_translation and value == "true":
- altered_value = True
- elif allow_str_bool_translation and value == "false":
- altered_value = False
-
- if value != altered_value:
- LOGGER.debug(f"Resolve: %s --> %s", value, altered_value)
- data_map[key] = altered_value
- made_change = True
- elif isinstance(value, dict):
- if self._process_vars_and_locals_loop(value, eval_map_by_var_name, relative_file_path,
- var_value_and_file_map,
- locals_values, resource_list,
- module_list, module_data_retrieval, root_directory,
- new_context):
- made_change = True
-
- elif isinstance(value, list):
- if len(value) > 0 and value[0] != value:
- if process_items_helper(lambda: enumerate(value), value, new_context, True):
- made_change = True
- # Some special cases that should be pruned from datasets
- if value == [None] or value == [{}] or value == [[]] or len(value) == 0:
- del data_map[key]
- return made_change
-
- return process_items_helper(out_definitions.items, out_definitions, outer_context, False)
+ # This stage needs to be done in a loop (again... alas, no DAG) because modules might not
+ # be loadable until other modules are loaded. This happens when parameters to one module
+ # depend on the output of another. For such cases, the base module must be loaded, then
+ # a parameter resolution pass needs to happen, then the second module can be loaded.
+ #
+ # One gotcha is that we need to make sure we load all modules at some point, even if their
+ # parameters don't resolve. So, if we hit a spot where resolution doesn't change anything
+ # and there are still modules to be loaded, they will be forced on the next pass.
+ force_final_module_load = False
+ for i in range(0, 10): # circuit breaker - no more than 10 loops
+ logging.debug("Module load loop %d", i)
+
+ # Stage 4a: Load eligible modules
+ has_more_modules = self._load_modules(directory, module_loader_registry,
+ dir_filter, module_load_context,
+ keys_referenced_as_modules,
+ force_final_module_load)
+
+ # Stage 4b: Variable resolution round 2 - now with (possibly more) modules
+ made_var_changes = False
+ if not has_more_modules:
+ break # nothing more to do
+ elif not made_var_changes:
+ # If there are more modules to load but no variables were resolved, then to a final module
+ # load, forcing things through without complete resolution.
+ force_final_module_load = True
def _load_modules(self, root_dir: str, module_loader_registry: ModuleLoaderRegistry,
- dir_filter: Callable[[str], bool], module_load_context: Optional[str]):
+ dir_filter: Callable[[str], bool], module_load_context: Optional[str],
+ keys_referenced_as_modules: Set[str], ignore_unresolved_params: bool = False) -> bool:
+ """
+ Load modules which have not already been loaded and can be loaded (don't have unresolved parameters).
+
+ :param ignore_unresolved_params: If true, not-yet-loaded modules will be loaded even if they are
+ passed parameters that are not fully resolved.
+ :return: True if there were modules that were not loaded due to unresolved
+ parameters.
+ """
all_module_definitions = {}
all_module_evaluations_context = {}
+ skipped_a_module = False
for file in list(self.out_definitions.keys()):
+ # Don't process a file in a directory other than the directory we're processing. For example,
+ # if we're down dealing with //something.tf, we don't want to rescan files
+ # up in .
+ if os.path.dirname(file) != root_dir:
+ continue
+ # Don't process a file reference which has already been processed
+ if file.endswith("]"):
+ continue
+
file_data = self.out_definitions.get(file)
+ if file_data is None:
+ continue
module_calls = file_data.get("module")
if not module_calls or not isinstance(module_calls, list):
continue
for module_index, module_call in enumerate(module_calls):
+
if not isinstance(module_call, dict):
continue
@@ -433,14 +339,37 @@ def _load_modules(self, root_dir: str, module_loader_registry: ModuleLoaderRegis
if not isinstance(module_call_data, dict):
continue
+ module_address = (file, module_index, module_call_name)
+ if module_address in self._loaded_modules:
+ continue
+
+ # Variables being passed to module, "source" and "version" are reserved
+ specified_vars = {k: v[0] if isinstance(v, list) else v for k, v in module_call_data.items()
+ if k != "source" and k != "version"}
+
+ if not ignore_unresolved_params:
+ has_unresolved_params = False
+ for k, v in specified_vars.items():
+ if not is_acceptable_module_param(v) or not is_acceptable_module_param(k):
+ has_unresolved_params = True
+ break
+ if has_unresolved_params:
+ skipped_a_module = True
+ continue
+ self._loaded_modules.add(module_address)
+
source = module_call_data.get("source")
if not source or not isinstance(source, list):
continue
source = source[0]
+ if not isinstance(source, str):
+ logging.debug(f"Skipping loading of {module_call_name} as source is not a string, it is: {source}")
+ continue
# Special handling for local sources to make sure we aren't double-parsing
if source.startswith("./") or source.startswith("../"):
- source = os.path.normpath(os.path.join(os.path.dirname(_remove_module_dependency_in_path(file)), source))
+ source = os.path.normpath(
+ os.path.join(os.path.dirname(_remove_module_dependency_in_path(file)), source))
version = module_call_data.get("version", "latest")
if version and isinstance(version, list):
@@ -450,16 +379,15 @@ def _load_modules(self, root_dir: str, module_loader_registry: ModuleLoaderRegis
if not content.loaded():
continue
- # Variables being passed to module, "source" and "version" are reserved
- specified_vars = {k: v[0] for k, v in module_call_data.items()
- if k != "source" and k != "version"}
-
- if not dir_filter(os.path.abspath(content.path())):
- continue
- self._internal_dir_load(directory=content.path(), module_loader_registry=module_loader_registry,
- dir_filter=dir_filter, specified_vars=specified_vars, module_load_context=module_load_context)
+ self._internal_dir_load(directory=content.path(),
+ module_loader_registry=module_loader_registry,
+ dir_filter=dir_filter, specified_vars=specified_vars,
+ module_load_context=module_load_context,
+ keys_referenced_as_modules=keys_referenced_as_modules)
- module_definitions = {path: self.out_definitions[path] for path in list(self.out_definitions.keys()) if os.path.dirname(path) == content.path()}
+ module_definitions = {path: self.out_definitions[path] for path in
+ list(self.out_definitions.keys()) if
+ os.path.dirname(path) == content.path()}
if not module_definitions:
continue
@@ -486,15 +414,16 @@ def _load_modules(self, root_dir: str, module_loader_registry: ModuleLoaderRegis
# has not already been added.
keys = list(module_definitions.keys())
for key in keys:
- if key.endswith("]"):
+ if key.endswith("]") or file.endswith("]"):
continue
+ keys_referenced_as_modules.add(key)
new_key = f"{key}[{file}#{module_index}]"
- module_definitions[new_key] = \
- module_definitions[key]
+ module_definitions[new_key] = module_definitions[key]
del module_definitions[key]
del self.out_definitions[key]
-
- resolved_loc_list.append(new_key)
+ if new_key not in resolved_loc_list:
+ resolved_loc_list.append(new_key)
+ resolved_loc_list.sort() # For testing, need predictable ordering
deep_merge.merge(all_module_definitions, module_definitions)
except Exception as e:
@@ -505,191 +434,183 @@ def _load_modules(self, root_dir: str, module_loader_registry: ModuleLoaderRegis
if all_module_definitions:
deep_merge.merge(self.out_definitions, all_module_definitions)
deep_merge.merge(self.out_evaluations_context, all_module_evaluations_context)
+ return skipped_a_module
+
+ def parse_hcl_module(self, source_dir, source, download_external_modules=False, parsing_errors=None, excluded_paths: List[str]=None):
+ tf_definitions = {}
+ self.parse_directory(directory=source_dir, out_definitions=tf_definitions, out_evaluations_context={},
+ out_parsing_errors=parsing_errors if parsing_errors is not None else {},
+ download_external_modules=download_external_modules,
+ external_modules_download_path=external_modules_download_path, excluded_paths=excluded_paths)
+ tf_definitions = self._clean_parser_types(tf_definitions)
+ tf_definitions = self._serialize_definitions(tf_definitions)
+ return self.parse_hcl_module_from_tf_definitions(tf_definitions, source_dir, source)
+
+ def parse_hcl_module_from_tf_definitions(self, tf_definitions, source_dir, source, excluded_paths: List[str]=None):
+ module_dependency_map, tf_definitions, dep_index_mapping = self.get_module_dependency_map(tf_definitions)
+ module = self.get_new_module(source_dir, module_dependency_map, dep_index_mapping)
+ self.add_tfvars(module, source)
+ copy_of_tf_definitions = deepcopy(tf_definitions)
+ for file_path in copy_of_tf_definitions:
+ blocks = copy_of_tf_definitions.get(file_path)
+ for block_type in blocks:
+ try:
+ module.add_blocks(block_type, blocks[block_type], file_path, source)
+ except Exception as e:
+ logging.error(f'Failed to add block {blocks[block_type]}. Error:')
+ logging.error(e, exc_info=True)
+ return module, module_dependency_map, tf_definitions
+ @staticmethod
+ def _clean_parser_types(conf: dict) -> dict:
+ sorted_keys = list(conf.keys())
+ if len(conf.keys()) > 0 and all(isinstance(x, type(list(conf.keys())[0])) for x in conf.keys()):
+ sorted_keys = sorted(filter(lambda x: x is not None, conf.keys()))
+ # Create a new dict where the keys are sorted alphabetically
+ sorted_conf = {key: conf[key] for key in sorted_keys}
+ for attribute, values in sorted_conf.items():
+ if attribute == 'alias':
+ continue
+ if isinstance(values, list):
+ sorted_conf[attribute] = Parser._clean_parser_types_lst(values)
+ elif isinstance(values, dict):
+ sorted_conf[attribute] = Parser._clean_parser_types(conf[attribute])
+ elif isinstance(values, str) and values in ('true', 'false'):
+ sorted_conf[attribute] = True if values == 'true' else False
+ elif isinstance(values, set):
+ sorted_conf[attribute] = Parser._clean_parser_types_lst(list(values))
+ elif isinstance(values, Tree):
+ sorted_conf[attribute] = str(values)
+ return sorted_conf
-def _handle_single_var_pattern(orig_variable: str, var_value_and_file_map: Dict[str, Tuple[Any, str]],
- locals_values: Dict[str, Any],
- resource_list: Optional[List[Dict[str, Any]]],
- module_list: Optional[List[Dict[str, Any]]],
- module_data_retrieval: Callable[[str], Dict[str, Any]],
- eval_map_by_var_name: Dict[str, EvaluationContext],
- context, orig_variable_full, root_directory: str) -> Any:
- if "${" in orig_variable:
- return orig_variable
-
- elif orig_variable.startswith("module."):
- if not module_list:
- return orig_variable
+ @staticmethod
+ def _clean_parser_types_lst(values: list) -> list:
+ for i in range(len(values)):
+ val = values[i]
+ if isinstance(val, dict):
+ values[i] = Parser._clean_parser_types(val)
+ elif isinstance(val, list):
+ values[i] = Parser._clean_parser_types_lst(val)
+ elif isinstance(val, str):
+ if val == 'true':
+ values[i] = True
+ elif val == 'false':
+ values[i] = False
+ elif isinstance(val, set):
+ values[i] = Parser._clean_parser_types_lst(list(val))
+ str_values_in_lst = [val for val in values if isinstance(val, str)]
+ str_values_in_lst.sort()
+ result_values = [val for val in values if not isinstance(val, str)]
+ result_values.extend(str_values_in_lst)
+ return result_values
- # Reference to module outputs, example: 'module.bucket.bucket_name'
- ref_tokens = orig_variable.split(".")
- if len(ref_tokens) != 3:
- return orig_variable # fail safe, can the length ever be something other than 3?
+ @staticmethod
+ def _serialize_definitions(tf_definitions):
+ return loads(dumps(tf_definitions, cls=DefinitionsEncoder))
- try:
- ref_list = jmespath.search(f"[].{ref_tokens[1]}.{RESOLVED_MODULE_ENTRY_NAME}[]", module_list)
- # ^^^^^^^^^^^^^ module name
+ @staticmethod
+ def get_next_vertices(evaluated_files: list, unevaluated_files: list) -> (list, list):
+ """
+ This function implements a lazy separation of levels for the evaluated files. It receives the evaluated
+ files, and returns 2 lists:
+ 1. The next level of files - files from the unevaluated_files which have no unresolved dependency (either
+ no dependency or all dependencies were evaluated).
+ 2. unevaluated - files which have yet to be evaluated, and still have pending dependencies
+
+ Let's say we have this dependency tree:
+ a -> b
+ x -> b
+ y -> c
+ z -> b
+ b -> c
+ c -> d
+
+ The first run will return [a, y, x, z] as the next level since all of them have no dependencies
+ The second run with the evaluated being [a, y, x, z] will return [b] as the next level.
+ Please mind that [c] has some resolved dependencies (from y), but has unresolved dependencies from [b].
+ The third run will return [c], and the fourth will return [d].
+ """
+ next_level, unevaluated, do_not_eval_yet = [], [], []
+ for key in unevaluated_files:
+ found = False
+ for eval_key in evaluated_files:
+ if eval_key in key:
+ found = True
+ break
+ if not found:
+ do_not_eval_yet.append(key.split('[')[0])
+ unevaluated.append(key)
+ else:
+ next_level.append(key)
- if not ref_list or not isinstance(ref_list, list):
- return orig_variable
+ move_to_uneval = list(filter(lambda k: k.split('[')[0] in do_not_eval_yet, next_level))
+ for k in move_to_uneval:
+ next_level.remove(k)
+ unevaluated.append(k)
+ return next_level, unevaluated
- for ref in ref_list:
- module_data = module_data_retrieval(ref)
- if not module_data:
+ @staticmethod
+ def get_module_dependency_map(tf_definitions):
+ """
+ :param tf_definitions, with paths in format 'dir/main.tf[module_dir/main.tf#0]'
+ :return module_dependency_map: mapping between directories and the location of its module definition:
+ {'dir': 'module_dir/main.tf'}
+ :return tf_definitions: with paths in format 'dir/main.tf'
+ """
+ module_dependency_map = {}
+ copy_of_tf_definitions = {}
+ dep_index_mapping = {}
+ definitions_keys = list(tf_definitions.keys())
+ origin_keys = list(filter(lambda k: not k.endswith(']'), definitions_keys))
+ unevaluated_keys = list(filter(lambda k: k.endswith(']'), definitions_keys))
+ for file_path in origin_keys:
+ dir_name = os.path.dirname(file_path)
+ module_dependency_map[dir_name] = [[]]
+ copy_of_tf_definitions[file_path] = deepcopy(tf_definitions[file_path])
+
+ next_level, unevaluated_keys = Parser.get_next_vertices(origin_keys, unevaluated_keys)
+ while next_level:
+ for file_path in next_level:
+ path, module_dependency, module_dependency_num = remove_module_dependency_in_path(file_path)
+ dir_name = os.path.dirname(path)
+ current_deps = deepcopy(module_dependency_map[os.path.dirname(module_dependency)])
+ for dep in current_deps:
+ dep.append(module_dependency)
+ if dir_name not in module_dependency_map:
+ module_dependency_map[dir_name] = current_deps
+ elif current_deps not in module_dependency_map[dir_name]:
+ module_dependency_map[dir_name] += current_deps
+ copy_of_tf_definitions[path] = deepcopy(tf_definitions[file_path])
+ origin_keys.append(path)
+ dep_index_mapping[path] = module_dependency_num
+ next_level, unevaluated_keys = Parser.get_next_vertices(origin_keys, unevaluated_keys)
+ for key, dep_trails in module_dependency_map.items():
+ hashes = set()
+ deduped = []
+ for trail in dep_trails:
+ hash = unify_dependency_path(trail)
+ if hash in hashes:
continue
+ hashes.add(hash)
+ deduped.append(trail)
+ module_dependency_map[key] = deduped
+ return module_dependency_map, copy_of_tf_definitions, dep_index_mapping
- result = _handle_indexing(ref_tokens[2],
- lambda r: jmespath.search(f"output[].{ref_tokens[2]}.value[] | [0]",
- module_data))
- if result:
- logging.debug("Resolved module ref: %s --> %s", orig_variable, result)
- return result
- except ValueError:
- pass
- return orig_variable
-
- elif orig_variable == "True":
- return True
- elif orig_variable == "False":
- return False
-
- elif orig_variable.startswith("var."):
- var_name = orig_variable[4:]
- var_value_and_file = _handle_indexing(var_name, lambda r: var_value_and_file_map.get(r))
- if var_value_and_file is not None:
- var_value, var_file = var_value_and_file
- eval_context = eval_map_by_var_name.get(var_name)
- if eval_context is None:
- eval_map_by_var_name[var_name] = EvaluationContext(os.path.relpath(var_file, root_directory),
- var_value,
- [VarReference(var_name,
- orig_variable_full,
- context)])
- else:
- eval_context.definitions.append(VarReference(var_name, orig_variable_full, context))
- return var_value
- elif orig_variable.startswith("local."):
- var_value = _handle_indexing(orig_variable[6:], lambda r: locals_values.get(r))
- if var_value is not None:
- return var_value
- elif orig_variable.startswith("to") and orig_variable.endswith(")"):
- # https://www.terraform.io/docs/configuration/functions/tobool.html
- if orig_variable.startswith("tobool("):
- bool_variable = orig_variable[7:-1].lower()
- bool_value = convert_str_to_bool(bool_variable)
- if isinstance(bool_value, bool):
- return bool_value
- else:
- return orig_variable
- # https://www.terraform.io/docs/configuration/functions/tolist.html
- elif orig_variable.startswith("tolist("):
- altered_value = _eval_string(orig_variable[7:-1])
- if altered_value is None:
- return orig_variable
- return altered_value if isinstance(altered_value, list) else list(altered_value)
- # NOTE: tomap as handled outside this loop (see below)
- # https://www.terraform.io/docs/configuration/functions/tonumber.html
- elif orig_variable.startswith("tonumber("):
- num_variable = orig_variable[9:-1]
- if num_variable.startswith('"') and num_variable.endswith('"'):
- num_variable = num_variable[1:-1]
- try:
- if "." in num_variable:
- return float(num_variable)
- else:
- return int(num_variable)
- except ValueError:
- return orig_variable
- # https://www.terraform.io/docs/configuration/functions/toset.html
- elif orig_variable.startswith("toset("):
- altered_value = _eval_string(orig_variable[6:-1])
- if altered_value is None:
- return orig_variable
- return set(altered_value)
- # https://www.terraform.io/docs/configuration/functions/tostring.html
- elif orig_variable.startswith("tostring("):
- altered_value = orig_variable[9:-1]
- # Indicates a safe string, all good
- if altered_value.startswith('"') and altered_value.endswith('"'):
- return altered_value[1:-1]
- # Otherwise, need to check for valid types (number or bool)
- bool_value = convert_str_to_bool(altered_value)
- if isinstance(bool_value, bool):
- return bool_value
- else:
- try:
- if "." in altered_value:
- return str(float(altered_value))
- else:
- return str(int(altered_value))
- except ValueError:
- return orig_variable # no change
- elif orig_variable.startswith("merge(") and orig_variable.endswith(")"):
- altered_value = orig_variable[6:-1]
- args = _split_merge_args(altered_value)
- if args is None:
- return orig_variable
- merged_map = {}
- for arg in args:
- if arg.startswith("{"):
- value = _map_string_to_native(arg)
- if value is None:
- return orig_variable
- else:
- value = _handle_single_var_pattern(arg,
- var_value_and_file_map,
- locals_values,
- resource_list,
- module_list,
- module_data_retrieval,
- eval_map_by_var_name,
- context,
- arg,
- root_directory)
- if isinstance(value, dict):
- merged_map.update(value)
- else:
- return orig_variable # don't know what this is, blow out
- return merged_map
- # TODO - format() support, still in progress
- # elif orig_variable.startswith("format(") and orig_variable.endswith(")"):
- # format_tokens = orig_variable[7:-1].split(",")
- # return format_tokens[0].format([_to_native_value(t) for t in format_tokens[1:]])
-
- elif _RESOURCE_REF_PATTERN.match(orig_variable):
- # Reference to resources, example: 'aws_s3_bucket.example.bucket'
- # TODO: handle index into map/list
- try:
- result = jmespath.search(f"[].{orig_variable}[] | [0]", resource_list)
- except ValueError:
- pass
- else:
- if result is not None:
- return result
-
- return orig_variable # fall back to no change
-
+ @staticmethod
+ def get_new_module(source_dir, module_dependency_map, dep_index_mapping):
+ return Module(source_dir, module_dependency_map, dep_index_mapping)
-def _handle_indexing(reference: str, data_source: Callable[[str], Optional[Any]]) -> Optional[Any]:
- if reference.endswith("]") and "[" in reference:
- base_ref = reference[:reference.rindex("[")]
- value = data_source(base_ref)
- reference_val = reference[reference.rindex("[") + 1: -1]
- if isinstance(value, dict):
- return value.get(reference_val)
- elif isinstance(value, list):
- try:
- return value[int(reference_val)]
- except ValueError as e:
- # TODO: handle count.index correctly
- logging.debug(f'Failed to parse index int out of {reference_val}')
- logging.debug(e, stack_info=True)
- return
- else:
- return data_source(reference)
+ def add_tfvars(self, module, source):
+ if not self.external_variables_data:
+ return
+ for (var_name, default, path) in self.external_variables_data:
+ if ".tfvars" in path:
+ block = {var_name: {"default": default}}
+ module.add_blocks(BlockType.TF_VARIABLE, block, path, source)
-def _load_or_die_quietly(file: os.PathLike, parsing_errors: Dict) -> Optional[Mapping]:
+def _load_or_die_quietly(file: os.PathLike, parsing_errors: Dict,
+ clean_definitions: bool = True) -> Optional[Mapping]:
"""
Load JSON or HCL, depending on filename.
:return: None if the file can't be loaded
@@ -699,54 +620,55 @@ def _load_or_die_quietly(file: os.PathLike, parsing_errors: Dict) -> Optional[Ma
file_name = os.path.basename(file_path)
try:
+ logging.debug(f"Parsing {file_path}")
with open(file, "r") as f:
if file_name.endswith(".json"):
- return _clean_bad_definitions(json.load(f))
+ return json.load(f)
else:
- return _clean_bad_definitions(hcl2.load(f))
+ raw_data = hcl2.load(f)
+ non_malformed_definitions = _validate_malformed_definitions(raw_data)
+ if clean_definitions:
+ return _clean_bad_definitions(non_malformed_definitions)
+ else:
+ return non_malformed_definitions
except Exception as e:
- LOGGER.debug(f'failed while parsing file {file}', exc_info=e)
+ logging.debug(f'failed while parsing file {file_path}', exc_info=e)
parsing_errors[file_path] = e
return None
+def _is_valid_block(block):
+ if not isinstance(block, dict):
+ return True
+ entity_name, _ = next(iter(block.items()))
+ if re.fullmatch(r'[^\W0-9][\w-]*', entity_name):
+ return True
+ return False
+
+
+def _validate_malformed_definitions(raw_data):
+ raw_data_cleaned = copy.deepcopy(raw_data)
+ for block_type, blocks in raw_data.items():
+ raw_data_cleaned[block_type] = [block for block in blocks if _is_valid_block(block)]
+
+ return raw_data_cleaned
+
+
def _clean_bad_definitions(tf_definition_list):
return {
- block_type: list(filter(lambda definition_list: block_type == 'locals' or len(definition_list.keys()) == 1, tf_definition_list[block_type]))
+ block_type: list(filter(lambda definition_list: block_type == 'locals' or
+ not isinstance(definition_list, dict)
+ or len(definition_list.keys()) == 1,
+ tf_definition_list[block_type]))
for block_type in tf_definition_list.keys()
}
-def _eval_string(value: str) -> Optional[Any]:
- try:
- value_string = value.replace("'", '"')
- parsed = hcl2.loads(f'eval = {value_string}\n') # NOTE: newline is needed
- return parsed["eval"][0]
- except Exception:
- return None
-
-
def _to_native_value(value: str) -> Any:
if value.startswith('"') or value.startswith("'"):
return value[1:-1]
else:
- return _eval_string(value)
-
-
-def _tostring(value: Any) -> str:
- if value is True:
- return "true"
- elif value is False:
- return "false"
- return str(value)
-
-
-def _map_string_to_native(value: str) -> Optional[Dict]:
- try:
- value_string = value.replace("'", '"')
- return json.loads(value_string)
- except Exception:
- return None
+ return eval_string(value)
def _remove_module_dependency_in_path(path):
@@ -760,153 +682,37 @@ def _remove_module_dependency_in_path(path):
return path
-def _split_merge_args(value: str) -> Optional[List[str]]:
- """
- Split arguments of a merge function. For example, "merge(local.one, local.two)" would
- call this function with a value of "local.one, local.two" which would return
- ["local.one", "local.two"]. If the value cannot be unpacked, None will be returned.
- """
- if not value:
- return None
-
- # There are a number of splitting scenarios depending on whether variables or
- # direct maps are used:
- # merge({tag1="foo"},{tag2="bar"})
- # merge({tag1="foo"},local.some_tags)
- # merge(local.some_tags,{tag2="bar"})
- # merge(local.some_tags,local.some_other_tags)
- # Also, the number of arguments can vary, things can be nested, strings are evil...
- # See tests/terraform/test_parser_internals.py for many examples.
-
- to_return = []
- current_arg_buffer = ""
- processing_str_escape = False
- inside_collection_stack = [] # newest at position 0, contains the terminator for the collection
- for c in value:
- if c == "," and not inside_collection_stack:
- current_arg_buffer = current_arg_buffer.strip()
- # Note: can get a zero-length buffer when there's a double comman. This can
- # happen with multi-line args (see parser_internals test)
- if len(current_arg_buffer) != 0:
- to_return.append(current_arg_buffer)
- current_arg_buffer = ""
- else:
- current_arg_buffer += c
-
- processing_str_escape = _str_parser_loop_collection_helper(c,
- inside_collection_stack,
- processing_str_escape)
-
- current_arg_buffer = current_arg_buffer.strip()
- if len(current_arg_buffer) > 0:
- to_return.append(current_arg_buffer)
-
- if len(to_return) == 0:
+def _safe_index(sequence_hopefully, index) -> Optional[Any]:
+ try:
+ return sequence_hopefully[index]
+ except IndexError as e:
+ logging.debug(f'Failed to parse index int ({index}) out of {sequence_hopefully}')
+ logging.debug(e, stack_info=True)
return None
- return to_return
-@dataclass
-class VarBlockMatch:
- full_str: str # Example: ${local.foo}
- var_only: str # Example: local.fop
-
-
-def _find_var_blocks(value: str) -> List[VarBlockMatch]:
- """
- Find and return all the var blocks within a given string.
+def is_acceptable_module_param(value: Any) -> bool:
"""
-
- # Note: This used to be implemented with a regex: r'\${([^{}]+?)}')
- # That found ${...} without a {} inside. However, this caused issues with things containing maps
- # which needed to be processed.
-
- to_return: List[VarBlockMatch] = []
- eval_buffer = ""
- in_eval = False
- preceding_dollar = False
- processing_str_escape = False
- inside_collection_stack: List[str] = [] # newest at position 0, contains terminator for collection
- for c in value:
- if c == "$":
- if preceding_dollar: # ignore double $
- preceding_dollar = False
- continue
- preceding_dollar = True
- elif c == "{" and preceding_dollar:
- # NOTE: An eval block can start within another eval block, in which case we drop the old
- # stuff and process this one.
- in_eval = True
- eval_buffer = "" # reset buffer
- inside_collection_stack.clear() # reset stack
- processing_str_escape = False
- preceding_dollar = False
- continue
- else:
- preceding_dollar = False
-
- if not in_eval:
- continue
-
- if c == "}" and not inside_collection_stack:
- eval_buffer = eval_buffer.strip()
- if len(eval_buffer) == 0:
- # Something went wrong because we have an empty arg. Blow out.
- return []
- to_return.append(VarBlockMatch("${" + eval_buffer + "}", eval_buffer))
- eval_buffer = ""
- in_eval = False
- continue
- else:
- eval_buffer += c
-
- processing_str_escape = _str_parser_loop_collection_helper(c, inside_collection_stack,
- processing_str_escape)
-
- return to_return
-
-
-def _str_parser_loop_collection_helper(c: str, inside_collection_stack: List[str],
- processing_str_escape: bool) -> bool:
+ This function determines if a value should be passed to a module as a parameter. We don't want to pass
+ unresolved var, local or module references because they can't be resolved from the module, so they need
+ to be resolved prior to being passed down.
"""
- This function handles dealing with tracking when a char-by-char state loop is inside a
- "collection" (map, array index, method args, string).
-
- :param c: Active character
- :param inside_collection_stack: Stack of terminators for collections. This will be modified by
- this function. The active terminator will be at position 0.
+ value_type = type(value)
+ if value_type is dict:
+ for k, v in value.items():
+ if not is_acceptable_module_param(v) or not is_acceptable_module_param(k):
+ return False
+ return True
+ if value_type is set or value_type is list:
+ for v in value:
+ if not is_acceptable_module_param(v):
+ return False
+ return True
+ if not value_type is str:
+ return True
- :return: value to set for `processing_str_escape`
- """
- inside_a_string = False
- if inside_collection_stack:
- terminator = inside_collection_stack[0]
-
- if terminator == '"' or terminator == "'":
- if processing_str_escape:
- processing_str_escape = False
- return processing_str_escape
- elif c == "\\":
- processing_str_escape = True
- return processing_str_escape
- else:
- inside_a_string = True
-
- if c == terminator:
- del inside_collection_stack[0]
- return processing_str_escape
-
- if not inside_a_string:
- if c == '"':
- inside_collection_stack.insert(0, '"')
- elif c == "'":
- inside_collection_stack.insert(0, "'")
- elif c == "{":
- inside_collection_stack.insert(0, "}")
- elif c == "[":
- inside_collection_stack.insert(0, "]")
- elif c == "(":
- inside_collection_stack.insert(0, ")")
-
- return processing_str_escape
+ for vbm in find_var_blocks(value):
+ if vbm.is_simple_var():
+ return False
+ return True
diff --git a/checkov/terraform/parser_functions.py b/checkov/terraform/parser_functions.py
new file mode 100644
index 0000000000..ce1e3cb691
--- /dev/null
+++ b/checkov/terraform/parser_functions.py
@@ -0,0 +1,169 @@
+import logging
+from typing import Dict, List, Union, Any
+
+from checkov.common.util.type_forcers import convert_str_to_bool
+from checkov.terraform.parser_utils import eval_string, split_merge_args, string_to_native, to_string
+
+#
+# Functions defined in this file implement terraform functions.
+#
+# Inputs:
+# - First arg (unnamed) - the value string provided to the function
+# - "var_resolver" - function pointer to resolve variable/local references and such
+# - "function_name" - name of the function being called (mainly useful for error reporting when a
+# function isn't defined)
+# These may be expanded over time, so accepting kwargs (via `**`) is recommended.
+#
+# If the value cannot be processed, `FUNCTION_FAILED` should be returned.
+#
+
+FUNCTION_FAILED = "____FUNCTION_FAILED____"
+
+
+def merge(original, var_resolver, **_):
+ # https://www.terraform.io/docs/language/functions/merge.html
+ args = split_merge_args(original)
+ if args is None:
+ return FUNCTION_FAILED
+ merged_map = {}
+ for arg in args:
+ if arg.startswith("{"):
+ arg_value = string_to_native(arg)
+ if arg_value is None:
+ return FUNCTION_FAILED
+ else:
+ arg_value = var_resolver(arg)
+ if isinstance(arg_value, dict):
+ merged_map.update(arg_value)
+ else:
+ return FUNCTION_FAILED # don't know what this is, blow out
+ return merged_map
+
+
+def concat(original, var_resolver, **_):
+ # https://www.terraform.io/docs/language/functions/concat.html
+ args = split_merge_args(original)
+ if args is None:
+ return FUNCTION_FAILED
+ merged_list = []
+ for arg in args:
+ if arg.startswith("["):
+ value = eval_string(arg)
+ if value is None:
+ logging.debug("Unable to convert to list: %s", arg)
+ return FUNCTION_FAILED
+ else:
+ value = var_resolver(arg)
+ if isinstance(value, list):
+ merged_list.extend(value)
+ else:
+ return FUNCTION_FAILED # don't know what this is, blow out
+ return merged_list
+
+
+def tobool(original: Union[bool, str], **_: Any) -> Union[bool, str]:
+ # https://www.terraform.io/docs/configuration/functions/tobool.html
+ bool_value = convert_str_to_bool(original)
+ return bool_value if isinstance(bool_value, bool) else FUNCTION_FAILED
+
+
+def tonumber(original, **_):
+ # https://www.terraform.io/docs/configuration/functions/tonumber.html
+ if original.startswith('"') and original.endswith('"'):
+ original = original[1:-1]
+ try:
+ if "." in original:
+ return float(original)
+ else:
+ return int(original)
+ except ValueError:
+ return FUNCTION_FAILED
+
+
+def tostring(original, **_):
+ # Indicates a safe string, all good
+ if original.startswith('"') and original.endswith('"'):
+ return original[1:-1]
+ # Otherwise, need to check for valid types (number or bool)
+ bool_value = convert_str_to_bool(original)
+ if isinstance(bool_value, bool):
+ return bool_value
+ else:
+ try:
+ if "." in original:
+ return str(float(original))
+ else:
+ return str(int(original))
+ except ValueError:
+ return FUNCTION_FAILED # no change
+
+
+def tolist(original, **_):
+ # https://www.terraform.io/docs/configuration/functions/tolist.html
+ altered_value = eval_string(original)
+ if altered_value is None:
+ return FUNCTION_FAILED
+ return altered_value if isinstance(altered_value, list) else list(altered_value)
+
+
+def toset(original, **_):
+ # https://www.terraform.io/docs/configuration/functions/toset.html
+ altered_value = eval_string(original)
+ if altered_value is None:
+ return FUNCTION_FAILED
+ return altered_value if isinstance(altered_value, set) else set(altered_value)
+
+
+def tomap(original, **_):
+ # https://www.terraform.io/docs/language/functions/tomap.html
+ original = original.replace(":", "=") # converted to colons by parser #shrug
+
+ altered_value = eval_string(original)
+ if altered_value is None or not isinstance(altered_value, dict):
+ return FUNCTION_FAILED
+ return _check_map_type_consistency(altered_value)
+
+
+def map(original, **_):
+ # https://www.terraform.io/docs/language/functions/map.html
+
+ # NOTE: Splitting by commas is annoying due to possible commas in strings. To avoid
+ # the issue, act like it's a list (to allow comma separation) and let the HCL
+ # parser deal with it. Then iterating the list is easy.
+ converted_to_list = eval_string(f"[{original}]")
+ if converted_to_list is None or len(converted_to_list) & 1: # none or odd number of args
+ return FUNCTION_FAILED
+
+ return create_map(converted_to_list)
+
+
+def create_map(lst: List):
+ new_map = {}
+ for i in range(0, len(lst), 2):
+ new_map[lst[i]] = lst[i + 1]
+ return _check_map_type_consistency(new_map)
+
+
+def _check_map_type_consistency(value: Dict) -> Dict:
+ # If there is a string and anything else, convert to string
+ had_string = False
+ had_something_else = False
+ for k, v in value.items():
+ if v == "${True}":
+ value[k] = True
+ v = True
+ elif v == "${False}":
+ value[k] = False
+ v = False
+
+ if isinstance(v, str):
+ had_string = True
+ if had_something_else:
+ break
+ else:
+ had_something_else = True
+ if had_string:
+ break
+ if had_string and had_something_else:
+ value = {k: to_string(v) for k, v in value.items()}
+ return value
diff --git a/checkov/terraform/parser_utils.py b/checkov/terraform/parser_utils.py
new file mode 100644
index 0000000000..cd7bb69694
--- /dev/null
+++ b/checkov/terraform/parser_utils.py
@@ -0,0 +1,301 @@
+import json
+import re
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+import hcl2
+
+
+_FUNCTION_NAME_CHARS = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+
+_ARG_VAR_PATTERN = re.compile(r"[a-zA-Z_]+(\.[a-zA-Z_]+)+")
+
+
+@dataclass
+class VarBlockMatch:
+ full_str: str # Example: ${local.foo}
+ var_only: str # Example: local.fop
+
+ def replace(self, original: str, replaced: str) -> None:
+ self.full_str = self.full_str.replace(original, replaced)
+ self.var_only = self.var_only.replace(original, replaced)
+
+ def is_simple_var(self) -> bool:
+ """
+ Indicates whether or not the value of the var block matches a "simple" var pattern. For example:
+ local.blah, var.foo, module.one.a_resource.
+ """
+ return _ARG_VAR_PATTERN.match(self.var_only) is not None
+
+
+class ParserMode(Enum):
+ # Note: values are just for debugging.
+ EVAL = "${"
+ MAP = "{"
+ STRING_SINGLE_QUOTE = "'"
+ STRING_DOUBLE_QUOTE = '"'
+ PARAMS = "("
+ ARRAY = "["
+ BLANK = " "
+
+ @staticmethod
+ def is_string(mode: "ParserMode") -> bool:
+ return mode == ParserMode.STRING_SINGLE_QUOTE or mode == ParserMode.STRING_DOUBLE_QUOTE
+
+ def __repr__(self) -> str:
+ return str(self.value)
+
+ def __str__(self) -> str:
+ return str(self.value)
+
+
+def find_var_blocks(value: str) -> List[VarBlockMatch]:
+ """
+ Find and return all the var blocks within a given string. Order is important and may contain portions of
+ one another.
+ """
+
+ to_return: List[VarBlockMatch] = []
+
+ mode_stack: List[ParserMode] = []
+ eval_start_pos_stack: List[int] = [] # location of first char inside brackets
+ param_start_pos_stack: List[int] = [] # location of open parens
+ preceding_dollar = False
+ preceding_string_escape = False
+ # NOTE: function calls can be nested, but since param args are only being inspected for variables,
+ # it's alright to ignore outer calls.
+ param_arg_start = -1
+ for index, c in enumerate(value):
+ current_mode = ParserMode.BLANK if not mode_stack else mode_stack[-1]
+
+ # Print statement of power...
+ # print(f"{str(index).ljust(3, ' ')} {c} {'$' if preceding_dollar else ' '} "
+ # f"{'`' if preceding_string_escape else ' '} "
+ # f"{current_mode.ljust(2)} - {mode_stack}")
+
+ if c == "$":
+ if preceding_dollar: # ignore double $
+ preceding_dollar = False
+ continue
+
+ preceding_dollar = True
+ continue
+
+ if c == "{" and preceding_dollar:
+ mode_stack.append(ParserMode.EVAL)
+ eval_start_pos_stack.append(index + 1) # next char
+ preceding_dollar = False
+ continue
+ elif c == "\\" and ParserMode.is_string(current_mode):
+ preceding_string_escape = True
+ continue
+
+ preceding_dollar = False
+
+ if c == "}":
+ if current_mode == ParserMode.EVAL:
+ mode_stack.pop()
+ start_pos = eval_start_pos_stack.pop()
+ eval_string = value[start_pos:index]
+ to_return.append(VarBlockMatch("${" + eval_string + "}", eval_string))
+ elif current_mode == ParserMode.MAP:
+ mode_stack.pop()
+ elif c == "]" and current_mode == ParserMode.ARRAY:
+ mode_stack.pop()
+ elif c == ")" and current_mode == ParserMode.PARAMS:
+ if param_arg_start > 0:
+ param_arg = value[param_arg_start:index].strip()
+ if _ARG_VAR_PATTERN.match(param_arg):
+ to_return.append(VarBlockMatch(param_arg, param_arg))
+ param_arg_start = -1
+
+ mode_stack.pop()
+ start_pos = param_start_pos_stack.pop()
+ # See if these params are for a function call. Back up from the index to determine if there's
+ # a function preceding.
+ function_name_start_index = start_pos
+ for function_index in range(start_pos - 1, 0, -1):
+ if value[function_index] in _FUNCTION_NAME_CHARS:
+ function_name_start_index = function_index
+ else:
+ break
+ # We know now there's a function call here. But, don't call it out if it's directly wrapped
+ # in eval markers.
+ in_eval_markers = False
+ if function_name_start_index >= 2:
+ in_eval_markers = (
+ value[function_name_start_index - 2] == "$" and value[function_name_start_index - 1] == "{"
+ )
+ if function_name_start_index < start_pos and not in_eval_markers:
+ to_return.append(
+ VarBlockMatch(
+ value[function_name_start_index : index + 1], value[function_name_start_index : index + 1]
+ )
+ )
+ elif c == '"':
+ if preceding_string_escape:
+ preceding_string_escape = False
+ continue
+ elif current_mode == ParserMode.STRING_DOUBLE_QUOTE:
+ mode_stack.pop()
+ else:
+ mode_stack.append(ParserMode.STRING_DOUBLE_QUOTE)
+ elif c == "'":
+ if preceding_string_escape:
+ preceding_string_escape = False
+ continue
+ elif current_mode == ParserMode.STRING_SINGLE_QUOTE:
+ mode_stack.pop()
+ else:
+ mode_stack.append(ParserMode.STRING_SINGLE_QUOTE)
+ elif c == "{":
+ # NOTE: Can't be preceded by a dollar sign (that was checked earlier)
+ if not ParserMode.is_string(current_mode):
+ mode_stack.append(ParserMode.MAP)
+ elif c == "[": # do we care?
+ if not ParserMode.is_string(current_mode):
+ mode_stack.append(ParserMode.ARRAY)
+ elif c == "(": # do we care?
+ if not ParserMode.is_string(current_mode):
+ mode_stack.append(ParserMode.PARAMS)
+ param_start_pos_stack.append(index)
+ param_arg_start = index + 1
+ elif c == ",":
+ if current_mode == ParserMode.PARAMS and param_arg_start > 0:
+ param_arg = value[param_arg_start:index].strip()
+ if _ARG_VAR_PATTERN.match(param_arg):
+ to_return.append(VarBlockMatch(param_arg, param_arg))
+ param_arg_start = index + 1
+ elif c == "?" and current_mode == ParserMode.EVAL: # ternary
+ # If what's been processed in the ternary so far is "true" or "false" (boolean or string type)
+ # then nothing special will happen here and only the full expression will be returned.
+ # Anything else will be treated as an unresolved variable block.
+ start_pos = eval_start_pos_stack[-1] # DO NOT pop: there's no separate eval start indicator
+ eval_string = value[start_pos:index].strip()
+
+ # HACK ALERT: For the cases with the trailing quotes, see:
+ # test_hcl2_load_assumptions.py -> test_weird_ternary_string_clipping
+ if eval_string not in {"true", "false", '"true"', '"false"', 'true"', 'false"'}:
+ # REMINDER: The eval string is not wrapped in a eval markers since they didn't really
+ # appear in the original value. If they're put in, substitution doesn't
+ # work properly.
+ to_return.append(VarBlockMatch(eval_string, eval_string))
+
+ preceding_string_escape = False
+
+ return to_return
+
+
+def split_merge_args(value: str) -> Optional[List[str]]:
+ """
+ Split arguments of a merge function. For example, "merge(local.one, local.two)" would
+ call this function with a value of "local.one, local.two" which would return
+ ["local.one", "local.two"]. If the value cannot be unpacked, None will be returned.
+ """
+ if not value:
+ return None
+
+ # There are a number of splitting scenarios depending on whether variables or
+ # direct maps are used:
+ # merge({tag1="foo"},{tag2="bar"})
+ # merge({tag1="foo"},local.some_tags)
+ # merge(local.some_tags,{tag2="bar"})
+ # merge(local.some_tags,local.some_other_tags)
+ # Also, the number of arguments can vary, things can be nested, strings are evil...
+ # See tests/terraform/test_parser_var_blocks.py for many examples.
+
+ to_return = []
+ current_arg_buffer = ""
+ processing_str_escape = False
+ inside_collection_stack: List[str] = [] # newest at position 0, contains the terminator for the collection
+ for c in value:
+ if c == "," and not inside_collection_stack:
+ current_arg_buffer = current_arg_buffer.strip()
+ # Note: can get a zero-length buffer when there's a double comman. This can
+ # happen with multi-line args (see parser_internals test)
+ if len(current_arg_buffer) != 0:
+ to_return.append(current_arg_buffer)
+ current_arg_buffer = ""
+ else:
+ current_arg_buffer += c
+
+ processing_str_escape = _str_parser_loop_collection_helper(c, inside_collection_stack, processing_str_escape)
+
+ current_arg_buffer = current_arg_buffer.strip()
+ if len(current_arg_buffer) > 0:
+ to_return.append(current_arg_buffer)
+
+ if len(to_return) == 0:
+ return None
+ return to_return
+
+
+def _str_parser_loop_collection_helper(c: str, inside_collection_stack: List[str], processing_str_escape: bool) -> bool:
+ """
+ This function handles dealing with tracking when a char-by-char state loop is inside a
+ "collection" (map, array index, method args, string).
+
+ :param c: Active character
+ :param inside_collection_stack: Stack of terminators for collections. This will be modified by
+ this function. The active terminator will be at position 0.
+
+
+ :return: value to set for `processing_str_escape`
+ """
+ inside_a_string = False
+ if inside_collection_stack:
+ terminator = inside_collection_stack[0]
+
+ if terminator == '"' or terminator == "'":
+ if processing_str_escape:
+ processing_str_escape = False
+ return processing_str_escape
+ elif c == "\\":
+ processing_str_escape = True
+ return processing_str_escape
+ else:
+ inside_a_string = True
+
+ if c == terminator:
+ del inside_collection_stack[0]
+ return processing_str_escape
+
+ if not inside_a_string:
+ if c == '"':
+ inside_collection_stack.insert(0, '"')
+ elif c == "'":
+ inside_collection_stack.insert(0, "'")
+ elif c == "{":
+ inside_collection_stack.insert(0, "}")
+ elif c == "[":
+ inside_collection_stack.insert(0, "]")
+ elif c == "(":
+ inside_collection_stack.insert(0, ")")
+
+ return processing_str_escape
+
+
+def eval_string(value: str) -> Optional[Any]:
+ try:
+ value_string = value.replace("'", '"')
+ parsed = hcl2.loads(f"eval = {value_string}\n") # NOTE: newline is needed
+ return parsed["eval"][0]
+ except Exception:
+ return None
+
+
+def string_to_native(value: str) -> Optional[Dict[str, Any]]:
+ try:
+ value_string = value.replace("'", '"')
+ return json.loads(value_string)
+ except Exception:
+ return None
+
+
+def to_string(value: Any) -> str:
+ if value is True:
+ return "true"
+ elif value is False:
+ return "false"
+ return str(value)
diff --git a/checkov/terraform/plan_parser.py b/checkov/terraform/plan_parser.py
index b127ecfb92..94dee61513 100644
--- a/checkov/terraform/plan_parser.py
+++ b/checkov/terraform/plan_parser.py
@@ -1,16 +1,21 @@
-from checkov.terraform.context_parsers.tf_plan import parse
+import itertools
+from typing import Optional, Tuple, Dict, List, Any
-simple_types = [str, int, float, bool]
+from checkov.terraform.context_parsers.tf_plan import parse, dict_node
+from checkov.terraform.context_parsers.tf_plan.node import list_node
+simple_types = (str, int, float, bool)
-def _is_simple_type(obj):
- for simple_type in simple_types:
- if isinstance(obj, simple_type) or obj == None:
- return True
+
+def _is_simple_type(obj: Any) -> bool:
+ if obj is None:
+ return True
+ if isinstance(obj, simple_types):
+ return True
return False
-def _is_list_of_simple_types(l):
+def _is_list_of_simple_types(l: Any) -> bool:
if not isinstance(l, list):
return False
for i in l:
@@ -19,7 +24,7 @@ def _is_list_of_simple_types(l):
return True
-def _is_list_of_dicts(l):
+def _is_list_of_dicts(l: Any) -> bool:
if not isinstance(l, list):
return False
for i in l:
@@ -28,11 +33,11 @@ def _is_list_of_dicts(l):
return False
-def _hclify(obj, parent_key=None):
+def _hclify(obj: dict_node, conf: Optional[dict_node] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:
ret_dict = {}
if not isinstance(obj, dict):
raise Exception("this method receives only dicts")
- if hasattr(obj, 'start_mark') and hasattr(obj, "end_mark"):
+ if hasattr(obj, "start_mark") and hasattr(obj, "end_mark"):
obj["start_line"] = obj.start_mark.line
obj["end_line"] = obj.end_mark.line
for key, value in obj.items():
@@ -44,38 +49,52 @@ def _hclify(obj, parent_key=None):
if _is_list_of_dicts(value):
child_list = []
- for internal_val in value:
- child_list.append(_hclify(internal_val))
+ conf_val = conf.get(key, []) if conf else []
+ for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):
+ child_list.append(_hclify(internal_val, internal_conf_val))
ret_dict[key] = child_list
if isinstance(value, dict):
- child_dict = _hclify(value, key)
+ child_dict = _hclify(value, parent_key=key)
if parent_key == "tags":
ret_dict[key] = child_dict
else:
ret_dict[key] = [child_dict]
+ if conf:
+ for conf_key in conf.keys() - obj.keys():
+ ref = next((x for x in conf[conf_key].get("references", []) if not x.startswith(("var.", "local."))), None)
+ if ref:
+ ret_dict[conf_key] = [ref]
+
return ret_dict
-def _prepare_resource_block(resource):
- """
- hclify resource if pre-conditions met.
- :type: resource: dict: tf resource block
- :rtype: resource_block: dict: hclifyed if conditions met
- :rtype: prepared: boolean: whether conditions met to prepare data
+
+def _prepare_resource_block(resource: dict_node, conf: Optional[dict_node]) -> Tuple[Dict[str, Dict[str, Any]], bool]:
+ """hclify resource if pre-conditions met.
+
+ :param resource: tf planned_values resource block
+ :param conf: tf configuration resource block
+
+ :returns:
+ - resource_block: a list of strings representing the header columns
+ - prepared: whether conditions met to prepare data
"""
- resource_block = {}
- resource_block[resource['type']] = {}
+
+ resource_block: Dict[str, Dict[str, Any]] = {}
+ resource_block[resource["type"]] = {}
prepared = False
mode = ""
- if 'mode' in resource:
+ if "mode" in resource:
mode = resource.get("mode")
# Rare cases where data block appears in resources with same name as resource block and only partial values
# and where *_module resources don't have values field
- if mode == "managed" and 'values' in resource:
- resource_block[resource['type']][resource.get("name", "default")] = _hclify(resource['values'])
+ if mode == "managed" and "values" in resource:
+ expressions = conf.get("expressions") if conf else None
+ resource_block[resource["type"]][resource.get("name", "default")] = _hclify(resource["values"], expressions)
prepared = True
return resource_block, prepared
-def _find_child_modules(child_modules):
+
+def _find_child_modules(child_modules: list_node) -> List[Dict[str, Dict[str, Any]]]:
"""
Find all child modules if any. Including any amount of nested child modules.
:type: child_modules: list of tf child_module objects
@@ -83,37 +102,45 @@ def _find_child_modules(child_modules):
"""
resource_blocks = []
for child_module in child_modules:
- if child_module.get("child_modules",[]):
- nested_child_modules = child_module.get("child_modules",[])
+ if child_module.get("child_modules", []):
+ nested_child_modules = child_module.get("child_modules", [])
nested_blocks = _find_child_modules(nested_child_modules)
for resource in nested_blocks:
resource_blocks.append(resource)
for resource in child_module.get("resources", []):
- resource_block, prepared = _prepare_resource_block(resource)
+ resource_block, prepared = _prepare_resource_block(resource, None)
if prepared is True:
resource_blocks.append(resource_block)
return resource_blocks
-def parse_tf_plan(tf_plan_file):
+def parse_tf_plan(tf_plan_file: str) -> Tuple[Optional[Dict[str, Dict[str, Any]]], Optional[List[Tuple[int, str]]]]:
"""
:type tf_plan_file: str - path to plan file
:rtype: tf_definition dictionary
"""
- tf_defintions = {}
+ tf_defintions: Dict[str, Dict[str, Any]] = {}
tf_defintions[tf_plan_file] = {}
- tf_defintions[tf_plan_file]['resource'] = []
+ tf_defintions[tf_plan_file]["resource"] = []
template, template_lines = parse(tf_plan_file)
if not template:
return None, None
- for resource in template.get('planned_values', {}).get("root_module", {}).get("resources", []):
- resource_block, prepared = _prepare_resource_block(resource)
+ for resource in template.get("planned_values", {}).get("root_module", {}).get("resources", []):
+ conf = next(
+ (
+ x
+ for x in template.get("configuration", {}).get("root_module", {}).get("resources", [])
+ if x["type"] == resource["type"] and x["name"] == resource["name"]
+ ),
+ None,
+ )
+ resource_block, prepared = _prepare_resource_block(resource, conf)
if prepared is True:
- tf_defintions[tf_plan_file]['resource'].append(resource_block)
- child_modules = template.get('planned_values', {}).get("root_module", {}).get("child_modules",[])
+ tf_defintions[tf_plan_file]["resource"].append(resource_block)
+ child_modules = template.get("planned_values", {}).get("root_module", {}).get("child_modules", [])
# Terraform supports modules within modules so we need to search
# in nested modules to find all resource blocks
resource_blocks = _find_child_modules(child_modules)
for resource in resource_blocks:
- tf_defintions[tf_plan_file]['resource'].append(resource)
+ tf_defintions[tf_plan_file]["resource"].append(resource)
return tf_defintions, template_lines
diff --git a/checkov/terraform/plan_runner.py b/checkov/terraform/plan_runner.py
index 6e0462483b..4b8980b59b 100644
--- a/checkov/terraform/plan_runner.py
+++ b/checkov/terraform/plan_runner.py
@@ -1,28 +1,27 @@
-import logging
import json
import logging
import os
+from checkov.common.checks_infra.registry import get_graph_checks_registry
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+
from checkov.common.output.record import Record
from checkov.common.output.report import Report
-from checkov.common.runners.base_runner import BaseRunner
+from checkov.common.runners.base_runner import filter_ignored_paths
from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.resource.registry import resource_registry
from checkov.terraform.context_parsers.registry import parser_registry
-# Allow the evaluation of empty variables
from checkov.terraform.plan_parser import parse_tf_plan
-
-LOG_LEVEL = os.getenv('LOG_LEVEL', 'WARNING').upper()
-logging.basicConfig(level=LOG_LEVEL)
+from checkov.terraform.runner import Runner as TerraformRunner, merge_reports
-class Runner(BaseRunner):
+class Runner(TerraformRunner):
check_type = "terraform_plan"
def __init__(self):
- self.tf_definitions = {}
- self.definitions_context = {}
+ super().__init__()
self.template_lines = {}
+ self.graph_registry = get_graph_checks_registry(super().check_type)
block_type_registries = {
'resource': resource_registry,
@@ -35,11 +34,14 @@ def run(self, root_folder=None, external_checks_dir=None, files=None, runner_fil
parsing_errors = {}
if external_checks_dir:
for directory in external_checks_dir:
- resource_registry.load_external_checks(directory, runner_filter)
+ resource_registry.load_external_checks(directory)
+ self.graph_registry.load_external_checks(directory)
if root_folder:
files = [] if not files else files
for root, d_names, f_names in os.walk(root_folder):
+ filter_ignored_paths(root, d_names, runner_filter.excluded_paths)
+ filter_ignored_paths(root, f_names, runner_filter.excluded_paths)
for file in f_names:
file_ending = os.path.splitext(file)[1]
if file_ending == '.json':
@@ -63,14 +65,25 @@ def run(self, root_folder=None, external_checks_dir=None, files=None, runner_fil
self.tf_definitions = tf_definitions
self.template_lines = template_lines
self.check_tf_definition(report, runner_filter)
+ else:
+ logging.debug(f'Failed to load {file} as is not a .json file, skipping')
+
+ report.add_parsing_errors(list(parsing_errors.keys()))
+
+ graph = self.graph_manager.build_graph_from_definitions(self.tf_definitions, render_variables=False)
+ self.graph_manager.save_graph(graph)
- report.add_parsing_errors(parsing_errors.keys())
+ graph_report = self.get_graph_checks_report(root_folder, runner_filter)
+ merge_reports(report, graph_report)
return report
- def check_tf_definition(self, report, runner_filter,
- ):
+ def get_entity_context_and_evaluations(self, entity):
+ raw_context = self.get_entity_context(entity[CustomAttributes.BLOCK_NAME].split("."), entity[CustomAttributes.FILE_PATH])
+ raw_context['definition_path'] = entity[CustomAttributes.BLOCK_NAME].split('.')
+ return raw_context, None
+ def check_tf_definition(self, report, runner_filter):
for full_file_path, definition in self.tf_definitions.items():
scanned_file = f"/{os.path.relpath(full_file_path)}"
logging.debug(f"Scanning file: {scanned_file}")
@@ -79,12 +92,10 @@ def check_tf_definition(self, report, runner_filter,
self.run_block(definition[block_type], full_file_path, report, scanned_file,
block_type, runner_filter)
- def run_block(self, entities, full_file_path, report, scanned_file, block_type,
- runner_filter=None):
+ def run_block(self, entities, full_file_path, report, scanned_file, block_type, runner_filter=None):
registry = self.block_type_registries[block_type]
if registry:
for entity in entities:
- entity_evaluations = None
context_parser = parser_registry.context_parsers[block_type]
definition_path = context_parser.get_entity_context_path(entity)
entity_id = ".".join(definition_path)
@@ -94,10 +105,10 @@ def run_block(self, entities, full_file_path, report, scanned_file, block_type,
entity_code_lines = entity_context.get('code_lines')
results = registry.scan(scanned_file, entity, [], runner_filter)
for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=scanned_file,
file_line_range=entity_lines_range,
- resource=entity_id, evaluations=entity_evaluations,
+ resource=entity_id, evaluations=None,
check_class=check.__class__.__module__, file_abs_path=full_file_path)
report.add_record(record=record)
@@ -111,6 +122,7 @@ def get_entity_context(self, definition_path, full_file_path):
resource_defintion = resource[resource_type][resource_name]
entity_context['start_line'] = resource_defintion['start_line'][0]
entity_context['end_line'] = resource_defintion['end_line'][0]
- entity_context['code_lines'] = self.template_lines[entity_context['start_line']:entity_context['end_line']]
+ entity_context['code_lines'] = self.template_lines[
+ entity_context['start_line']:entity_context['end_line']]
return entity_context
return entity_context
diff --git a/checkov/terraform/runner.py b/checkov/terraform/runner.py
index 1c03e022d0..94d2610a08 100644
--- a/checkov/terraform/runner.py
+++ b/checkov/terraform/runner.py
@@ -1,14 +1,19 @@
+import copy
import dataclasses
import logging
import os
-from typing import Dict
+from typing import Dict, Optional, Tuple, List
import dpath.util
+from checkov.common.checks_infra.registry import get_graph_checks_registry
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.graph_record import GraphRecord
from checkov.common.output.record import Record
-from checkov.common.output.report import Report
-from checkov.common.util import dict_utils
+from checkov.common.output.report import Report, merge_reports, remove_duplicate_results
from checkov.common.runners.base_runner import BaseRunner
+from checkov.common.util import data_structures_utils
from checkov.common.variables.context import EvaluationContext
from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.data.registry import data_registry
@@ -17,25 +22,34 @@
from checkov.terraform.checks.resource.registry import resource_registry
from checkov.terraform.context_parsers.registry import parser_registry
from checkov.terraform.evaluation.base_variable_evaluation import BaseVariableEvaluation
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_to_tf_definitions import convert_graph_vertices_to_tf_definitions
+from checkov.terraform.graph_builder.local_graph import TerraformLocalGraph
+from checkov.terraform.graph_manager import TerraformGraphManager
+# Allow the evaluation of empty variables
from checkov.terraform.parser import Parser
+from checkov.terraform.tag_providers import get_resource_tags
-# Allow the evaluation of empty variables
dpath.options.ALLOW_EMPTY_STRING_KEYS = True
-LOG_LEVEL = os.getenv('LOG_LEVEL', 'WARNING').upper()
-logging.basicConfig(level=LOG_LEVEL)
-
CHECK_BLOCK_TYPES = frozenset(['resource', 'data', 'provider', 'module'])
class Runner(BaseRunner):
check_type = "terraform"
- def __init__(self, parser=Parser()):
+ def __init__(self, parser=Parser(), db_connector=NetworkxConnector(), external_registries=None,
+ source="Terraform", graph_class=TerraformLocalGraph, graph_manager=None):
+ self.external_registries = [] if external_registries is None else external_registries
+ self.graph_class = graph_class
self.parser = parser
- self.tf_definitions = {}
- self.definitions_context = {}
+ self.definitions = None
+ self.context = None
+ self.breadcrumbs = None
self.evaluations_context: Dict[str, Dict[str, EvaluationContext]] = {}
+ self.graph_manager = graph_manager if graph_manager is not None else TerraformGraphManager(source=source,
+ db_connector=db_connector)
+ self.graph_registry = get_graph_checks_registry(self.check_type)
block_type_registries = {
'resource': resource_registry,
@@ -46,68 +60,151 @@ def __init__(self, parser=Parser()):
def run(self, root_folder, external_checks_dir=None, files=None, runner_filter=RunnerFilter(), collect_skip_comments=True):
report = Report(self.check_type)
- self.tf_definitions = {}
parsing_errors = {}
+ self.load_external_checks(external_checks_dir)
+
+ if self.context is None or self.definitions is None or self.breadcrumbs is None:
+ self.definitions = {}
+ logging.info("Scanning root folder and producing fresh tf_definitions and context")
+ if root_folder:
+ root_folder = os.path.abspath(root_folder)
+
+ local_graph, tf_definitions = \
+ self.graph_manager.build_graph_from_source_directory(root_folder,
+ local_graph_class=self.graph_class,
+ download_external_modules=runner_filter.download_external_modules,
+ parsing_errors=parsing_errors, excluded_paths=runner_filter.excluded_paths)
+ elif files:
+ files = [os.path.abspath(file) for file in files]
+ root_folder = os.path.split(os.path.commonprefix(files))[0]
+ self.parser.evaluate_variables = False
+ for file in files:
+ if file.endswith(".tf"):
+ file_parsing_errors = {}
+ parse_result = self.parser.parse_file(file=file, parsing_errors=file_parsing_errors)
+ if parse_result is not None:
+ self.definitions[file] = parse_result
+ if file_parsing_errors:
+ parsing_errors.update(file_parsing_errors)
+ continue
+ local_graph = self.graph_manager.build_graph_from_definitions(self.definitions)
+ else:
+ raise Exception("Root directory was not specified, files were not specified")
+
+ self.graph_manager.save_graph(local_graph)
+ self.definitions, self.breadcrumbs = convert_graph_vertices_to_tf_definitions(local_graph.vertices, root_folder)
+ else:
+ logging.info(f"Scanning root folder using existing tf_definitions")
+
+ self.check_tf_definition(report, root_folder, runner_filter, collect_skip_comments)
+
+ report.add_parsing_errors(list(parsing_errors.keys()))
+
+ graph_report = self.get_graph_checks_report(root_folder, runner_filter)
+ merge_reports(report, graph_report)
+ report = remove_duplicate_results(report)
+
+ return report
+
+ def load_external_checks(self, external_checks_dir: List[str]):
if external_checks_dir:
for directory in external_checks_dir:
- resource_registry.load_external_checks(directory, runner_filter)
- if root_folder:
- root_folder = os.path.abspath(root_folder)
-
- self.parser.parse_directory(directory=root_folder,
- out_definitions=self.tf_definitions,
- out_evaluations_context=self.evaluations_context,
- out_parsing_errors=parsing_errors,
- download_external_modules=runner_filter.download_external_modules,
- external_modules_download_path=runner_filter.external_modules_download_path,
- evaluate_variables=runner_filter.evaluate_variables)
- self.check_tf_definition(report, root_folder, runner_filter, collect_skip_comments)
-
- if files:
- files = [os.path.abspath(file) for file in files]
- root_folder = os.path.split(os.path.commonprefix(files))[0]
- for file in files:
- if file.endswith(".tf"):
- file_parsing_errors = {}
- self.tf_definitions[file] = self.parser.parse_file(file=file, parsing_errors=file_parsing_errors)
- if file_parsing_errors:
- parsing_errors.update(file_parsing_errors)
- continue
- self.check_tf_definition(report, root_folder, runner_filter, collect_skip_comments)
+ resource_registry.load_external_checks(directory)
+ self.graph_registry.load_external_checks(directory)
- report.add_parsing_errors(parsing_errors.keys())
+ def get_graph_checks_report(self, root_folder, runner_filter: RunnerFilter):
+ report = Report(self.check_type)
+ checks_results = self.run_graph_checks_results(runner_filter)
+
+ for check, check_results in checks_results.items():
+ for check_result in check_results:
+ entity = check_result['entity']
+ entity_context, entity_evaluations = self.get_entity_context_and_evaluations(entity)
+ if entity_context:
+ full_file_path = entity[CustomAttributes.FILE_PATH]
+ copy_of_check_result = copy.deepcopy(check_result)
+ for skipped_check in entity_context.get('skipped_checks', []):
+ if skipped_check['id'] == check.id:
+ copy_of_check_result['result'] = CheckResult.SKIPPED
+ copy_of_check_result['suppress_comment'] = skipped_check['suppress_comment']
+ break
+ copy_of_check_result['entity'] = entity.get(CustomAttributes.CONFIG)
+ record = Record(check_id=check.id,
+ bc_check_id=check.bc_id,
+ check_name=check.name,
+ check_result=copy_of_check_result,
+ code_block=entity_context.get('code_lines'),
+ file_path=f"/{os.path.relpath(full_file_path, root_folder)}",
+ file_line_range=[entity_context.get('start_line'),
+ entity_context.get('end_line')],
+ resource=".".join(entity_context['definition_path']),
+ entity_tags=entity.get('tags', {}),
+ evaluations=entity_evaluations,
+ check_class=check.__class__.__module__,
+ file_abs_path=os.path.abspath(full_file_path))
+ if self.breadcrumbs:
+ breadcrumb = self.breadcrumbs.get(record.file_path, {}).get(record.resource)
+ if breadcrumb:
+ record = GraphRecord(record, breadcrumb)
+ report.add_record(record=record)
return report
- def check_tf_definition(self, report, root_folder, runner_filter, collect_skip_comments=True, external_definitions_context=None):
+ def get_entity_context_and_evaluations(self, entity):
+ entity_evaluations = None
+ block_type = entity[CustomAttributes.BLOCK_TYPE]
+ full_file_path = entity[CustomAttributes.FILE_PATH]
+ definition_path = entity[CustomAttributes.BLOCK_NAME].split('.')
+ entity_context_path = [block_type] + definition_path
+ entity_context = self.context.get(full_file_path, {})
+ try:
+ if not entity_context:
+ dc_keys = self.context.keys()
+ dc_key = next(x for x in dc_keys if x.startswith(full_file_path))
+ entity_context = self.context.get(dc_key, {})
+ for k in entity_context_path:
+ if k in entity_context:
+ entity_context = entity_context[k]
+ else:
+ logging.warning(f'Failed to find context for {".".join(entity_context_path)}')
+ return None, None
+ entity_context['definition_path'] = definition_path
+ except StopIteration:
+ logging.debug(f"Did not find context for key {full_file_path}")
+ return entity_context, entity_evaluations
+
+ def check_tf_definition(self, report, root_folder, runner_filter, collect_skip_comments=True):
parser_registry.reset_definitions_context()
- if external_definitions_context:
- definitions_context = external_definitions_context
- else:
+ if not self.context:
definitions_context = {}
- for definition in self.tf_definitions.items():
+ for definition in self.definitions.items():
definitions_context = parser_registry.enrich_definitions_context(definition, collect_skip_comments)
- self.definitions_context = definitions_context
+ self.context = definitions_context
logging.debug('Created definitions context')
- for full_file_path, definition in self.tf_definitions.items():
- scanned_file = f"/{os.path.relpath(self._strip_module_referrer(full_file_path), root_folder)}"
+ for full_file_path, definition in self.definitions.items():
+ abs_scanned_file, abs_referrer = self._strip_module_referrer(full_file_path)
+ scanned_file = f"/{os.path.relpath(abs_scanned_file, root_folder)}"
logging.debug(f"Scanning file: {scanned_file}")
- self.run_all_blocks(definition, definitions_context, full_file_path, root_folder, report,
- scanned_file, runner_filter)
+ self.run_all_blocks(definition, self.context, full_file_path, root_folder, report,
+ scanned_file, runner_filter, abs_referrer)
def run_all_blocks(self, definition, definitions_context, full_file_path, root_folder, report,
- scanned_file, runner_filter):
- for block_type in definition.keys():
- if block_type in CHECK_BLOCK_TYPES:
- self.run_block(definition[block_type], definitions_context,
- full_file_path, root_folder, report,
- scanned_file, block_type, runner_filter)
+ scanned_file, runner_filter, module_referrer: Optional[str]):
+ if not definition:
+ logging.debug("Empty definition, skipping run (root_folder=%s)", root_folder)
+ return
+ block_types = set(definition.keys())
+ for block_type in block_types & CHECK_BLOCK_TYPES:
+ self.run_block(definition[block_type], definitions_context,
+ full_file_path, root_folder, report,
+ scanned_file, block_type, runner_filter, None, module_referrer)
def run_block(self, entities,
definition_context,
full_file_path, root_folder, report, scanned_file,
- block_type, runner_filter=None, entity_context_path_header=None):
+ block_type, runner_filter=None, entity_context_path_header=None,
+ module_referrer: Optional[str] = None):
registry = self.block_type_registries[block_type]
if not registry:
@@ -117,14 +214,42 @@ def run_block(self, entities,
entity_evaluations = None
context_parser = parser_registry.context_parsers[block_type]
definition_path = context_parser.get_entity_context_path(entity)
- entity_id = ".".join(definition_path)
+ entity_id = ".".join(definition_path) # example: aws_s3_bucket.my_bucket
+
+ caller_file_path = None
+ caller_file_line_range = None
+
+ if module_referrer is not None:
+ referrer_id = self._find_id_for_referrer(full_file_path,
+ self.definitions)
+ if referrer_id:
+ entity_id = f"{referrer_id}.{entity_id}" # ex: module.my_module.aws_s3_bucket.my_bucket
+ abs_caller_file = module_referrer[:module_referrer.rindex("#")]
+ caller_file_path = f"/{os.path.relpath(abs_caller_file, root_folder)}"
+
+ try:
+ caller_context = dpath.get(definition_context[abs_caller_file],
+ # HACK ALERT: module data is currently double-nested in
+ # definition context. If fixed, remove the
+ # addition of "module." at the beginning.
+ "module." + referrer_id,
+ separator=".")
+ except KeyError:
+ logging.debug("Unable to find caller context for: %s", abs_caller_file)
+ caller_context = None
+
+ if caller_context:
+ caller_file_line_range = [caller_context.get('start_line'), caller_context.get('end_line')]
+ else:
+ logging.debug(f"Unable to find referrer ID for full path: %s", full_file_path)
+
if entity_context_path_header is None:
entity_context_path = [block_type] + definition_path
else:
entity_context_path = entity_context_path_header + block_type + definition_path
# Entity can exist only once per dir, for file as well
try:
- entity_context = dict_utils.getInnerDict(definition_context[full_file_path], entity_context_path)
+ entity_context = data_structures_utils.get_inner_dict(definition_context[full_file_path], entity_context_path)
entity_lines_range = [entity_context.get('start_line'), entity_context.get('end_line')]
entity_code_lines = entity_context.get('code_lines')
skipped_checks = entity_context.get('skipped_checks')
@@ -141,18 +266,47 @@ def run_block(self, entities,
entity_evaluations = BaseVariableEvaluation.reduce_entity_evaluations(variables_evaluations,
entity_context_path)
results = registry.scan(scanned_file, entity, skipped_checks, runner_filter)
+ absolut_scanned_file_path, _ = self._strip_module_referrer(file_path=full_file_path)
+ # This duplicates a call at the start of scan, but adding this here seems better than kludging with some tuple return type
+ (entity_type, entity_name, entity_config) = registry.extract_entity_details(entity)
+ tags = get_resource_tags(entity_type, entity_config)
for check, check_result in results.items():
- record = Record(check_id=check.id, check_name=check.name, check_result=check_result,
+ record = Record(check_id=check.id, bc_check_id=check.bc_id, check_name=check.name, check_result=check_result,
code_block=entity_code_lines, file_path=scanned_file,
file_line_range=entity_lines_range,
resource=entity_id, evaluations=entity_evaluations,
- check_class=check.__class__.__module__, file_abs_path=full_file_path)
+ check_class=check.__class__.__module__, file_abs_path=absolut_scanned_file_path,
+ entity_tags=tags,
+ caller_file_path=caller_file_path,
+ caller_file_line_range=caller_file_line_range)
+ breadcrumb = self.breadcrumbs.get(record.file_path, {}).get('.'.join([entity_type, entity_name]))
+ if breadcrumb:
+ record = GraphRecord(record, breadcrumb)
report.add_record(record=record)
@staticmethod
- def _strip_module_referrer(file_path:str) -> str:
+ def _strip_module_referrer(file_path: str) -> Tuple[str, Optional[str]]:
+ """
+ For file paths containing module referrer information (e.g.: "module/module.tf[main.tf#0]"), this
+ returns a tuple containing the file path (e.g., "module/module.tf") and referrer (e.g., "main.tf#0").
+ If the file path does not contain a referred, the tuple will contain the original file path and None.
+ """
if file_path.endswith("]") and "[" in file_path:
- return file_path[:file_path.index("[")]
+ return file_path[:file_path.index("[")], file_path[file_path.index("[") + 1: -1]
else:
- return file_path
+ return file_path, None
+
+ @staticmethod
+ def _find_id_for_referrer(full_file_path, definitions) -> Optional[str]:
+ for file, file_content in definitions.items():
+ if "module" not in file_content:
+ continue
+
+ for modules in file_content["module"]:
+ for module_name, module_content in modules.items():
+ if "__resolved__" not in module_content:
+ continue
+ if full_file_path in module_content["__resolved__"]:
+ return f"module.{module_name}"
+ return None
diff --git a/checkov/terraform/tag_providers/__init__.py b/checkov/terraform/tag_providers/__init__.py
new file mode 100644
index 0000000000..ddbd5b33cb
--- /dev/null
+++ b/checkov/terraform/tag_providers/__init__.py
@@ -0,0 +1,21 @@
+from typing import Dict, Any, Optional
+
+from checkov.terraform.tag_providers import aws
+from checkov.terraform.tag_providers import azure
+from checkov.terraform.tag_providers import gcp
+
+provider_tag_mapping = {"aws": aws.get_resource_tags, "azure": azure.get_resource_tags, "gcp": gcp.get_resource_tags}
+
+
+def get_resource_tags(resource_type: str, entity_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+ if not isinstance(entity_config, dict):
+ return None
+
+ if "_" not in resource_type:
+ return None # probably not a resource block
+ provider = resource_type[: resource_type.index("_")]
+ provider_tag_function = provider_tag_mapping.get(provider)
+ if provider_tag_function:
+ return provider_tag_function(entity_config)
+ else:
+ return None
diff --git a/checkov/terraform/tag_providers/aws.py b/checkov/terraform/tag_providers/aws.py
new file mode 100644
index 0000000000..ac618d8bf9
--- /dev/null
+++ b/checkov/terraform/tag_providers/aws.py
@@ -0,0 +1,7 @@
+from typing import Dict, List, Any, Optional
+
+from checkov.common.util.type_forcers import force_dict
+
+
+def get_resource_tags(entity_config: Dict[str, List[Any]]) -> Optional[Dict[str, Any]]:
+ return force_dict(entity_config.get("tags"))
diff --git a/checkov/terraform/tag_providers/azure.py b/checkov/terraform/tag_providers/azure.py
new file mode 100644
index 0000000000..ac618d8bf9
--- /dev/null
+++ b/checkov/terraform/tag_providers/azure.py
@@ -0,0 +1,7 @@
+from typing import Dict, List, Any, Optional
+
+from checkov.common.util.type_forcers import force_dict
+
+
+def get_resource_tags(entity_config: Dict[str, List[Any]]) -> Optional[Dict[str, Any]]:
+ return force_dict(entity_config.get("tags"))
diff --git a/checkov/terraform/tag_providers/gcp.py b/checkov/terraform/tag_providers/gcp.py
new file mode 100644
index 0000000000..328d68fc7b
--- /dev/null
+++ b/checkov/terraform/tag_providers/gcp.py
@@ -0,0 +1,7 @@
+from typing import Dict, List, Any, Optional
+
+from checkov.common.util.type_forcers import force_dict
+
+
+def get_resource_tags(entity_config: Dict[str, List[Any]]) -> Optional[Dict[str, Any]]:
+ return force_dict(entity_config.get("labels"))
diff --git a/checkov/terraform/variable_rendering/__init__.py b/checkov/terraform/variable_rendering/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/checkov/terraform/variable_rendering/evaluate_terraform.py b/checkov/terraform/variable_rendering/evaluate_terraform.py
new file mode 100644
index 0000000000..c92ba74b98
--- /dev/null
+++ b/checkov/terraform/variable_rendering/evaluate_terraform.py
@@ -0,0 +1,308 @@
+import re
+from typing import Any, Union, Optional, List, Dict, Callable, TypeVar
+
+# condition ? true_val : false_val -> (condition, true_val, false_val)
+from checkov.terraform.parser_utils import find_var_blocks
+from checkov.terraform.variable_rendering.safe_eval_functions import evaluate
+
+T = TypeVar("T", str, int, bool)
+
+CONDITIONAL_EXPR = r"([^\?]+)\?([^:]+)\:([^:]+)"
+
+# {key1 = value1, key2 = value2, ...}
+MAP_REGEX = r"\{(?:\s*[\S]+\s*\=\s*[\S]+\s*\,)*(?:\s*[\S]+\s*\=\s*[\S]+\s*)\}"
+
+# {key:val}[key]
+MAP_WITH_ACCESS = re.compile(r"(?P{(?:.*?:.*?)+(,?:.*?:.*?)*})\s*(?P\[[^\]]+\])")
+
+LIST_PATTERN = r"(?P\[([^\[\]]+?)+(\,[^\[\]]+?)*\])"
+
+KEY_VALUE_REGEX = r"([\S]+)\s*\=\s*([\S]+)"
+
+# %{ some_text }
+DIRECTIVE_EXPR = r"\%\{([^\}]*)\}"
+
+COMPARE_REGEX = re.compile(r"^(?P.+)(?P==|!=|>=|>|<=|<|&&|\|\|)+(?P.+)$")
+
+
+def evaluate_terraform(input_str: str, keep_interpolations: bool = True) -> Any:
+ evaluated_value = _try_evaluate(input_str)
+ if type(evaluated_value) is not str:
+ return input_str if callable(evaluated_value) else evaluated_value
+ evaluated_value = evaluated_value.replace("\n", "")
+ evaluated_value = evaluated_value.replace(",,", ",")
+ if not keep_interpolations:
+ evaluated_value = remove_interpolation(evaluated_value)
+ evaluated_value = evaluate_map(evaluated_value)
+ evaluated_value = evaluate_list_access(evaluated_value)
+ evaluated_value = strip_double_quotes(evaluated_value)
+ evaluated_value = evaluate_directives(evaluated_value)
+ evaluated_value = evaluate_conditional_expression(evaluated_value)
+ evaluated_value = evaluate_compare(evaluated_value)
+ evaluated_value = evaluate_json_types(evaluated_value)
+ second_evaluated_value = _try_evaluate(evaluated_value)
+
+ return evaluated_value if callable(second_evaluated_value) else second_evaluated_value
+
+
+def _try_evaluate(input_str: Union[str, bool]) -> Any:
+ try:
+ return evaluate(input_str)
+ except Exception:
+ try:
+ return evaluate(f'"{input_str}"')
+ except Exception:
+ return input_str
+
+
+def replace_string_value(original_str: Any, str_to_replace: str, replaced_value: str, keep_origin: bool = True) -> Any:
+ if original_str is None or type(original_str) not in (str, list):
+ return original_str
+
+ if type(original_str) is list:
+ for i, item in enumerate(original_str):
+ original_str[i] = replace_string_value(item, str_to_replace, replaced_value, keep_origin)
+ if type(replaced_value) in [int, float, bool]:
+ original_str[i] = evaluate_terraform(original_str[i])
+ return original_str
+
+ if str_to_replace not in original_str:
+ return original_str if keep_origin else str_to_replace
+
+ string_without_interpolation = remove_interpolation(original_str, str_to_replace)
+ return string_without_interpolation.replace(str_to_replace, str(replaced_value)).replace(" ", "")
+
+
+def remove_interpolation(original_str: str, var_to_clean: Optional[str] = None) -> str:
+ # get all variable references in string
+ # remove from the string all ${} or '${}' occurrences
+ var_blocks = find_var_blocks(original_str)
+ var_blocks.reverse()
+ for block in var_blocks:
+ if (
+ block.full_str.startswith("${")
+ and block.full_str.endswith("}")
+ and (not var_to_clean or block.var_only == var_to_clean)
+ ):
+ full_str_start = original_str.find(block.full_str)
+ full_str_end = full_str_start + len(block.full_str)
+ if (
+ full_str_start > 0
+ and full_str_end <= len(original_str) - 2
+ and original_str[full_str_start - 1] == "'"
+ and original_str[full_str_start - 1] == original_str[full_str_end]
+ and "." in block.full_str
+ ):
+ # checking if ${} is wrapped with '' like : '${}'
+ original_str = original_str[: full_str_start - 1] + block.full_str + original_str[full_str_end + 1 :]
+ original_str = original_str.replace(block.full_str, block.var_only)
+ return original_str
+
+
+def strip_double_quotes(input_str: str) -> str:
+ if input_str.startswith('"') and input_str.endswith('"'):
+ input_str = input_str[1:-1]
+ return input_str
+
+
+def evaluate_conditional_expression(input_str: str) -> str:
+ variable_ref = re.match(r"^\${(.*)}$", input_str)
+ if variable_ref:
+ input_str = variable_ref.groups()[0]
+
+ condition = re.match(CONDITIONAL_EXPR, input_str)
+ while condition:
+ groups = condition.groups()
+ if len(groups) != 3:
+ return input_str
+ evaluated_condition = evaluate_terraform(groups[0])
+ condition_substr = input_str[condition.start() : condition.end()]
+ if convert_to_bool(evaluated_condition):
+ true_val = str(evaluate_terraform(groups[1])).strip()
+ input_str = input_str.replace(condition_substr, true_val)
+ else:
+ false_val = str(evaluate_terraform(groups[2])).strip()
+ input_str = input_str.replace(condition_substr, false_val)
+ condition = re.match(CONDITIONAL_EXPR, input_str)
+
+ return input_str
+
+
+def evaluate_compare(input_str: str) -> Union[str, bool]:
+ """
+ :param input_str: string like "a && b" (supported operators: ==, != , <, <=, >, >=, && , ||)
+ :return: evaluation of the expression
+ """
+ if isinstance(input_str, str) and "for" not in input_str:
+ match = re.search(COMPARE_REGEX, input_str)
+ if match:
+ compare_parts = match.groupdict()
+ a = compare_parts.get("a")
+ b = compare_parts.get("b")
+ op = compare_parts.get("operator")
+ if a and b and op:
+ try:
+ return apply_binary_op(evaluate_terraform(a), evaluate_terraform(b), op)
+ except TypeError or SyntaxError:
+ return input_str
+
+ return input_str
+
+
+def evaluate_json_types(input_str: Any) -> Any:
+ # https://www.terraform.io/docs/language/functions/jsonencode.html
+ if isinstance(input_str, str) and input_str.startswith("jsonencode("):
+ return input_str.replace("true", "True").replace("false", "False").replace("null", "None")
+
+ return input_str
+
+
+def apply_binary_op(a: Optional[Union[str, int, bool]], b: Optional[Union[str, int, bool]], operator: str) -> bool:
+ # apply the operator after verifying that a and b have the same type.
+ operators: Dict[str, Callable[[T, T], bool]] = {
+ "==": lambda a, b: a == b,
+ "!=": lambda a, b: a != b,
+ ">": lambda a, b: a > b,
+ ">=": lambda a, b: a >= b,
+ "<": lambda a, b: a < b,
+ "<=": lambda a, b: a <= b,
+ "&&": lambda a, b: a and b,
+ "||": lambda a, b: a or b,
+ }
+ type_a = type(a)
+ type_b = type(b)
+
+ if type_a != type_b:
+ try:
+ temp_b = type_a(b)
+ if isinstance(type_a, bool):
+ temp_b = bool(convert_to_bool(b))
+ return operators[operator](a, temp_b)
+ except Exception:
+ temp_a = type_b(a)
+ if isinstance(type_b, bool):
+ temp_a = bool(convert_to_bool(a))
+ return operators[operator](temp_a, b)
+ else:
+ return operators[operator](a, b)
+
+
+def evaluate_directives(input_str: str) -> str:
+ if re.search(DIRECTIVE_EXPR, input_str) is None:
+ return input_str
+
+ # replace `%{if }%{true_val}%{else}%{false_val}%{endif}` pattern with ` ? true_val : false_val`
+ matching_directives = re.findall(DIRECTIVE_EXPR, input_str)
+ if len(matching_directives) == 3:
+ if (
+ re.search(r"\bif\b", matching_directives[0])
+ and re.search(r"\belse\b", matching_directives[1])
+ and re.search(r"\bendif\b", matching_directives[2])
+ ):
+ split_by_directives = re.split(DIRECTIVE_EXPR, input_str)
+ edited_str = ""
+ for part in split_by_directives:
+ if re.search(r"\bif\b", part):
+ part = part.replace("if", "%{") + " ? "
+ part = re.sub(r"\s", "", part)
+ if re.search(r"\belse\b", part):
+ part = part.replace("else", ":")
+ part = re.sub(r"\s", "", part)
+ if re.search(r"\bendif\b", part):
+ part = part.replace("endif", "}")
+ part = re.sub(r"\s", "", part)
+ edited_str += part
+ input_str = edited_str
+
+ matching_directives = re.split(DIRECTIVE_EXPR, input_str)
+ evaluated_string_parts = []
+ for str_part in matching_directives:
+ evaluated_string_parts.append(evaluate_terraform(str_part))
+
+ # Handle evaluation results which are integer / boolean
+ evaluated_string_parts = [v if isinstance(v, str) else str(v) for v in evaluated_string_parts]
+ return "".join(evaluated_string_parts)
+
+
+def evaluate_map(input_str: str) -> str:
+ # first replace maps ":" with "="
+ all_curly_brackets = find_brackets_pairs(input_str, "{", "}")
+ for curly_match in all_curly_brackets:
+ curly_start = curly_match["start"]
+ curly_end = curly_match["end"]
+ replaced_matching_map = input_str[curly_start : curly_end + 1].replace("=", ":")
+ input_str = input_str.replace(input_str[curly_start : curly_end + 1], replaced_matching_map)
+
+ # find map access like {a: b}[a] and extract the right value - b
+ all_square_brackets = find_brackets_pairs(input_str, "[", "]")
+
+ curr_square_match = 0
+ for curly_match in all_curly_brackets:
+ curly_start = curly_match["start"]
+ curly_end = curly_match["end"]
+ for i in range(curr_square_match, len(all_square_brackets)):
+ curr_square_match = i
+ square_match = all_square_brackets[i]
+ square_start = square_match["start"]
+ square_end = square_match["end"]
+ if square_start > curly_end and (
+ square_start == curly_end + 1 or all(c == " " for c in input_str[curly_end + 1 : square_start])
+ ):
+ origin_match_str = input_str[curly_start : square_end + 1]
+ map_access = input_str[square_start + 1 : square_end]
+ if not map_access.startswith('"') and not map_access.endswith('"'):
+ origin_match_str = origin_match_str.replace(f"[{map_access}]", f'["{map_access}"]')
+ evaluated = _try_evaluate(origin_match_str)
+ if evaluated:
+ input_str = input_str.replace(input_str[curly_start : square_end + 1], str(evaluated))
+ break
+
+ return input_str
+
+
+def convert_to_bool(bool_str: Union[str, int]) -> Union[str, int, bool]:
+ if bool_str in ["true", '"true"', "True", '"True"', 1, "1"]:
+ return True
+ elif bool_str in ["false", '"false"', "False", '"False"', 0, "0"]:
+ return False
+ else:
+ return bool_str
+
+
+def evaluate_list_access(input_str: str) -> str:
+ # find list access like [a, b, c][0] and extract the right value - a
+
+ all_square_brackets = find_brackets_pairs(input_str, "[", "]")
+ prev_start = -1
+ prev_end = -1
+ for match in all_square_brackets:
+ if (
+ match["start"] == prev_end + 1 or all(c == " " for c in input_str[prev_end + 1 : match["start"]])
+ ) and prev_start != -1:
+ curr_str = input_str[match["start"] + 1 : match["end"]]
+ if curr_str.isnumeric():
+ evaluated = _try_evaluate(input_str[prev_start : match["end"] + 1])
+ if evaluated:
+ input_str = input_str.replace(input_str[prev_start : match["end"] + 1], str(evaluated))
+ prev_start = match["start"]
+ prev_end = match["end"]
+
+ return input_str
+
+
+def find_brackets_pairs(input_str: str, starting: str, closing: str) -> List[Dict[str, int]]:
+ brackets_pairs = [-1] * len(input_str)
+ unmatched_open = []
+
+ for i, c in enumerate(input_str):
+ if c == starting:
+ unmatched_open.append(i)
+ elif c == closing and len(unmatched_open) > 0:
+ brackets_pairs[unmatched_open[-1]] = i
+ unmatched_open = unmatched_open[:-1]
+
+ all_brackets = []
+ for start, end in enumerate(brackets_pairs):
+ if end != -1:
+ all_brackets.append({"start": start, "end": end})
+ return all_brackets
diff --git a/checkov/terraform/variable_rendering/renderer.py b/checkov/terraform/variable_rendering/renderer.py
new file mode 100644
index 0000000000..f68c5f55f2
--- /dev/null
+++ b/checkov/terraform/variable_rendering/renderer.py
@@ -0,0 +1,357 @@
+from collections import Hashable
+import logging
+import os
+from copy import deepcopy
+from typing import TYPE_CHECKING, List, Dict, Any, Tuple, Union, Optional
+
+from lark.tree import Tree
+
+from checkov.common.graph.graph_builder import Edge
+from checkov.common.graph.graph_builder.utils import calculate_hash, run_function_multithreaded, join_trimmed_strings
+from checkov.terraform.graph_builder.utils import (
+ get_referenced_vertices_in_value,
+ remove_index_pattern_from_str,
+ attribute_has_nested_attributes,
+ VertexReference,
+)
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes, reserved_attribute_names
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.variable_rendering.evaluate_terraform import replace_string_value, evaluate_terraform
+
+if TYPE_CHECKING:
+ from checkov.terraform.graph_builder.local_graph import TerraformLocalGraph
+
+ATTRIBUTES_NO_EVAL = ["template_body", "template"]
+
+
+class VariableRenderer:
+ def __init__(self, local_graph: "TerraformLocalGraph") -> None:
+ self.local_graph = local_graph
+ run_async = os.environ.get("RENDER_VARIABLES_ASYNC", "False")
+ self.run_async = True if run_async == "True" else False
+ self.max_workers = int(os.environ.get("RENDER_ASYNC_MAX_WORKERS", 50))
+ self.done_edges_by_origin_vertex: Dict[int, List[Edge]] = {}
+ self.replace_cache: List[Dict[str, Any]] = [{}] * len(local_graph.vertices)
+
+ def render_variables_from_local_graph(self) -> None:
+ # find vertices with out-degree = 0 and in-degree > 0
+ end_vertices_indexes = self.local_graph.get_vertices_with_degrees_conditions(
+ out_degree_cond=lambda degree: degree == 0, in_degree_cond=lambda degree: degree > 0
+ )
+
+ # all the edges entering `end_vertices`
+ edges_to_render = self.local_graph.get_in_edges(end_vertices_indexes)
+ loops = 0
+ while len(edges_to_render) > 0:
+ logging.info(f"evaluating {len(edges_to_render)} edges")
+ # group edges that have the same origin and label together
+ edges_groups = self.group_edges_by_origin_and_label(edges_to_render)
+ if self.run_async:
+ run_function_multithreaded(
+ func=self._edge_evaluation_task,
+ data=edges_groups,
+ max_group_size=1,
+ num_of_workers=self.max_workers,
+ )
+ else:
+ for edge_group in edges_groups:
+ self._edge_evaluation_task([edge_group])
+ for edge in edges_to_render:
+ origin = edge.origin
+ if origin not in self.done_edges_by_origin_vertex:
+ self.done_edges_by_origin_vertex[origin] = []
+ self.done_edges_by_origin_vertex[origin].append(edge)
+
+ for edge in edges_to_render:
+ origin_vertex_index = edge.origin
+ out_edges = self.local_graph.out_edges.get(origin_vertex_index, [])
+ if all(e in self.done_edges_by_origin_vertex.get(origin_vertex_index, []) for e in out_edges):
+ end_vertices_indexes.append(origin_vertex_index)
+ edges_to_render = self.local_graph.get_in_edges(end_vertices_indexes)
+ edges_to_render = list(
+ set(
+ [
+ edge
+ for edge in edges_to_render
+ if edge not in self.done_edges_by_origin_vertex.get(edge.origin, [])
+ ]
+ )
+ )
+ loops += 1
+ if loops >= 50:
+ logging.warning(
+ f"Reached 50 graph edge iterations, breaking. Module: {self.local_graph.module.source_dir}"
+ )
+ break
+
+ self.local_graph.update_vertices_configs()
+ logging.info("done evaluating edges")
+ self.evaluate_non_rendered_values()
+ logging.info("done evaluate_non_rendered_values")
+
+ def _edge_evaluation_task(self, edges: List[List[Edge]]) -> List[Edge]:
+ inner_edges = edges[0]
+ self.evaluate_vertex_attribute_from_edge(inner_edges)
+ return inner_edges
+
+ def evaluate_vertex_attribute_from_edge(self, edge_list: List[Edge]) -> None:
+ multiple_edges = len(edge_list) > 1
+ edge = edge_list[0]
+ origin_vertex_attributes = self.local_graph.vertices[edge.origin].attributes
+ val_to_eval = deepcopy(origin_vertex_attributes.get(edge.label, ""))
+
+ referenced_vertices = get_referenced_vertices_in_value(
+ value=val_to_eval, aliases={}, resources_types=self.local_graph.get_resources_types_in_graph()
+ )
+ if not referenced_vertices:
+ origin_vertex = self.local_graph.vertices[edge.origin]
+ destination_vertex = self.local_graph.vertices[edge.dest]
+ if origin_vertex.block_type == BlockType.VARIABLE and destination_vertex.block_type == BlockType.MODULE:
+ self.update_evaluated_value(
+ changed_attribute_key=edge.label,
+ changed_attribute_value=destination_vertex.attributes[origin_vertex.name],
+ vertex=edge.origin,
+ change_origin_id=edge.dest,
+ attribute_at_dest=edge.label,
+ )
+ return
+ if (
+ origin_vertex.block_type == BlockType.VARIABLE
+ and destination_vertex.block_type == BlockType.TF_VARIABLE
+ ):
+ self.update_evaluated_value(
+ changed_attribute_key=edge.label,
+ changed_attribute_value=destination_vertex.attributes["default"],
+ vertex=edge.origin,
+ change_origin_id=edge.dest,
+ attribute_at_dest=edge.label,
+ )
+ return
+
+ modified_vertex_attributes = self.local_graph.vertices[edge.origin].attributes
+ val_to_eval = deepcopy(modified_vertex_attributes.get(edge.label, ""))
+ origin_val = deepcopy(val_to_eval)
+ first_key_path = None
+
+ if referenced_vertices:
+ for edge in edge_list:
+ dest_vertex_attributes = self.local_graph.get_vertex_attributes_by_index(edge.dest)
+ key_path_in_dest_vertex, replaced_key = self.find_path_from_referenced_vertices(
+ referenced_vertices, dest_vertex_attributes
+ )
+ if not key_path_in_dest_vertex or not replaced_key:
+ continue
+ if not first_key_path:
+ first_key_path = key_path_in_dest_vertex
+
+ evaluated_attribute_value = self.extract_value_from_vertex(
+ key_path_in_dest_vertex, dest_vertex_attributes
+ )
+ if evaluated_attribute_value is not None:
+ val_to_eval = self.replace_value(edge, val_to_eval, replaced_key, evaluated_attribute_value, True)
+ if not multiple_edges and val_to_eval != origin_val:
+ self.update_evaluated_value(
+ changed_attribute_key=edge.label,
+ changed_attribute_value=val_to_eval,
+ vertex=edge.origin,
+ change_origin_id=edge.dest,
+ attribute_at_dest=key_path_in_dest_vertex,
+ )
+
+ if multiple_edges and val_to_eval != origin_val:
+ self.update_evaluated_value(
+ changed_attribute_key=edge.label,
+ changed_attribute_value=val_to_eval,
+ vertex=edge.origin,
+ change_origin_id=edge.dest,
+ attribute_at_dest=first_key_path,
+ )
+
+ # Avoid loops on output => output edges
+ if (
+ self.local_graph.vertices[edge.origin].block_type == BlockType.OUTPUT
+ and self.local_graph.vertices[edge.dest].block_type == BlockType.OUTPUT
+ ):
+ if edge.origin not in self.done_edges_by_origin_vertex:
+ self.done_edges_by_origin_vertex[edge.origin] = []
+ self.done_edges_by_origin_vertex[edge.origin].append(edge)
+
+ def extract_value_from_vertex(self, key_path: List[str], attributes: Dict[str, Any]) -> Any:
+ for i, _ in enumerate(key_path):
+ key = join_trimmed_strings(char_to_join=".", str_lst=key_path, num_to_trim=i)
+ value = attributes.get(key, None)
+ if value is not None:
+ return value
+
+ reversed_key_path = deepcopy(key_path)
+ reversed_key_path.reverse()
+ for i, _ in enumerate(reversed_key_path):
+ key = join_trimmed_strings(char_to_join=".", str_lst=reversed_key_path, num_to_trim=i)
+ value = attributes.get(key, None)
+ if value is not None:
+ return value
+
+ if attributes.get(CustomAttributes.BLOCK_TYPE) in [BlockType.VARIABLE, BlockType.TF_VARIABLE]:
+ default_val = attributes.get("default")
+ value = None
+ if isinstance(default_val, dict):
+ value = self.extract_value_from_vertex(key_path, default_val)
+ return default_val if not value else value
+ if attributes.get(CustomAttributes.BLOCK_TYPE) == BlockType.OUTPUT:
+ return attributes.get("value")
+ return None
+
+ @staticmethod
+ def find_path_from_referenced_vertices(
+ referenced_vertices: List[VertexReference], vertex_attributes: Dict[str, Any]
+ ) -> Tuple[List[str], str]:
+ """
+ :param referenced_vertices: an array of VertexReference
+ :param vertex_attributes: attributes to search
+ :return attribute_path: [] if referenced_vertices does not contain vertex_attributes,
+ else the path to the searched attribute: ['vpc_id']
+ :return origin_value
+ """
+ for vertex_reference in referenced_vertices:
+ block_type = vertex_reference.block_type
+ attribute_path = vertex_reference.sub_parts
+ copy_of_attribute_path = deepcopy(attribute_path)
+ if vertex_attributes[CustomAttributes.BLOCK_TYPE] == block_type:
+ for i in range(len(copy_of_attribute_path)):
+ copy_of_attribute_path[i] = remove_index_pattern_from_str(copy_of_attribute_path[i])
+ name = ".".join(copy_of_attribute_path[: i + 1])
+ if vertex_attributes[CustomAttributes.BLOCK_NAME] == name:
+ return attribute_path, vertex_reference.origin_value
+ elif block_type == BlockType.MODULE:
+ copy_of_attribute_path.reverse()
+ for i in range(len(copy_of_attribute_path)):
+ copy_of_attribute_path[i] = remove_index_pattern_from_str(copy_of_attribute_path[i])
+ name = ".".join(copy_of_attribute_path[: i + 1])
+ if vertex_attributes[CustomAttributes.BLOCK_NAME] == name:
+ return name.split("."), vertex_reference.origin_value
+ return [], ""
+
+ def update_evaluated_value(
+ self,
+ changed_attribute_key: str,
+ changed_attribute_value: Union[str, List[str]],
+ vertex: int,
+ change_origin_id: int,
+ attribute_at_dest: Optional[Union[str, List[str]]] = None,
+ ) -> None:
+ """
+ The function updates the value of changed_attribute_key with changed_attribute_value for vertex
+ """
+ str_to_evaluate = (
+ str(changed_attribute_value)
+ if changed_attribute_key in ATTRIBUTES_NO_EVAL
+ else f'"{str(changed_attribute_value)}"'
+ )
+ str_to_evaluate = str_to_evaluate.replace("\\\\", "\\")
+ evaluated_attribute_value = (
+ str_to_evaluate if changed_attribute_key in ATTRIBUTES_NO_EVAL else evaluate_terraform(str_to_evaluate)
+ )
+ self.local_graph.update_vertex_attribute(
+ vertex, changed_attribute_key, evaluated_attribute_value, change_origin_id, attribute_at_dest
+ )
+
+ def evaluate_vertices_attributes(self) -> None:
+ for vertex in self.local_graph.vertices:
+ decoded_attributes = vertex.get_attribute_dict()
+ for attr in decoded_attributes:
+ if attr in vertex.changed_attributes:
+ continue
+ origin_value = decoded_attributes[attr]
+ if not isinstance(origin_value, str):
+ continue
+ evaluated_attribute_value = evaluate_terraform(origin_value)
+ if origin_value != evaluated_attribute_value:
+ vertex.update_inner_attribute(attr, vertex.attributes, evaluated_attribute_value)
+
+ @staticmethod
+ def group_edges_by_origin_and_label(edges: List[Edge]) -> List[List[Edge]]:
+ edge_groups: Dict[str, List[Edge]] = {}
+ for edge in edges:
+ origin_and_label_hash = calculate_hash(f"{edge.origin}{edge.label}")
+ if not edge_groups.get(origin_and_label_hash):
+ edge_groups[origin_and_label_hash] = []
+ edge_groups[origin_and_label_hash].append(edge)
+ return list(edge_groups.values())
+
+ def replace_value(
+ self,
+ edge: Edge,
+ original_val: List[Any],
+ replaced_key: str,
+ replaced_value: Any,
+ keep_origin: bool,
+ count: int = 0,
+ ) -> Union[Any, List[Any]]:
+ if count > 1:
+ return original_val
+ new_val = replace_string_value(
+ original_str=original_val,
+ str_to_replace=replaced_key,
+ replaced_value=replaced_value,
+ keep_origin=keep_origin,
+ )
+ return new_val
+
+ def evaluate_non_rendered_values(self) -> None:
+ for vertex in self.local_graph.vertices:
+ changed_attributes = {}
+ attributes: Dict[str, Any] = {}
+ vertex.get_origin_attributes(attributes)
+ for attribute in filter(
+ lambda attr: attr not in reserved_attribute_names
+ and not attribute_has_nested_attributes(attr, vertex.attributes),
+ vertex.attributes,
+ ):
+ curr_val = vertex.attributes.get(attribute)
+ lst_curr_val = curr_val
+ if not isinstance(lst_curr_val, list):
+ lst_curr_val = [lst_curr_val]
+ if len(lst_curr_val) > 0 and isinstance(lst_curr_val[0], Tree):
+ lst_curr_val[0] = str(lst_curr_val[0])
+ evaluated_lst = []
+ for inner_val in lst_curr_val:
+ if (
+ isinstance(inner_val, str)
+ and not any(c in inner_val for c in ["{", "}", "[", "]", "="])
+ or attribute in ATTRIBUTES_NO_EVAL
+ ):
+ evaluated_lst.append(inner_val)
+ continue
+ evaluated = self.evaluate_value(inner_val)
+ evaluated_lst.append(evaluated)
+ evaluated = evaluated_lst
+ if not isinstance(curr_val, list):
+ evaluated = evaluated_lst[0]
+ if evaluated != curr_val:
+ vertex.update_inner_attribute(attribute, vertex.attributes, evaluated)
+ changed_attributes[attribute] = evaluated
+ self.local_graph.update_vertex_config(vertex, changed_attributes)
+
+ def evaluate_value(self, val: Any) -> Any:
+ if type(val) not in [str, list, set, dict]:
+ evaluated_val = val
+ elif isinstance(val, str):
+ evaluated_val = evaluate_terraform(val, keep_interpolations=False)
+ elif isinstance(val, list):
+ evaluated_val = []
+ for v in val:
+ evaluated_val.append(self.evaluate_value(v))
+ elif isinstance(val, set):
+ evaluated_val = set()
+ for v in val:
+ evaluated_v = self.evaluate_value(v)
+ if isinstance(evaluated_v, Hashable):
+ evaluated_val.add(evaluated_v)
+ else:
+ evaluated_val.add(str(evaluated_v))
+ else:
+ evaluated_val = {}
+ for k, v in val.items():
+ evaluated_key = self.evaluate_value(k)
+ evaluated_val[evaluated_key] = self.evaluate_value(v)
+ return evaluated_val
diff --git a/checkov/terraform/variable_rendering/safe_eval_functions.py b/checkov/terraform/variable_rendering/safe_eval_functions.py
new file mode 100644
index 0000000000..2ef5aaec43
--- /dev/null
+++ b/checkov/terraform/variable_rendering/safe_eval_functions.py
@@ -0,0 +1,190 @@
+import itertools
+import logging
+import re
+from functools import reduce
+from math import ceil, floor, log
+from typing import Union, Any, Dict, Callable, List, Optional
+
+from checkov.terraform.parser_functions import tonumber, FUNCTION_FAILED, create_map, tobool, tomap, tostring
+
+"""
+This file contains a custom implementation of the builtin `eval` function.
+`eval` is not a safe function, because it can execute *every* command,
+so this file overrides `eval` and allows only the functions in SAFE_EVAL_DICT.
+
+The functions are an implementation of Terraform's built-in functions
+https://www.terraform.io/docs/configuration/functions.html
+
+Not all of the functions are implemented yet. If a function doesn't exist, the original value is returned.
+"""
+
+
+def _find_regex_groups(pattern: str, input_str: str) -> Optional[Union[Dict[str, str], List[str]]]:
+ match = re.match(pattern, input_str)
+ if match:
+ if match.groupdict():
+ # try to find named capturing groups
+ return match.groupdict()
+ if list(match.groups()):
+ # try to find unnamed capturing groups
+ return list(match.groups())
+ return None
+
+
+def regex(pattern: str, input_str: str) -> Union[Dict[str, str], List[str], str]:
+ try:
+ groups = _find_regex_groups(pattern, input_str)
+ if groups is not None:
+ return groups
+
+ results: List[str] = re.findall(pattern, input_str)
+ # return first match
+ if len(results) > 0:
+ return results[0]
+ return ""
+ except TypeError:
+ return f"regex({pattern}, {input_str})"
+
+
+def regexall(pattern: str, input_str: str) -> Union[Dict[str, str], List[str], str]:
+ try:
+ groups = _find_regex_groups(pattern, input_str)
+ if groups is not None:
+ return groups
+
+ results = re.findall(pattern, input_str)
+ return results
+ except TypeError:
+ return f"regexall({pattern}, {input_str})"
+
+
+def trim(input_str: str, chars_to_remove: str) -> str:
+ for c in chars_to_remove:
+ input_str = input_str.replace(c, "")
+ return input_str
+
+
+def coalesce(*arg: Any) -> Any:
+ return reduce(lambda x, y: x if x not in [None, ""] else y, arg)
+
+
+def coalesce_list(*arg: List[Any]) -> List[Any]:
+ return reduce(lambda x, y: x if x not in [None, []] else y, arg)
+
+
+def flatten(lst: List[List[Any]]) -> List[Any]:
+ res = [item for sublist in lst for item in sublist]
+ if any(type(elem) is list for elem in res):
+ return flatten(res)
+ else:
+ return res
+
+
+def matchkeys(values_list: List[Any], keys_list: List[Any], search_set: List[Any]) -> List[Any]:
+ matching = set()
+ for search in search_set:
+ indices = [i for i, x in enumerate(keys_list) if x == search]
+ for i in indices:
+ matching.add(values_list[i])
+
+ return list(matching)
+
+
+def reverse(lst: List[Any]) -> List[Any]:
+ lst.reverse()
+ return lst
+
+
+def sort(lst: List[str]) -> List[str]:
+ lst.sort()
+ return lst
+
+
+def merge(*args: Any) -> Dict[str, Any]:
+ res: Dict[str, Any] = {}
+ for d in args:
+ res = {**res, **d}
+ return res
+
+
+def wrap_func(f: Callable[..., Any], *args: Any) -> Any:
+ res = f(*args)
+ if res == FUNCTION_FAILED:
+ raise ValueError
+ return res
+
+
+SAFE_EVAL_FUNCTIONS: List[str] = []
+SAFE_EVAL_DICT = dict([(k, locals().get(k, None)) for k in SAFE_EVAL_FUNCTIONS])
+
+# math functions
+SAFE_EVAL_DICT["abs"] = abs
+SAFE_EVAL_DICT["ceil"] = ceil
+SAFE_EVAL_DICT["floor"] = floor
+SAFE_EVAL_DICT["log"] = log
+SAFE_EVAL_DICT["max"] = max
+SAFE_EVAL_DICT["min"] = min
+SAFE_EVAL_DICT["parsint"] = int
+SAFE_EVAL_DICT["pow"] = pow
+SAFE_EVAL_DICT["signum"] = lambda x: -1 if x < 0 else 0 if x == 0 else 1
+
+# string functions
+SAFE_EVAL_DICT["chomp"] = lambda x: x.rstrip()
+SAFE_EVAL_DICT["format"] = lambda text_to_format, *args: (text_to_format % args)
+SAFE_EVAL_DICT["formatlist"] = lambda text_to_format, args_list: [(text_to_format % args) for args in args_list]
+SAFE_EVAL_DICT["indent"] = lambda num_of_space, input_str: input_str
+SAFE_EVAL_DICT["join"] = lambda separator, lst: separator.join(lst)
+SAFE_EVAL_DICT["lower"] = lambda input_str: input_str.lower()
+SAFE_EVAL_DICT["regex"] = regex
+SAFE_EVAL_DICT["regexall"] = regexall
+SAFE_EVAL_DICT["replace"] = lambda string, substring, replacement: string.replace(substring, replacement)
+SAFE_EVAL_DICT["split"] = lambda separator, input_str: input_str.split(separator)
+SAFE_EVAL_DICT["strrev"] = lambda input_str: input_str[::-1]
+SAFE_EVAL_DICT["substr"] = lambda input_str, offset, length: input_str[offset : offset + length]
+SAFE_EVAL_DICT["title"] = lambda input_str: input_str.title()
+SAFE_EVAL_DICT["trim"] = trim
+SAFE_EVAL_DICT["trimprefix"] = lambda input_str, prefix: input_str.lstrip(prefix)
+SAFE_EVAL_DICT["trimsuffix"] = lambda input_str, prefix: input_str.rstrip(prefix)
+SAFE_EVAL_DICT["trimspace"] = lambda input_str: input_str.strip()
+SAFE_EVAL_DICT["upper"] = lambda input_str: input_str.upper()
+
+# collections
+SAFE_EVAL_DICT["chunklist"] = lambda lst, chunk_size: [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)]
+SAFE_EVAL_DICT["coalesce"] = coalesce
+SAFE_EVAL_DICT["coalescelist"] = coalesce_list
+SAFE_EVAL_DICT["compact"] = lambda lst: list(filter(lambda l: l != "", lst))
+SAFE_EVAL_DICT["concat"] = lambda *lists: list(itertools.chain(*lists))
+SAFE_EVAL_DICT["contains"] = lambda lst, value: value in lst
+SAFE_EVAL_DICT["distinct"] = lambda lst: list(dict.fromkeys(lst))
+SAFE_EVAL_DICT["element"] = lambda lst, index: lst[index]
+SAFE_EVAL_DICT["flatten"] = flatten
+SAFE_EVAL_DICT["index"] = lambda lst, value: lst.index(value)
+SAFE_EVAL_DICT["keys"] = lambda map_input: list(map_input.keys())
+SAFE_EVAL_DICT["length"] = len
+SAFE_EVAL_DICT["list"] = lambda *args: list(args)
+SAFE_EVAL_DICT["lookup"] = lambda map_input, key, default: map_input.get(key, default)
+SAFE_EVAL_DICT["map"] = lambda *args: wrap_func(create_map, list(args))
+SAFE_EVAL_DICT["matchkeys"] = matchkeys
+SAFE_EVAL_DICT["merge"] = merge
+# SAFE_EVAL_DICT['range']
+SAFE_EVAL_DICT["reverse"] = reverse
+SAFE_EVAL_DICT["sort"] = sort
+
+
+# type conversion
+SAFE_EVAL_DICT["tobool"] = lambda arg: wrap_func(tobool, arg)
+SAFE_EVAL_DICT["tolist"] = lambda *args: list(*args)
+SAFE_EVAL_DICT["tomap"] = lambda arg: wrap_func(tomap, str(arg))
+SAFE_EVAL_DICT["tonumber"] = lambda arg: arg if type(arg) in [int, float] else wrap_func(tonumber, arg)
+SAFE_EVAL_DICT["toset"] = lambda origin: set(origin)
+SAFE_EVAL_DICT["tostring"] = lambda arg: arg if isinstance(arg, str) else wrap_func(tostring, str(arg))
+
+# encoding
+SAFE_EVAL_DICT["jsonencode"] = lambda arg: arg
+
+
+def evaluate(input_str: str) -> Any:
+ if "__" in input_str:
+ logging.warning(f"got a substring with double underscore, which is not allowed. origin string: {input_str}")
+ return input_str
+ return eval(input_str, {"__builtins__": None}, SAFE_EVAL_DICT) # nosec
diff --git a/checkov/version.py b/checkov/version.py
index bfd1ba0f18..9b3583523b 100644
--- a/checkov/version.py
+++ b/checkov/version.py
@@ -1 +1 @@
-version = '1.0.717'
+version = '2.0.363'
diff --git a/coverage.svg b/coverage.svg
index 4b105c689f..0e4754384d 100644
--- a/coverage.svg
+++ b/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 82%
- 82%
+ 86%
+ 86%
diff --git a/docs/1.Introduction/Getting Started.md b/docs/1.Introduction/Getting Started.md
deleted file mode 100644
index 6f4bfad516..0000000000
--- a/docs/1.Introduction/Getting Started.md
+++ /dev/null
@@ -1,855 +0,0 @@
----
-layout: default
-published: true
-title: Getting Started
-order: 2
----
-
-# Getting Started
-
-The installation is quick and straightforward - install, configure input & scan.
-
-
-```bash
-# install from pypi using pip
-pip install checkov
-
-
-# select an input folder that contains your terraform plan files
-checkov -d /user/tf
-```
-
-## CLI Options
-```bash
- -h, --help show this help message and exit
- -v, --version version
- -d DIRECTORY, --directory DIRECTORY
- IaC root directory (can not be used together with
- --file).
- -f FILE, --file FILE IaC file(can not be used together with --directory)
- --external-checks-dir EXTERNAL_CHECKS_DIR
- Directory for custom checks to be loaded. Can be
- repeated
- -l, --list List checks
- -o [{cli,json,junitxml,github_failed_only}], --output [{cli,json,junitxml,github_failed_only}]
- Report output format
- --quiet in case of CLI output, display only failed checks
- --framework {cloudformation,terraform,kubernetes,all}
- filter scan to run only on a specific infrastructure
- code frameworks
- -c CHECK, --check CHECK
- filter scan to run only on a specific check
- identifier(allowlist), You can specify multiple checks
- separated by comma delimiter
- --skip-check SKIP_CHECK
- filter scan to run on all check but a specific check
- identifier(denylist), You can specify multiple checks
- separated by comma delimiter
- -s, --soft-fail Runs checks but suppresses error code
- --bc-api-key BC_API_KEY
- Bridgecrew API key
- --repo-id REPO_ID Identity string of the repository, with form
- /
- -b BRANCH, --branch BRANCH
- Selected branch of the persisted repository. Only has
- effect when using the --bc-api-key flag
-
-```
-
-## Scan result sample (CLI)
-
-Consider the following Terraform configuration of an S3 bucket:
-```hcl-terraform
-resource "aws_s3_bucket" "foo-bucket" {
- region = var.region
- bucket = local.bucket_name
- force_destroy = true
-
- tags = {
- Name = "foo-${data.aws_caller_identity.current.account_id}"
- }
- versioning {
- enabled = true
- }
- logging {
- target_bucket = "${aws_s3_bucket.log_bucket.id}"
- target_prefix = "log/"
- }
- server_side_encryption_configuration {
- rule {
- apply_server_side_encryption_by_default {
- kms_master_key_id = "${aws_kms_key.mykey.arn}"
- sse_algorithm = "aws:kms"
- }
- }
- }
- acl = "private"
-}
-```
-The appropriate output report is:
-
-```bash
-Passed checks: 4, Failed checks: 0, Skipped checks: 0
-
-Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure the S3 bucket has access logging enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure all data stored in the S3 bucket have versioning enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "S3 Bucket has an ACL defined which allows public access."
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-```
-The scanned bucket's configuration seems to comply with the available ``aws_s3_bucket`` resource type checks.
-
-Suppose that now the bucket is used for static content hosting, and thus requires to configured with
-allowed public access:
-```hcl-terraform
-resource "aws_s3_bucket" "foo-bucket" {
- region = var.region
- bucket = local.bucket_name
- force_destroy = true
-
- tags = {
- Name = "foo-${data.aws_caller_identity.current.account_id}"
- }
- versioning {
- enabled = true
- }
- logging {
- target_bucket = "${aws_s3_bucket.log_bucket.id}"
- target_prefix = "log/"
- }
- server_side_encryption_configuration {
- rule {
- apply_server_side_encryption_by_default {
- kms_master_key_id = "${aws_kms_key.mykey.arn}"
- sse_algorithm = "aws:kms"
- }
- }
- }
- acl = "public-read"
-}
-data "aws_caller_identity" "current" {}
-
-```
-The output report would then contain the failed check:
-```bash
-Passed checks: 3, Failed checks: 1, Skipped checks: 0
-
-Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure the S3 bucket has access logging enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure all data stored in the S3 bucket have versioning enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "S3 Bucket has an ACL defined which allows public access."
- FAILED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
- 1 | resource "aws_s3_bucket" "foo-bucket" {
- 2 | region = var.region
- 3 | bucket = local.bucket_name
- 4 | force_destroy = true
- 5 |
- 6 | tags = {
- 7 | Name = "foo-${data.aws_caller_identity.current.account_id}"
- 8 | }
- 9 | versioning {
- 10 | enabled = true
- 11 | }
- 12 | logging {
- 13 | target_bucket = "${aws_s3_bucket.log_bucket.id}"
- 14 | target_prefix = "log/"
- 15 | }
- 16 | server_side_encryption_configuration {
- 17 | rule {
- 18 | apply_server_side_encryption_by_default {
- 19 | kms_master_key_id = "${aws_kms_key.mykey.arn}"
- 20 | sse_algorithm = "aws:kms"
- 21 | }
- 22 | }
- 23 | }
- 24 | acl = "public-read"
- 25 | }
-
-```
-The corresponding check would now fail, and the report will include the appropriate failing configuration
-source code.
-
-In order to skip the failed check, we annotate the bucket with a suppression comment (which needs to appear inside the resource scope):
-```hcl-terraform
-resource "aws_s3_bucket" "foo-bucket" {
- # checkov:skip=CKV_AWS_20:The bucket is a public static content host
- region = var.region
- bucket = local.bucket_name
- force_destroy = true
- tags = {
- Name = "foo-${data.aws_caller_identity.current.account_id}"
- }
- versioning {
- enabled = true
- }
- logging {
- target_bucket = "${aws_s3_bucket.log_bucket.id}"
- target_prefix = "log/"
- }
- server_side_encryption_configuration {
- rule {
- apply_server_side_encryption_by_default {
- kms_master_key_id = "${aws_kms_key.mykey.arn}"
- sse_algorithm = "aws:kms"
- }
- }
- }
- acl = "public-read"
-}
-```
-
-Checkov would then skip check ``CKV_AWS_20``, and the output report would be:
-
-```bash
-Passed checks: 3, Failed checks: 0, Skipped checks: 1
-
-Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure the S3 bucket has access logging enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "Ensure all data stored in the S3 bucket have versioning enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-25
-
-
-Check: "S3 Bucket has an ACL defined which allows public access."
- SKIPPED for resource: aws_s3_bucket.foo-bucket
- Suppress comment: The bucket is a public static content host
- File: /example.tf:1-25
-```
-
-### Running a specific check(s)
-To scan you directory with only a specific check use the `-c`\ `--check` flag. You can use multiple checks with comma `,` delimiter.
-This is another way to skip execution of specific checks on a allowlist fashion
-
-The following example will show results only for 2 scans (CKV_AWS_1 and CKV_AWS_2) :
-
-```bash
-checkov -d /user/tf --check CKV_AWS_1,CKV_AWS_2
-```
-
-## Export scan to JSON
-For the sake of the example, we use the previous bucket configuration and disable it;s versioning is disabled for a check to fail.
-
-```bash
-checkov -d /user/tf -o json
-```
-
-Sample output
-```json
-{
- "results": {
- "passed_checks": [
- {
- "check_id": "CKV_AWS_19",
- "check_name": "Ensure all data stored in the S3 bucket is securely encrypted at rest",
- "check_result": {
- "result": "PASSED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3Encryption"
- },
- {
- "check_id": "CKV_AWS_18",
- "check_name": "Ensure the S3 bucket has access logging enabled",
- "check_result": {
- "result": "PASSED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3AccessLogs"
- }
- ],
- "failed_checks": [
- {
- "check_id": "CKV_AWS_21",
- "check_name": "Ensure all data stored in the S3 bucket have versioning enabled",
- "check_result": {
- "result": "FAILED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3Versioning"
- }
- ],
- "skipped_checks": [
- {
- "check_id": "CKV_AWS_20",
- "check_name": "S3 Bucket has an ACL defined which allows public access.",
- "check_result": {
- "result": "SKIPPED",
- "suppress_comment": "The bucket is a public static content host"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3PublicACL"
- }
- ],
- "parsing_errors": []
- },
- "summary": {
- "passed": 2,
- "failed": 1,
- "skipped": 1,
- "parsing_errors": 0,
- "checkov_version": "1.0.63"
- }
-}
-```
-
-### Sample policy
-
-Each Checkov policy is defined by resources it scans and expected values for related resource blocks.
-
-For example, a policy that ensures all data is stored in S3 is versioned, scans the ``versioning`` configuration for all ``aws_s3_bucket`` supported resources. The `scan_resource_conf` is a method that defines the scan's expected behavior, i.e. ``versioning_block['enabled']``
-
-```python
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-class S3Versioning(BaseResourceCheck):
- def __init__(self):
- name = "Ensure all data stored in the S3 bucket is versioned"
- id = "CKV_AWS_21"
- supported_resources = ['aws_s3_bucket']
- categories = [CheckCategories.BACKUP_AND_RECOVERY]
- super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
- def scan_resource_conf(self, conf):
- if 'versioning' in conf.keys():
- versioning_block = conf['versioning'][0]
- if versioning_block['enabled'][0]:
- return CheckResult.SUCCESS
- return CheckResult.FAILURE
-scanner = S3Versioning()
-```
-
-Consider the following Terraform AWS S3 bucket configuration:
-```
-# example_versioning_pass.tf
-resource "aws_s3_bucket" "foo-bucket" {
- region = var.region
- bucket = local.bucket_name
- force_destroy = true
-
- tags = {
- Name = "foo-${data.aws_caller_identity.current.account_id}"
- }
- versioning {
- enabled = true
- }
-
-```
-
-Running `checkov -d ./example_versioning_pass.tf -o cli` would yield a `PASSED` check result for the `S3Versioning` scanner:
-
-```bash
-...
-Check: "Ensure all data stored in the S3 bucket have versioning enabled"
- PASSED for resource: aws_s3_bucket.foo-bucket
- File: /example.tf:1-24
-...
-```
-
-If the scanned configuration would have been without bucket versioning enabled, the corresponding check would fail:
-
-Configuration:
-
-```bash
-example_acl_fail.tf
-resource "aws_s3_bucket" "foo-bucket" {
- region = var.region
- bucket = local.bucket_name
- force_destroy = true
-
- tags = {
- Name = "foo-${data.aws_caller_identity.current.account_id}"
- }
- versioning {
- enabled = false
- }
-}
-
-```
-Run result:
-
-
-```bash
-> checkov -d ./example_versioning_fail.tf -o cli
-
-Check: "Ensure all data stored in the S3 bucket have versioning enabled"
- FAILED for resource: aws_s3_bucket.foo-bucket
- File: /example_versioning_fail.tf:1-12
-
- 1 | resource "aws_s3_bucket" "foo-bucket" {
- 2 | region = var.region
- 3 | bucket = local.bucket_name
- 4 | force_destroy = true
- 5 |
- 6 | tags = {
- 7 | Name = "foo-${data.aws_caller_identity.current.account_id}"
- 8 | }
- 9 | versioning {
- 10 | enabled = false
- 11 | }
- 12 | }
-```
-
-## What's Next?
-From this point, you can head to the [Policies](Policies.md) for further examples or the How-to Guides section if you’re ready to get your hands dirty.
diff --git a/docs/1.Introduction/Policies.md b/docs/1.Introduction/Policies.md
deleted file mode 100644
index 5f49362fc1..0000000000
--- a/docs/1.Introduction/Policies.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-layout: default
-published: true
-title: Policies
-order: 3
----
-
-# Policies
-
-Checkov runs static code analysis on Terraform files. It will scan each resource only for policies that were defined by the policy file.
-
-Policies are expressed as Python files and include the following:
-
-* The type of resource to run the policy against
-* The required result(s) of a specific configuration under that resource
-
-
-
-## Writing a new Policy
-
-A policy needs to specify the following items:
-
-``name`` : A new policy's unique purpose; It should ideally specify the positive desired outcome of the policy.
-
-``id``: A mandatory unique identifier of a policy; Native policies written by Bridgecrew contributors will follow the following convention ``CKV_providerType_serialNumber``. (e.g. `CKV_AWS_9` , `CKV_GCP_12`)
-
-``supported_resources``: Infrastructure objects, as described in the scanned IaC's language; This should usually contain one specific resource block. If you support multiple resources, you can use `*` to match any type of entity in that specific domain (This depends on which check base class you extend. If you extend `checkov.terraform.checks.resource.base_resource_check.BaseResourceCheck`, the check is registered for all terraform resources.) `?ws_*` will match anything where the second character is a `'w'`, the third is a `'s'` and the fourth is a `'_'`.
-
-``categories``: Categorization of a scan; usually used to produce compliance reports, pipeline analytics and infrastructure health metrics, etc.
-
-
-
-For this tutorial let's produce a policy that ensures that new RDS services spun-up are encrypted at rest, given a scanned Terraform configuration ([CKV_AWS_16](https://github.com/bridgecrewio/checkov/blob/master/checkov/terraform/checks/resource/aws/RDSEncryption.py)).
-1. Start by creating a new file in the AWS check directory ``checkov/terraform/checks/resource/aws/RDSEncryption.py``.
-2. Import the following:
-
-```python
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-```
-
-3. At this point, we define the meta entities for this check: ``name``, ``id``, ``supported_resources``, ``categories``
-
-```python
-class RDSEncryption(BaseResourceCheck):
- def __init__(self):
- name = "Ensure all data stored in the RDS is securely encrypted at rest"
- id = "CKV_AWS_16"
- supported_resources = ['aws_db_instance']
- categories = [CheckCategories.ENCRYPTION]
- super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-```
-
-4. Next we define a simple check of the ```aws_db_instance``` resource block to find if ```aws_db_instance``` is disabled. When disabled, we require it will result in a ```CheckResult.FAILURE```.
-
-```python
-def scan_resource_conf(self, conf):
- """
- Looks for encryption configuration at aws_db_instance:
- https://www.terraform.io/docs/providers/aws/d/db_instance.html
- :param conf: aws_db_instance configuration
- :return:
- """
- if 'storage_encrypted' in conf.keys():
- key = conf['storage_encrypted'][0]
- if key:
- return CheckResult.PASSED
- return CheckResult.FAILED
-```
-
-5. Last - our file should conclude the policy name and operationalize it with this statement.
-
-```python
-check = RDSEncryption()
-```
-
-
-
-## Run a new scan
-
-To run a new scan containing our newly added policy, use the ```checkov``` command.
-
-```bash
-checkov -d /user/tf
-```
-
-
-
-## Next Steps
-
-Explore your scan [Results](Results.md) and check out the supported output methods (CLI, JSON, Junit XML).
-
-##
diff --git a/docs/1.Introduction/Results.md b/docs/1.Introduction/Results.md
deleted file mode 100644
index 041d24056c..0000000000
--- a/docs/1.Introduction/Results.md
+++ /dev/null
@@ -1,662 +0,0 @@
----
-layout: default
-published: true
-title: Results
-order: 4
----
-
-# Results
-
-## Scan type
-Checkov scans IaC with the context of the IaC's type. Currently, Checkov scans both Terraform and AWS CloudFormation configurations,
-and outputs a scan report for each IaC type.
-
-## Scan outputs
-
-After running a ``checkov`` command on a IaC file or folder, the scan's results will print in your current session Checkov currently supports output in 3 common formats: CLI, JSON & JUnit XML.
-
-
-
-### CLI Output
-
-Running a Checkov scan with no output parameter will result in a color-coded CLI print output.
-
-```
-checkov -d /user/path/to/iac
-```
-
-Each print includes a scan summary and detailed scan results following.
-For example, the following are the Terraform and CloudFormation scan reports:
-
-```bash
-
-Terraform scan results:
-
-Passed checks: 7, Failed checks: 15, Skipped checks: 2
-
-Check: "S3 Bucket has an ACL defined which allows public access."
- PASSED for resource: aws_s3_bucket.sls_deployment_bucket_name
- File: /../regionStack/main.tf:23-32
-
-Check: "Ensure the S3 bucket has access logging enabled"
- FAILED for resource: aws_s3_bucket.template_bucket
- File: /main.tf:81-92
-
- 81 | resource "aws_s3_bucket" "template_bucket" {
- 82 | region = var.region
- 83 | bucket = local.bucket_name
- 84 | acl = "public-read"
- 85 | # checkov:skip=CKV_AWS_20: The bucket is a public static content host
- 86 | acl = "public-read"
- 87 | force_destroy = true
- 88 | # checkov:skip=CKV_AWS_19: Bucket access logs is not required for public content
- 89 | tags = {
- 90 | Name = "${local.bucket_name}-${data.aws_caller_identity.current.account_id}"
- 91 | }
- 92 | }
-
-Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
- SKIPPED for resource: aws_s3_bucket.template_bucket
- Suppress comment: The bucket is a public static content host, that does not require encryption
- File: /main.tf:81-92 ```
-
-CloudFormation scan results:
-
-Passed checks: 1, Failed checks: 1, Skipped checks: 0
-
-Check: CKV_AWS_36: "Ensure CloudTrail log file validation is enabled"
- PASSED for resource: AWS::CloudTrail::Trail.BridgecrewCWSTrail
- File: /cloud-formation-template.json:300-343
-
-
-Check: CKV_AWS_35: "Ensure CloudTrail logs are encrypted at rest using KMS CMKs"
- FAILED for resource: AWS::CloudTrail::Trail.BridgecrewCWSTrail
- File: /cloud-formation-template.json:300-343
-
- 300 | "BridgecrewCWSTrail": {
- 301 | "Condition": "CreateNewTrail",
- 302 | "Type": "AWS::CloudTrail::Trail",
- 303 | "DependsOn": [
- 304 | "BridgecrewCWSTopicPolicy",
- 305 | "BridgecrewCWSBucketPolicy"
- 306 | ],
- 307 | "Properties": {
- 308 | "TrailName": {
- 309 | "Fn::Join": [
- 310 | "",
- 311 | [
- 312 | {
- 313 | "Ref": "ResourceNamePrefix"
- 314 | },
- 315 | "-bridgecrewcws"
- 316 | ]
- 317 | ]
- 318 | },
- 319 | "S3BucketName": {
- 320 | "Ref": "BridgecrewCWSBucket"
- 321 | },
- 322 | "S3KeyPrefix": {
- 323 | "Fn::If": [
- 324 | "NewTrailUsesLogFilePrefix",
- 325 | {
- 326 | "Ref": "NewTrailLogFilePrefix"
- 327 | },
- 328 | {
- 329 | "Ref": "AWS::NoValue"
- 330 | }
- 331 | ]
- 332 | },
- 333 | "SnsTopicName": {
- 334 | "Fn::GetAtt": [
- 335 | "BridgecrewCWSTopic",
- 336 | "TopicName"
- 337 | ]
- 338 | },
- 339 | "EnableLogFileValidation": true,
- 340 | "IncludeGlobalServiceEvents": true,
- 341 | "IsMultiRegionTrail": true,
- 342 | "IsLogging": true
- 343 | }
-
-
-
-Process finished with exit code 1
-
-```
-
-### JSON Output
-
-Running a Checkov scan with the JSON output parameter (```-o json```) will result in JSON print output.
-
-```
-checkov -d /user/tf -o json
-```
-
-```json
-{
- "check_type": "Terraform",
- "results": {
- "passed_checks": [
- {
- "check_id": "CKV_AWS_19",
- "check_name": "Ensure all data stored in the S3 bucket is securely encrypted at rest",
- "check_result": {
- "result": "PASSED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3Encryption"
- },
- {
- "check_id": "CKV_AWS_18",
- "check_name": "Ensure the S3 bucket has access logging enabled",
- "check_result": {
- "result": "PASSED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3AccessLogs"
- }
- ],
- "failed_checks": [
- {
- "check_id": "CKV_AWS_21",
- "check_name": "Ensure all data stored in the S3 bucket have versioning enabled",
- "check_result": {
- "result": "FAILED"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3Versioning"
- }
- ],
- "skipped_checks": [
- {
- "check_id": "CKV_AWS_20",
- "check_name": "S3 Bucket has an ACL defined which allows public access.",
- "check_result": {
- "result": "SKIPPED",
- "suppress_comment": "The bucket is a public static content host"
- },
- "code_block": [
- [
- 1,
- "resource \"aws_s3_bucket\" \"foo-bucket\" {\n"
- ],
- [
- 2,
- " region = var.region\n"
- ],
- [
- 3,
- " bucket = local.bucket_name\n"
- ],
- [
- 4,
- " force_destroy = true\n"
- ],
- [
- 5,
- " #checkov:skip=CKV_AWS_20:The bucket is a public static content host\n"
- ],
- [
- 6,
- " tags = {\n"
- ],
- [
- 7,
- " Name = \"foo-${data.aws_caller_identity.current.account_id}\"\n"
- ],
- [
- 8,
- " }\n"
- ],
- [
- 9,
- " versioning {\n"
- ],
- [
- 10,
- " enabled = false\n"
- ],
- [
- 11,
- " }\n"
- ],
- [
- 12,
- " logging {\n"
- ],
- [
- 13,
- " target_bucket = \"${aws_s3_bucket.log_bucket.id}\"\n"
- ],
- [
- 14,
- " target_prefix = \"log/\"\n"
- ],
- [
- 15,
- " }\n"
- ],
- [
- 16,
- " server_side_encryption_configuration {\n"
- ],
- [
- 17,
- " rule {\n"
- ],
- [
- 18,
- " apply_server_side_encryption_by_default {\n"
- ],
- [
- 19,
- " kms_master_key_id = \"${aws_kms_key.mykey.arn}\"\n"
- ],
- [
- 20,
- " sse_algorithm = \"aws:kms\"\n"
- ],
- [
- 21,
- " }\n"
- ],
- [
- 22,
- " }\n"
- ],
- [
- 23,
- " }\n"
- ],
- [
- 24,
- " acl = \"public-read\"\n"
- ],
- [
- 25,
- "}\n"
- ]
- ],
- "file_path": "/example.tf",
- "file_line_range": [
- 1,
- 25
- ],
- "resource": "aws_s3_bucket.foo-bucket",
- "check_class": "checkov.terraform.checks.resource.aws.S3PublicACL"
- }
- ],
- "parsing_errors": []
- },
- "summary": {
- "passed": 2,
- "failed": 1,
- "skipped": 1,
- "parsing_errors": 0,
- "checkov_version": "1.0.63"
- }
-}
-```
-The print includes detailed structured data-blocks that contain exact references to code blocks, line ranges, optional skipped checks,
-and scanned resources.
-
-
-
-### JUnit XML
-
-Running a Checkov scan with the JSON output parmeter (```-o junitxml```) will result in JUnit XML print output.
-
-```
-checkov -d /user/tf -o junitxml
-```
-
-This print also includes detailed structured data-blocks that contain exact references to code blocks, line ranges and resources scanned.
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-## Next Steps
-
-Explore the [suppression](../2.Concepts/Suppressions.md)
-
diff --git a/docs/1.Welcome/Feature Descriptions.md b/docs/1.Welcome/Feature Descriptions.md
new file mode 100644
index 0000000000..a0e84e4dbf
--- /dev/null
+++ b/docs/1.Welcome/Feature Descriptions.md
@@ -0,0 +1,41 @@
+---
+layout: default
+published: true
+title: Feature Descriptions
+nav_order: 4
+---
+
+# Feature Descriptions
+
+With Checkov you can:
+
+* Run a variety of scan types
+* Enable Checkov to run as part of your CI/CD workflow
+* Create and contribute custom Checkov policies
+
+## Running Checkov
+
+With Checkov you can scan a repository, branch, folder, or a single file with attribute-based misconfigurations or connection state errors. See [CLI Command Reference](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html).
+
+When running Checkov, you can also:
+
+* [Review scan results](https://www.checkov.io/2.Basics/Reviewing%20Scan%20Results.html)
+* [Suppress or skip](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html)
+* [Scan credentials and secrets](https://www.checkov.io/2.Basics/Scanning%20Credentials%20and%20Secrets.html)
+* [Scan Kubernetes clusters](https://www.checkov.io/4.Integrations/Kubernetes.html)
+* [Scan Terraform plan output and 3rd party modules](https://www.checkov.io/4.Integrations/Terraform%20Scanning.html)
+
+## Integrating with CI/CD
+In addition to integrating with your code repository, Checkov can also integrate with your automated build pipeline via CI/CD providers. When your build tests run, Checkov will scan your infrastructure as code files for misconfigurations and you can review the output directly in your CI pipeline.
+
+* [Integrate with Jenkins](https://www.checkov.io/4.Integrations/Jenkins.html)
+* [Integrate with Bitbucket Cloud Pipelines](https://www.checkov.io/4.Integrations/Bitbucket%20Cloud%20Pipelines.html)
+* [Integrate with Github Actions](https://www.checkov.io/4.Integrations/GitHub%20Actions.html)
+* [Integrate with Gitlab CLI](https://www.checkov.io/4.Integrations/GitLab%20CLI.html)
+
+## Custom Policies
+
+* [Create custom Python attribute policies](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html)
+* [Create custom YAML attribute and composite policies](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html)
+* [Custom policy examples](https://www.checkov.io/3.Custom%20Policies/Examples.html)
+* [Share custom policies across repos](https://www.checkov.io/3.Custom%20Policies/Sharing%20Custom%20Policies.html)
diff --git a/docs/1.Welcome/Quick Start.md b/docs/1.Welcome/Quick Start.md
new file mode 100644
index 0000000000..a65657691d
--- /dev/null
+++ b/docs/1.Welcome/Quick Start.md
@@ -0,0 +1,178 @@
+---
+layout: default
+published: true
+title: Quick Start
+nav_order: 3
+---
+
+# Quick Start
+
+This Quick Start guide shows how to install Checkov, run a scan, and analyze the results.
+For more advanced configuration, see the [CLI Reference](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html) and the rest of this documentation.
+
+## Install Checkov from PyPI
+
+```text
+pip install checkov
+```
+
+## Select input folder and scan
+
+Use the command below to indicate the folder that contains your Terraform plan files and run a scan.
+
+```text
+checkov -d /user/tf
+```
+
+## Example
+
+### S3 Bucket configuration (compliant)
+
+Consider the configuration of an S3 bucket as represented in the Terraform sample below.
+
+```yaml
+resource "aws_s3_bucket" "foo-bucket" {
+ region = var.region
+ bucket = local.bucket_name
+ force_destroy = true
+
+ tags = {
+ Name = "foo-${data.aws_caller_identity.current.account_id}"
+ }
+ versioning {
+ enabled = true
+ }
+ logging {
+ target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ target_prefix = "log/"
+ }
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+ acl = "private"
+}
+```
+
+### Scan output for compliant S3 Bucket configuration
+
+The scan output would be:
+
+```xml
+Passed checks: 4, Failed checks: 0, Skipped checks: 0
+
+Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure the S3 bucket has access logging enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure all data stored in the S3 bucket have versioning enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "S3 Bucket has an ACL defined which allows public access."
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+```
+
+The configuration complies with the policies for AWS S3 resources.
+
+### S3 Bucket configuration (non-compliant)
+
+Suppose that now the same bucket is configured to allow public access:
+
+```text
+resource "aws_s3_bucket" "foo-bucket" {
+#same resource configuration as previous example, but acl set for public access.
+
+ acl = "public-read"
+}
+data "aws_caller_identity" "current" {}
+```
+
+### Scan output for non-compliant S3 Bucket Configuration
+
+The output report would then contain a failed check:
+
+```xml
+Passed checks: 3, Failed checks: 1, Skipped checks: 0
+
+Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure the S3 bucket has access logging enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure all data stored in the S3 bucket have versioning enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "S3 Bucket has an ACL defined which allows public access."
+ FAILED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+ 1 | resource "aws_s3_bucket" "foo-bucket" {
+ 2 | region = var.region
+ 3 | bucket = local.bucket_name
+ 4 | force_destroy = true
+ 5 |
+ 6 | tags = {
+ 7 | Name = "foo-${data.aws_caller_identity.current.account_id}"
+ 8 | }
+ 9 | versioning {
+ 10 | enabled = true
+ 11 | }
+ 12 | logging {
+ 13 | target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ 14 | target_prefix = "log/"
+ 15 | }
+ 16 | server_side_encryption_configuration {
+ 17 | rule {
+ 18 | apply_server_side_encryption_by_default {
+ 19 | kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ 20 | sse_algorithm = "aws:kms"
+ 21 | }
+ 22 | }
+ 23 | }
+ 24 | acl = "public-read"
+ 25 | }
+```
+
+## Visualizing scan output
+
+In addition to the various formats for seeing scan results (for example, CLI), you can also visualize Checkov results with a quick integration with a free Bridgecrew account. Read more about [visualizing scan results in the Bridgecrew platform](https://www.checkov.io/2.Basics/Visualizing%20Checkov%20Output.html).
+
+
+
+## Integrations
+
+In addition to integrating with your code repository, Checkov can also integrate with your automated build pipeline via CI/CD providers. When your build tests run, Checkov will scan your infrastructure as code files for misconfigurations.
+You can integrate Checkov with:
+
+* [Jenkins](https://www.checkov.io/4.Integrations/Jenkins.html)
+* [Bitbucket Cloud Pipelines](https://www.checkov.io/4.Integrations/Bitbucket%20Cloud%20Pipelines.html)
+* [GitHub Actions](https://www.checkov.io/4.Integrations/GitHub%20Actions.html)
+* [GitLab CLI](https://www.checkov.io/4.Integrations/GitLab%20CLI.html)
+* [Kubernetes](https://www.checkov.io/4.Integrations/Kubernetes.html)
+* [Terraform Plans and Third-Party Modules](https://www.checkov.io/4.Integrations/Terraform%20Scanning.html)
+
+## Add-ons
+
+To get real-time IaC scanning and in-line fixes directly from your IDE, check out the [Checkov Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=Bridgecrew.checkov).
+
+_An upcoming release of Checkov will include support for IntelliJ IDE._
diff --git a/docs/1.Welcome/Terms and Concepts.md b/docs/1.Welcome/Terms and Concepts.md
new file mode 100644
index 0000000000..278636c07b
--- /dev/null
+++ b/docs/1.Welcome/Terms and Concepts.md
@@ -0,0 +1,34 @@
+---
+layout: default
+published: true
+title: Terms and Concepts
+nav_order: 2
+---
+
+# Terms and Concepts
+
+**Policy:** Security policies define various aspects of your cloud configuration that impact the overall security of the environment. For example, multi-factor authentication should be enabled for the root account. A resource that is not in the state defined in a policy is non-compliant and will appear in scan results.
+
+**Composite Policy:** A composite, or connection-state is one in which Checkov looks for resources, or types of resources that are or are not connected to other resources. For example, it may be essential for certain resource types to be connected to security groups; or it may be important that certain resource types are not connected to other resources with public access. On each scan, Checkov creates a virtual connection graph based on Composite Policies. Learn more about [creating composite policies in YAML format](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html).
+
+**Incident:** Upon each scan, Checkov creates Incidents for each case of non-conformance to a Policy.
+
+**Resource:** A Resource is a Cloud Platform entity, for example, an Amazon EC2 instance, a CloudFormation stack, or an Amazon S3 bucket.
+
+**Suppression:** This is an action that can be taken to indicate that an Incident reported by Checkov is actually not problematic. When Suppressing an Incident, you can Suppress it for all relevant Resources or only specific Resources.
+
+## Commonly used terms
+
+**Infrastructure as code** frameworks are systems for automating infrastructure deployment, scaling and management through the use of machine-readable configuration files.
+
+**Declarative** configurations are absolute methods to design the execution of well-defined infrastructure building blocks.
+
+**Imperative** configurations are procedural methods to design the steps required to build a required end-result.
+
+**Immutable infrastructure** defines a version-controlled data model that enables reproducing point-in-time changes to individual attributes of a configuration manifest.
+
+**Terraform** is a popular open source declarative infrastructure as code framework used primarily to define resource in public cloud services.
+
+**CloudFormation** is a declarative infrastructure as code framework used to define resources in Amazon Web Services.
+
+**Kubernetes** is a popular open source declarative infrastructure as code framework used primarily to orchestrate containers in a virtual computing environment.
diff --git a/docs/1.Welcome/What is Checkov.md b/docs/1.Welcome/What is Checkov.md
new file mode 100644
index 0000000000..83b5b6187d
--- /dev/null
+++ b/docs/1.Welcome/What is Checkov.md
@@ -0,0 +1,58 @@
+---
+layout: default
+published: true
+title: What is Checkov?
+nav_order: 1
+---
+
+# What is Checkov?
+
+Checkov is a static code analysis tool for scanning infrastructure as code (IaC) files for misconfigurations that may lead to security or compliance problems. Checkov includes more than 750 predefined policies to check for common misconfiguration issues. Checkov also supports the creation and contribution of of [custom policies](../3.Custom%20Policies/Custom%20Policies%20Overview.md).
+
+## Supported IaC types
+
+Checkov scans these IaC file types:
+
+* Terraform (for AWS, GCP and Azure)
+* CloudFormation
+* Azure Resource Manager (ARM)
+* Kubernetes
+* Docker
+
+## Custom policies
+
+Custom policies can be created to check cloud resources based on configuration attributes (in [Python](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html) or [YAML](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html) or connection states (in [YAML](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html)). For composite policies, Checkov creates a cloud resource connection graph for deep misconfiguration analysis across resource relationships.
+
+## Compliance with Industry Standards
+
+In addition, Checkov scans for compliance with common industry standards such as the Center for Internet Security (CIS) and Amazon Web Services (AWS) Foundations Benchmark.
+
+## Integrates seamlessly with Bridgecrew
+
+Checkov integrates with advanced features in the [Bridgecew platform](https://bridgecrew.io/platform). You can sign up for a free Bridgecrew account by running Checkov with no arguments and following the CLI prompts, or directly via the [Bridgecrew website](https://www.bridgecrew.cloud/login/signUp). Bridgecrew extends Checkov's capabilities to provide runtime scanning and visibility, native VCS integrations, compliance benchmarking, and more.
+
+### Runtime Scanning
+
+Bridgecrew can validate the same Checkov IaC policies against your runtime cloud environments in AWS, Azure and Google Cloud, allowing you to find and fix issues in existing deployments and detect cloud drifts. Read more in [Bridgecrew's documentation](https://docs.bridgecrew.io/docs/step-2-integrate-with-cloud-provider).
+
+
+### Pull Request Annotations
+
+Enable automated pull/merge request annotations on your repositories without having to build a CI pipeline or run scheduled checks. The Bridgecrew platform will automatically scan new pull requests and annotate them with comments for any policy violations discovered. Read more in [Bridgecrew's documentation](https://docs.bridgecrew.io/docs/step-3-integrate-with-code-repository).
+
+
+
+
+
+### Repository Badges
+
+Dynamic git repository badges to show compliance targets or currently failing policies. Read more in [Bridgecrew's documentation](https://docs.bridgecrew.io/docs/code-repository-badges).
+
+
+
+
+### Compliance Reports
+
+Automate the creation of rich, detailed PDF reports for numerous compliance benchmarks, such as SOC2, HIPAA and PCI-DSS using the data within the Bridgecrew platform from your repositories and runtime environments.
+
+
diff --git a/docs/1.Welcome/pull-request-annotations.png b/docs/1.Welcome/pull-request-annotations.png
new file mode 100644
index 0000000000..e14c3cb81d
Binary files /dev/null and b/docs/1.Welcome/pull-request-annotations.png differ
diff --git a/docs/1.Welcome/pull-request.png b/docs/1.Welcome/pull-request.png
new file mode 100644
index 0000000000..4cd71f6fdd
Binary files /dev/null and b/docs/1.Welcome/pull-request.png differ
diff --git a/docs/1.Welcome/readme-badges.png b/docs/1.Welcome/readme-badges.png
new file mode 100644
index 0000000000..45e1277ba8
Binary files /dev/null and b/docs/1.Welcome/readme-badges.png differ
diff --git a/docs/1.Welcome/sample-pci-report.png b/docs/1.Welcome/sample-pci-report.png
new file mode 100644
index 0000000000..18b320f3f2
Binary files /dev/null and b/docs/1.Welcome/sample-pci-report.png differ
diff --git a/docs/1.Welcome/visualizing-scan-results.gif b/docs/1.Welcome/visualizing-scan-results.gif
new file mode 100644
index 0000000000..e214c319d3
Binary files /dev/null and b/docs/1.Welcome/visualizing-scan-results.gif differ
diff --git a/docs/2.Basics/CLI Command Reference.md b/docs/2.Basics/CLI Command Reference.md
new file mode 100644
index 0000000000..3cfcda2d8d
--- /dev/null
+++ b/docs/2.Basics/CLI Command Reference.md
@@ -0,0 +1,28 @@
+---
+layout: default
+published: true
+title: CLI Command Reference
+nav_order: 2
+---
+
+# CLI Command Reference
+
+| Parameter | Description |
+| --- | --- |
+| `-h`, `--help` | Show this help message and exit. |
+| `-v`, `--version` | Version. |
+| `-d DIRECTORY`, `--directory DIRECTORY` | IaC root directory. Cannot be used together with --file. |
+| `-f FILE`, `--file FILE` | IaC file. Cannot be used together with `--directory`. |
+| `--external-checks-dir EXTERNAL_CHECKS_DIR` | Directory for custom checks to be loaded. Can be repeated. |
+| `-l`, `--list` | List checks. |
+| `-o [{cli,json,junitxml,github_failed_only}]`, `--output [{cli,json,junitxml,github_failed_only}]` | Report output format. |
+| `--quiet` | Display only failed checks in CLI output. | [View Scan Results](doc:scan-use-cases#section-view-scan-results) |
+| `--compact` | Do not display code blocks in CLI output. |
+| `--framework {cloudformation,terraform,kubernetes,all}` | Filter scan to run only on a specific infrastructure code frameworks. Possible arguments are `cloudformation`, `terraform`, `kubernetes`, `all` |
+| `-c CHECK`, `--check CHECK` | Filter scan to run only on a specific check identifier (allowlist). You can specify multiple checks separated by comma delimiter. |
+| `--skip-check SKIP_CHECK` | Filter scan to run on all checks except for a specific check identifier (denylist). You can specify multiple checks separated by comma delimiter. | [Suppress or Skip](doc:scan-use-cases#section-suppress-or-skip) |
+| `-s`, `--soft-fail` | Runs checks but suppresses error code. |
+| `--bc-api-key BC_API_KEY` | Bridgecrew API key. |
+| `--repo-id REPO_ID` | The identity string of the repository. It should be in the form: `/` |
+| `-b BRANCH`, `--branch BRANCH` | The selected branch of the persisted repository. Only has effect when using the `--bc-api-key` flag. |
+| `-ca CA_CERTIFICATE`, `--ca-certificate CA_CERTIFICATE` | Custom CA (bundle) file. |
diff --git a/docs/2.Concepts/Evaluations.md b/docs/2.Basics/Handling Variables.md
similarity index 56%
rename from docs/2.Concepts/Evaluations.md
rename to docs/2.Basics/Handling Variables.md
index 219e83cf9f..f2142be4fd 100644
--- a/docs/2.Concepts/Evaluations.md
+++ b/docs/2.Basics/Handling Variables.md
@@ -1,26 +1,21 @@
---
layout: default
published: true
-title: Variable Evaluation
-order: 7
+title: Handling Variables
+nav_order: 7
---
-# Evaluations
-Checkov supports the evaluation of variables, found in Terraform expressions.
-Variables are declared in `.tf` files, where each variable has an identifying name,
-a description, and an optional default value.
+# Handling Variables
-Checkov collects the default values of variables and assigns them
-to their corresponding references in Terraform expressions.
-
-The advantage of variable evaluation is to cover optional scenarios, in which a forbidden value of a
-variable, is set inside a Terraform resource configuration. In that scenario, the resource
-would maybe not meet with certain security compliance.
+Checkov supports the evaluation of variables found in Terraform expressions.
+Variables are declared in `.tf` files where each variable has an identifying name, description, and optional default value.
+Checkov collects the default values of variables and assigns them to their corresponding references in Terraform expressions.
+The advantage of variable evaluation is to cover optional scenarios in which a forbidden value of a variable is set inside a Terraform resource configuration. In that scenario, the resource may not comply to security standards.
## Example
-Recall the `CKV_AWS_20` check, which validates if an S3 Bucket has an ACL defined which allows
-public access:
+This example uses the `CKV_AWS_20` check which validates if an S3 Bucket has an ACL defined which allows public access:
+
```python
class S3PublicACL(BaseResourceCheck):
def __init__(self):
@@ -42,11 +37,11 @@ class S3PublicACL(BaseResourceCheck):
if acl_block in [["public-read"],["public-read-write"],["website"]]:
return CheckResult.FAILED
return CheckResult.PASSED
-
```
-Suppose the following Terraform configuration:
-```terraform
+If we have the Terraform configuration and variable files below, Checkov evaluates the `var.acl` variable to `public-acl`, which results in the check failing:
+
+```python
# ./main.tf
resource "aws_s3_bucket" "my_bucket" {
region = var.region
@@ -54,11 +49,9 @@ resource "aws_s3_bucket" "my_bucket" {
acl = var.acl
force_destroy = true
}
-
```
-and the following variable file:
-```terraform
+```python
# ./variables.tf
variable "bucket_name" {
@@ -75,9 +68,8 @@ variable "region" {
### CLI output
```
-Checkov would evaluate `var.acl` variable to `public-acl`, resulting the check to fail:
-```bash
+```python
> checkov -d .
...
Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public access."
@@ -93,9 +85,10 @@ Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public access."
Variable acl (of /variables.tf) evaluated to value "public-acl" in expression: acl = ${var.acl}
Variable region (of /variables.tf) evaluated to value "us-west-2" in expression: region = ${var.region}
```
-To make the check pass, the value of `var.acl` needs to be set to `private` as follows:
-```terraform
+To pass the check, the value of `var.acl` needs to be set to `private` as follows:
+
+```python
# ./variables.tf
...
variable "acl" {
@@ -103,20 +96,20 @@ variable "acl" {
}
```
-The check result would then pass:
-```bash
+
+The check result now passes:
+
+```python
Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public access."
PASSED for resource: aws_s3_bucket.template_bucket
File: /main.tf:24-29
- Variable acl (of /variables.tf) evaluated to value "public-acl" in expression: acl = ${var.acl}
+ Variable acl (of /variables.tf) evaluated to value "private" in expression: acl = ${var.acl}
Variable region (of /variables.tf) evaluated to value "us-west-2" in expression: region = ${var.region}
```
### JSON Output
-If available, each `PASSED/FAILED` check contains the evaluation information, which contains all the variables
- who were evaluated.
-
+If available, each `PASSED/FAILED` check contains the evaluation information, which contains all the variables that were evaluated.
Each variable contains its variable source file path, the evaluated value, and the expressions in
which it was referenced:
@@ -141,37 +134,3 @@ evaluations: {
...
}
```
-
-# Further Terraform Concepts
-
-## Scanning third party Terraform modules
-
-Third party Terraform modules often reduce complexity for deploying services made up of many objects.
-
-For example, the third party EKS module by howdio reduces the terraform required to the nine lines below, however, in doing so abstracts the terraform configuration away from a regular Checkov scan on the current directory.
-
-```
-module "eks" {
- source = "howdio/eks/aws"
-
- name = "examplecluster"
- default_vpc = true
-
- enable_kubectl = true
- enable_dashboard = true
-}
-```
-
-To ensure coverage of objects within these modules, you can instruct checkov to scan the `.terraform` directory, after a `terraform init`, which will have retrieved the third party modules and any associated `.tf` files.
-
-```sh
-terraform init
-checkov -d . # Your TF files.
-checkov -d .terraform # Module TF files.
-```
-
-
-
-
-
-It is worth noting however, when scanning the `.terraform` directory, Checkov cannot differentiate between third party and internally written modules, however, you will gain scanning coverage for all of them.
diff --git a/docs/2.Basics/Installing Checkov.md b/docs/2.Basics/Installing Checkov.md
new file mode 100644
index 0000000000..1aa3708502
--- /dev/null
+++ b/docs/2.Basics/Installing Checkov.md
@@ -0,0 +1,109 @@
+---
+layout: default
+published: true
+title: Installing Checkov
+nav_order: 1
+---
+Installing Checkov is quick and straightforward—just install, configure input, and scan.
+
+### Install From PyPI Using Pip
+
+```shell
+pip install checkov
+```
+
+### Install on Python
+
+```shell
+pip3 install checkov
+```
+
+### Install on Alpine
+
+```shell
+pip3 install --upgrade pip && pip3 install --upgrade setuptools
+pip3 install checkov
+```
+
+### Install on Ubuntu 18.04 LTS
+
+Ubuntu 18.04 ships with Python 3.6. Before you can install Checkov, you need to install python 3.7 (from the PPA repository):
+
+```shell
+sudo apt update
+sudo apt install software-properties-common
+sudo add-apt-repository ppa:deadsnakes/ppa
+sudo apt install python3.7
+sudo apt install python3-pip
+sudo python3.7 -m pip install -U checkov #to install or upgrade checkov)
+```
+
+or using homebrew (MacOS only)
+
+```shell
+brew install checkov
+or
+
+brew upgrade checkov
+```
+
+## Upgrading Checkov
+
+If you installed Checkov with pip3, use the following command to upgrade:
+
+```shell
+pip3 install -U checkov
+```
+
+## Configure an input folder or file
+
+### Configure a folder
+
+```shell
+checkov --directory /user/path/to/iac/code
+```
+
+### Configure a specific file
+
+```shell
+checkov --file /user/tf/example.tf
+```
+
+### Configure Multiple Specific Files
+
+```shell
+checkov -f /user/cloudformation/example1.yml -f /user/cloudformation/example2.yml
+```
+
+### Configure a Terraform Plan file in JSON
+
+```json
+terraform init
+terraform plan -out tf.plan
+terraform show -json tf.plan > tf.json
+checkov -f tf.json
+```
+
+Note: The Terraform show output file `tf.json` will be a single line. For that reason Checkov will report all findings as line number 0.
+
+```json
+check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
+ FAILED for resource: aws_s3_bucket.customer
+ File: /tf/tf.json:0-0
+ Guide: https://docs.bridgecrew.io/docs/s3_16-enable-versioning
+```
+
+If you have installed jq, you can convert a JSON file into multiple lines with the command `terraform show -json tf.plan | jq '.' > tf.json`, making it easier to read the scan result.
+
+```json
+checkov -f tf.json
+Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
+ FAILED for resource: aws_s3_bucket.customer
+ File: /tf/tf1.json:224-268
+ Guide: https://docs.bridgecrew.io/docs/s3_16-enable-versioning
+
+ 225 | "values": {
+ 226 | "acceleration_status": "",
+ 227 | "acl": "private",
+ 228 | "arn": "arn:aws:s3:::mybucket",
+```
diff --git a/docs/2.Basics/Reviewing Scan Results.md b/docs/2.Basics/Reviewing Scan Results.md
new file mode 100644
index 0000000000..8e1201d994
--- /dev/null
+++ b/docs/2.Basics/Reviewing Scan Results.md
@@ -0,0 +1,211 @@
+---
+layout: default
+published: true
+title: Reviewing Scan Results
+nav_order: 5
+---
+
+# Reviewing Scan Results
+
+The results of Checkov scans can be viewed in CLI, JSON, or JUnit
+
+## Scan Result Sample (CLI)
+
+Consider the following Terraform configuration of an S3 bucket:
+
+```python
+resource "aws_s3_bucket" "foo-bucket" {
+ region = var.region
+ bucket = local.bucket_name
+ force_destroy = true
+
+ tags = {
+ Name = "foo-${data.aws_caller_identity.current.account_id}"
+ }
+ versioning {
+ enabled = true
+ }
+ logging {
+ target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ target_prefix = "log/"
+ }
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+ acl = "private"
+}
+```
+
+The appropriate output report is:
+
+```python
+Passed checks: 4, Failed checks: 0, Skipped checks: 0
+
+Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure the S3 bucket has access logging enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure all data stored in the S3 bucket have versioning enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "S3 Bucket has an ACL defined which allows public access."
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+```
+
+The bucket's current configuration seems to comply with the available ``aws_s3_bucket`` resource type checks.
+
+However, if the bucket is going to be used for static content hosting, it requires additional configuration to allow public access:
+
+```python
+resource "aws_s3_bucket" "foo-bucket" {
+ region = var.region
+ bucket = local.bucket_name
+ force_destroy = true
+
+ tags = {
+ Name = "foo-${data.aws_caller_identity.current.account_id}"
+ }
+ versioning {
+ enabled = true
+ }
+ logging {
+ target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ target_prefix = "log/"
+ }
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+ acl = "public-read"
+}
+data "aws_caller_identity" "current" {}
+```
+
+After configuring the bucket to allow public access, the output report contains the failed check:
+
+```python
+Passed checks: 3, Failed checks: 1, Skipped checks: 0
+
+Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure the S3 bucket has access logging enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure all data stored in the S3 bucket have versioning enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "S3 Bucket has an ACL defined which allows public access."
+ FAILED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+ 1 | resource "aws_s3_bucket" "foo-bucket" {
+ 2 | region = var.region
+ 3 | bucket = local.bucket_name
+ 4 | force_destroy = true
+ 5 |
+ 6 | tags = {
+ 7 | Name = "foo-${data.aws_caller_identity.current.account_id}"
+ 8 | }
+ 9 | versioning {
+ 10 | enabled = true
+ 11 | }
+ 12 | logging {
+ 13 | target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ 14 | target_prefix = "log/"
+ 15 | }
+ 16 | server_side_encryption_configuration {
+ 17 | rule {
+ 18 | apply_server_side_encryption_by_default {
+ 19 | kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ 20 | sse_algorithm = "aws:kms"
+ 21 | }
+ 22 | }
+ 23 | }
+ 24 | acl = "public-read"
+ 25 | }
+```
+
+The corresponding check now fails, and the report includes the appropriate failing configuration source code.
+
+In order to skip the failed check, we annotate the bucket with a suppression comment (which needs to appear inside the resource scope):
+
+```python
+resource "aws_s3_bucket" "foo-bucket" {
+ # checkov:skip=CKV_AWS_20:The bucket is a public static content host
+ region = var.region
+ bucket = local.bucket_name
+ force_destroy = true
+ tags = {
+ Name = "foo-${data.aws_caller_identity.current.account_id}"
+ }
+ versioning {
+ enabled = true
+ }
+ logging {
+ target_bucket = "${aws_s3_bucket.log_bucket.id}"
+ target_prefix = "log/"
+ }
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = "${aws_kms_key.mykey.arn}"
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+ acl = "public-read"
+}
+```
+
+Checkov then skips the ``CKV_AWS_20`` check, and the output report is:
+
+```python
+Passed checks: 3, Failed checks: 0, Skipped checks: 1
+
+Check: "Ensure all data stored in the S3 bucket is securely encrypted at rest"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure the S3 bucket has access logging enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "Ensure all data stored in the S3 bucket have versioning enabled"
+ PASSED for resource: aws_s3_bucket.foo-bucket
+ File: /example.tf:1-25
+
+
+Check: "S3 Bucket has an ACL defined which allows public access."
+ SKIPPED for resource: aws_s3_bucket.foo-bucket
+ Suppress comment: The bucket is a public static content host
+ File: /example.tf:1-25
+```
+## Visualize Checkov output
+Read more about [sending your Checkov scan results to the Bridgecrew platform](https://www.checkov.io/2.Basics/Visualizing%20Checkov%20Output.html).
diff --git a/docs/3.Scans/Credentials Scans.md b/docs/2.Basics/Scanning Credentials and Secrets.md
similarity index 62%
rename from docs/3.Scans/Credentials Scans.md
rename to docs/2.Basics/Scanning Credentials and Secrets.md
index 7c65044a9e..be2ee6a0c5 100644
--- a/docs/3.Scans/Credentials Scans.md
+++ b/docs/2.Basics/Scanning Credentials and Secrets.md
@@ -1,21 +1,18 @@
---
layout: default
published: true
-title: Credentials scans
-order: 7
+title: Scanning Credentials and Secrets
+nav_order: 4
---
-# Credentials scans
+# Scanning Credentials and Secrets
-Cloud account secrets are a priceless target for an attacker to utilize cloud resources, leak data or harm the application infrastructure.
+Checkov can scan for a number of different common credentials such as AWS access keys, Azure service credentials, or private keys that are hard-coded in a Terraform code block.
+See list of regular expressions [here](https://github.com/bridgecrewio/checkov/blob/master/checkov/common/util/secrets.py).
-Checkov can scan for a number of different common credentials, such as AWS access keys, Azure, service credentials, or private keys that are hard coded in a terraform code block.
+Let’s assume we have the following Terraform provider block:
-The list of regular expressions is available [here](https://github.com/bridgecrewio/checkov/blob/master/checkov/common/util/secrets.py), and we welcome any contributions to this list.
-
-## Example
-Let's assume we have the following terraform provider block:
-```hcl-terraform
+```yaml
# Snippet from main.tf
provider "aws" {
region = "us-west-2"
@@ -23,20 +20,19 @@ provider "aws" {
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
```
-As mentioned in terraform official docs [here](https://www.terraform.io/docs/providers/aws/index.html#static-credentials):
-"Hard-coding credentials into any Terraform configuration is not recommended, and risks secret leakage should this file ever be committed to a public version control system."
-Running checkov to detect secrets:
+As stated in Terraform's documentation: “Hard-coding credentials into any Terraform configuration is not recommended, and risks secret leakage should this file ever be committed to a public version control system.”
+
+Run Checkov to detect secrets:
-```bash
+```shell
checkov -f main.tf
```
-Will result in the following output:
+This is the output of the scan
-```bash
-
- _ _
+```text
+ _ _
___| |__ ___ ___| | _______ __
/ __| '_ \ / _ \/ __| |/ / _ \ \ / /
| (__| | | | __/ (__| < (_) \ V /
@@ -57,13 +53,10 @@ Check: CKV_AWS_41: "Ensure no hard coded AWS access key and secret key exists"
3 | access_key = "AKIAIOSFODNN7EXAMPLE"
4 | secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
5 | }
-
```
+Checkov can also detect secrets defined in lambda variables as shown in the example below.
-checkov can also detect secrets defined in lambda variables like the following example:
-
-```hcl-terraform
-
+```yaml
resource "aws_lambda_function" "test_lambda" {
filename = "resources/lambda_function_payload.zip"
function_name = "${local.resource_prefix.value}-analysis"
@@ -83,10 +76,9 @@ resource "aws_lambda_function" "test_lambda" {
}
```
-or in EC2 user data:
-
-```hcl-terraform
+or in EC2 user data as shown in the example below:
+```yaml
resource "aws_instance" "compute_host" {
# ec2 have plain text secrets in user data
ami = "ami-04169656fea786776"
diff --git a/docs/2.Concepts/Suppressions.md b/docs/2.Basics/Suppressing and Skipping Policies.md
similarity index 55%
rename from docs/2.Concepts/Suppressions.md
rename to docs/2.Basics/Suppressing and Skipping Policies.md
index 3d6b23084b..d21bf6da73 100644
--- a/docs/2.Concepts/Suppressions.md
+++ b/docs/2.Basics/Suppressing and Skipping Policies.md
@@ -1,29 +1,28 @@
---
layout: default
published: true
-title: Suppressions
-order: 5
+title: Suppressing and Skipping Policies
+nav_order: 3
---
-# Suppressions
+# Suppressing/skipping
-Like any static-analysis tool it is limited by its analysis scope.
-For example, if a resource is managed manually, or using subsequent configuration management tooling,
-a suppression can be inserted as a simple code annotation.
+Like any static-analysis tool, suppression is limited by its analysis scope.
+For example, if a resource is managed manually, or using configuration management tools, a suppression can be inserted as a simple code annotation.
-## Suppression comment format
-
-To skip a check on a given Terraform definition block or CloudFormation resource, apply the following comment pattern inside it's scope:
+## Suppression Comment Format
+To skip a check on a given Terraform definition block or CloudFormation resource, apply the following comment pattern inside its scope:
`checkov:skip=:`
-* `` is one of the [available check scanners](../3.Scans/resource-scans.md)
-* `` is an optional suppression reason to be included in the output
+* `` is one of the available check scanners.
+* `` is an optional suppression reason to be included in the output.
### Example
-The following comment skip the `CKV_AWS_20` check on the resource identified by `foo-bucket`, where the scan checks if an AWS S3 bucket is private.
-In the example, the bucket is configured with a public read access; Adding the suppress comment would skip the appropriate check instead of the check to fail.
-```hcl-terraform
+The following comment skips the `CKV_AWS_20` check on the resource identified by `foo-bucket`, where the scan checks if an AWS S3 bucket is private.
+In the example, the bucket is configured with a public read access; Adding the suppress comment skips the appropriate check instead of the check failing.
+
+```python
resource "aws_s3_bucket" "foo-bucket" {
region = var.region
#checkov:skip=CKV_AWS_20:The bucket is a public static content host
@@ -33,9 +32,9 @@ resource "aws_s3_bucket" "foo-bucket" {
}
```
-The output would now contain a ``SKIPPED`` check result entry:
+The output now contains a ``SKIPPED`` check result entry:
-```bash
+```python
...
...
Check: "S3 Bucket has an ACL defined which allows public access."
@@ -50,7 +49,7 @@ Check: "S3 Bucket has an ACL defined which allows public access."
To suppress checks in Kubernetes manifests, annotations are used with the following format:
`checkov.io/skip#: =`
-```bash
+```yaml
apiVersion: v1
kind: Pod
metadata:
@@ -63,6 +62,3 @@ spec:
containers:
...
```
-
-# Global skip
-If you'd like to allowlist or denylist a check_id from being executed, use the `--check`(allowlist) or `--skip-check` flags
diff --git a/docs/2.Basics/Visualizing Checkov Output.md b/docs/2.Basics/Visualizing Checkov Output.md
new file mode 100644
index 0000000000..3db2913210
--- /dev/null
+++ b/docs/2.Basics/Visualizing Checkov Output.md
@@ -0,0 +1,77 @@
+---
+layout: default
+published: true
+title: Visualizing Checkov Output
+nav_order: 6
+---
+
+# Visualizing Checkov Output in Bridgecrew
+
+You can integrate Checkov with Bridgecrew to view the results of Checkov scans in the Bridgecrew platform.
+
+
+## Integration
+
+### Get your API Token
+
+To get a Bridgecrew issued API token:
+
+1. Sign up for a free [Bridgecrew account](https://www.bridgecrew.cloud/).
+2. Navigate to the [Integrations page](https://www.bridgecrew.cloud/integrations) and select **API Token**.
+
+
+3. Copy the API token.
+
+## Execution
+
+After acquiring the API token, run Checkov as follows:
+
+```shell
+checkov -d --bc-api-key --repo-id --branch
+```
+
+Or by using the `-f` file flag:
+
+```shell
+checkov -f ... --bc-api-key --repo-id --branch
+```
+
+The table below details the arguments used when executing the API token:
+
+| Argument | Description |
+| -------- | ----------- |
+| `` | Bridgecrew issued API key |
+| `` | Identifying string of the scanned repository, following the standard Git repository naming scheme: `/` |
+| `` | Branch name to be persisted on platform. Defaults to the master branch. **NOTE:** Ensure the scanned directory (supplied in the `-d` flag) is currently checked out from the given branch name. |
+
+### Environment Variables
+
+To enrich Bridgecrew's context with CI/CD systems data, we strongly recommend that Checkov uses environment variables.
+
+| Environment Variable | Description | Example |
+| -------- | ----------- | ----------- |
+| BC_FROM_BRANCH | Source branch | feature/foo |
+| BC_TO_BRANCH | Target branch | main |
+| BC_PR_ID | Pull request identifier | 825 |
+| BC_PR_URL | Link to pull request/merge request | https://github.com/bridgecrewio/checkov/pull/825 |
+| BC_COMMIT_HASH | Commit identifier | 5df50ab857e7a255e4e731877748b539915ad489 |
+| BC_COMMIT_URL | Link to commit in CI/VCS system | https://github.com/bridgecrewio/checkov/commit/5df50ab857e7a255e4e731877748b539915ad489 |
+| BC_AUTHOR_NAME | User associated with the CI trigger | schosterbarak |
+| BC_AUTHOR_URL | Link to the user profile page | https://github.com/schosterbarak |
+| BC_RUN_ID | CI run identifier | 525220526 |
+| BC_RUN_URL | Link to the run in the CI system | https://github.com/bridgecrewio/checkov/actions/runs/525220526 |
+| BC_REPOSITORY_URL | Link to the GitHub repository | https://github.com/bridgecrewio/checkov/ |
+| BC_SOURCE | Name of CI system being integrated | githubActions |
+
+## Bridgecrew platform view
+
+After successfully executing, scan results are persisted in [Bridgecrew](https://www.bridgecrew.cloud), and can be seen in the [Incidents screen](https://www.bridgecrew.cloud/incidents).
+
+
+## Example Usage
+
+The following command scans the repository identified as `foo/bar`, on branch `develop`, using a Bridgecrew API key:
+
+```shell
+checkov -d . --bc-api-key 84b8f259-a3dv-5c1e-9422-1bdc9aec0487 --repo-id foo/bar --branch develop
+```
diff --git a/docs/2.Basics/api-token.gif b/docs/2.Basics/api-token.gif
new file mode 100644
index 0000000000..e214c319d3
Binary files /dev/null and b/docs/2.Basics/api-token.gif differ
diff --git a/docs/2.Basics/bridgecrew-dashboard.png b/docs/2.Basics/bridgecrew-dashboard.png
new file mode 100644
index 0000000000..26695ef732
Binary files /dev/null and b/docs/2.Basics/bridgecrew-dashboard.png differ
diff --git a/docs/4.Integrations/bc-violations.png b/docs/2.Basics/bridgecrew-incidents.png
similarity index 100%
rename from docs/4.Integrations/bc-violations.png
rename to docs/2.Basics/bridgecrew-incidents.png
diff --git a/docs/2.Basics/terraform-module-scanning.png b/docs/2.Basics/terraform-module-scanning.png
new file mode 100644
index 0000000000..fbf7ef3043
Binary files /dev/null and b/docs/2.Basics/terraform-module-scanning.png differ
diff --git a/docs/2.Concepts/checkov_terraform_plan.png b/docs/2.Basics/terraform-plan-output.png
similarity index 100%
rename from docs/2.Concepts/checkov_terraform_plan.png
rename to docs/2.Basics/terraform-plan-output.png
diff --git a/docs/2.Basics/visualizing-scan-output.gif b/docs/2.Basics/visualizing-scan-output.gif
new file mode 100644
index 0000000000..e214c319d3
Binary files /dev/null and b/docs/2.Basics/visualizing-scan-output.gif differ
diff --git a/docs/2.Concepts/Custom Policies.md b/docs/2.Concepts/Custom Policies.md
deleted file mode 100644
index 7adb7752ee..0000000000
--- a/docs/2.Concepts/Custom Policies.md
+++ /dev/null
@@ -1,127 +0,0 @@
----
-layout: default
-published: true
-title: Custom Policies
-order: 6
----
-
-# Custom Policies
-
-Checkov is delivered with a [set of built-in policies](../3.Scans/resource-scans.md) that checks for compliance and security best practices at its core.
- In addition, Checkov enables loading of extra checks, that give the user a possibility to author and execute custom policies.
-
-# Example
-Let's assume we have the following directory structure:
-```text
-├── main.tf
-├── variables.tf
-└── outputs.tf
-```
-And that we have a unique need to enforce bucket ACL policies only when the tag `Scope=PCI` is present.
-In other words, as security-aware engineers, we want the following bucket definition will trigger a failed check result:
-
-```hcl-terraform
-# Snippet from main.tf
-resource "aws_s3_bucket" "credit_cards_bucket" {
- region = var.region
- bucket = local.bucket_name
- acl = "public-read"
- force_destroy = true
-
- tags = {
- Scope = "PCI",
-
- }
-}
-```
-For that we will need to add a new check to ensure PCI related S3 buckets will stay private.
-So we will create a new python folder named `my_extra_checks` containing our new check
-
-```text
-├── main.tf
-├── variables.tf
-└── outputs.tf
-└── my_extra_checks
- └── __init__.py
- └── S3PCIPrivateACL.py
-
-```
-
-First time setup of your custom checks folder requires a `__init__.py` file
-```python
-from os.path import dirname, basename, isfile, join
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
-__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
-```
-
-And we will fill the matching logic in `S3PCIPrivateACL.py`:
-```python
-from lark import Token
-
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.models.enums import CheckResult, CheckCategories
-
-
-class S3PCIPrivateACL(BaseResourceCheck):
- def __init__(self):
- name = "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)"
- id = "CKV_AWS_999"
- supported_resources = ['aws_s3_bucket']
- # CheckCategories are defined in models/enums.py
- categories = [CheckCategories.BACKUP_AND_RECOVERY]
- super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-
- def scan_resource_conf(self, conf):
- """
- Looks for ACL configuration at aws_s3_bucket and Tag values:
- https://www.terraform.io/docs/providers/aws/r/s3_bucket.html
- :param conf: aws_s3_bucket configuration
- :return:
- """
- if 'tags' in conf.keys():
- environment_tag = Token("IDENTIFIER", "Scope")
- if environment_tag in conf['tags'][0].keys():
- if conf['tags'][0][environment_tag] == "PCI":
- if 'acl' in conf.keys():
- acl_block = conf['acl']
- if acl_block in [["public-read"], ["public-read-write"], ["website"]]:
- return CheckResult.FAILED
- return CheckResult.PASSED
-
-
-scanner = S3PCIPrivateACL()
-
-```
-Now that we have the new custom check in place, we can run Checkov and verify the results:
-
-```bash
-# install from pypi using pip
-pip install checkov
-
-
-# select an input folder that contains your terraform files and enable loading of extra checks
-checkov -d . --external-checks-dir my_extra_checks
-```
-
-Results:
-
-```bash
-Check: "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)"
- FAILED for resource: aws_s3_bucket.credit_cards_bucket
- File: /main.tf:80-90
-
- 80 | resource "aws_s3_bucket" "credit_cards_bucket" {
- 81 | region = var.region
- 82 | bucket = local.bucket_name
- 83 | acl = "public-read"
- 84 | force_destroy = true
- 85 |
- 86 | tags = {
- 87 | Scope = "PCI",
- 88 |
- 89 | }
- 90 | }
-```
-
-Attention: Policies can not share the same file name (whichever is loaded first) will be loaded.
diff --git a/docs/2.Concepts/Evaluate Terraform Plan.md b/docs/2.Concepts/Evaluate Terraform Plan.md
deleted file mode 100644
index f4b2ee5f92..0000000000
--- a/docs/2.Concepts/Evaluate Terraform Plan.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-layout: default
-published: true
-title: Evaluate Checkov policies on Terraform plan
-order: 8
----
-# Evaluate Checkov policies on Terraform plan
-
-Checkov supports the evaluation of policies on resources declared in `.tf` files. It can also be used to evaluate `terraform plan` expressed in a json file.
-Plan evaluation provides Checkov additional dependencies and context that can result in a more complete scan result.
-Since Terraform plan files may contain arguments (like secrets) that are injected dynamically, it is advised to run a plan evaluation using Checkov in a secure CI/CD pipeline setting.
-
-## Example
-
-```bash
-terraform init
-terraform plan --out tfplan.binary
-terraform show -json tfplan.binary > tfplan.json
-
-checkov -f tfplan.json
-
-```
-### Example output:
-
-
diff --git a/docs/2.Concepts/Sharing Policies.md b/docs/2.Concepts/Sharing Policies.md
deleted file mode 100644
index ecc6f187a0..0000000000
--- a/docs/2.Concepts/Sharing Policies.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-layout: default
-published: true
-title: Sharing Policies
-order: 7
----
-
-# Sharing Policies
-
-[Custom policies](Custom%20Policies.md) can be reused across multiple projects.
-
-You can download git repository containing custom checks:
-
-```bash
-checkov --external-checks-git https://github.com/bridgecrewio/checkov.git
-```
-
-# Sub directories
-```bash
-checkov --external-checks-git https://github.com/bridgecrewio/checkov.git//tests/terraform/checks/resource/registry/example_external_dir/extra_checks
-```
-If you want to download only a specific subdirectory from a git repository, you can specify a subdirectory after a double-slash //. (inspired by [go-getter](https://github.com/hashicorp/go-getter))
-checkov will first download the URL specified before the double-slash (as if you didn't specify a double-slash), but will then copy the path after the double slash into temporal directory.
-
-For example, if you're downloading this GitHub repository, but you only want to download the "extra_checks" directory, you can do the following:
-
-`https://github.com/bridgecrewio/checkov.git//extra_checks`
\ No newline at end of file
diff --git a/docs/3.Custom Policies/Custom Policies Overview.md b/docs/3.Custom Policies/Custom Policies Overview.md
new file mode 100644
index 0000000000..d5523f884e
--- /dev/null
+++ b/docs/3.Custom Policies/Custom Policies Overview.md
@@ -0,0 +1,15 @@
+---
+layout: default
+published: true
+title: Custom Policies Overview
+nav_order: 1
+---
+
+# Custom Policies Overview
+
+Custom Policies allow monitoring and enforcing of cloud infrastructure configuration in accordance with your organization's specific needs. For example, for certain resource types, you may want to enforce a tagging methodology or a special secure password policy; or you may want to restrict usage of a new service depending on the types of other services it is connected to.
+
+* You can create custom policies in [Python](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html) that check for the status of configuration attributes.
+* You can create custom policies in [YAML](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html) that can both check for the status of configuration attributes and check the connection state between types of resources.
+* You can also apply sophisticated logic to multiple conditions within a Custom Policy. Check out our [custom policy examples](https://www.checkov.io/3.Custom%20Policies/Examples.html).
+* After creating tests for your custom policies, you can contribute them back to Checkov! Learn how to [contibute your policies](https://www.checkov.io/6.Contribution/Contribution%20Overview.html).
diff --git a/docs/3.Custom Policies/Examples.md b/docs/3.Custom Policies/Examples.md
new file mode 100644
index 0000000000..845d0551dd
--- /dev/null
+++ b/docs/3.Custom Policies/Examples.md
@@ -0,0 +1,335 @@
+---
+layout: default
+published: true
+title: Custom YAML Policies Examples
+nav_order: 4
+---
+
+# Examples - YAML-Based Custom Policies
+
+## Basic Query - One Attribute Block
+
+```yaml
+---
+metadata:
+ name: "Check that all resources are tagged with the key - env"
+ id: "CKV2_AWS_1"
+ category: "GENERAL_SECURITY"
+definition:
+ cond_type: "attribute"
+ resource_types: "all"
+ attribute: "tags.env"
+ operator: "exists"
+```
+
+## OR at Top Level - Two Attribute Blocks
+
+```yaml
+---
+metadata:
+ name: "Org's compute instances should not be t3.micro or t3.nano"
+ id: "CKV2_AWS_1"
+ category: "NETWORKING"
+definition:
+ or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_instance"
+ attribute: "instance_type"
+ operator: "not_equals"
+ value: "t3.micro"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_instance"
+ attribute: "instance_type"
+ operator: "not_equals"
+ value: "t3.nano"
+```
+
+## OR Logic - Attribute Block
+
+```yaml
+---
+metadata:
+ name: "Check that all encrypted RDS clusters are tagged with encrypted: true"
+ id: "CKV2_AWS_1"
+ category: "SECRETS"
+definition:
+ and:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ attribute: "tags.encrypted"
+ operator: "equals"
+ value: "true"
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ attribute: "kms_key_id"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ attribute: "storage_encrypted"
+ operator: "equals"
+ value: "true"
+```
+
+## OR - Multiple Attribute Blocks
+
+```yaml
+---
+metadata:
+ name: "Ensure all AWS databases have Backup Policy"
+ id: "CKV2_AWS_1"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ - "aws_db_instance"
+ attribute: "backup_retention_period"
+ operator: "not_exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ - "aws_db_instance"
+ attribute: "backup_retention_period"
+ operator: "not_equals"
+ value: "0"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_redshift_cluster"
+ attribute: "automated_snapshot_retention_period"
+ operator: "not_equals"
+ value: "0"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_dynamodb_table"
+ attribute: "point_in_time_recovery"
+ operator: "not_equals"
+ value: "false"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_dynamodb_table"
+ attribute: "point_in_time_recovery"
+ operator: "exists"
+```
+
+## Simple Connection State Block and Filter and Attribute Blocks
+
+```yaml
+---
+metadata:
+ name: "Ensure all EC2s are connected only to encrypted EBS volumes"
+ id: "CKV2_AWS_1"
+ category: "ENCRYPTION"
+definition:
+ and:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_ebs_volume"
+ attribute: "encrypted"
+ operator: "equals"
+ value: "true"
+ - cond_type: "connection"
+ resource_types:
+ - "aws_volume_attachment"
+ connected_resource_types:
+ - "aws_ebs_volume"
+ operator: "exists"
+ - cond_type: "filter"
+ attribute: "resource_type"
+ value:
+ - "aws_ebs_volume"
+ operator: "within"
+```
+
+## Complex Definition - Connection State Block and Filter and Attribute Blocks - Example 1
+
+```yaml
+---
+metadata:
+ name: "Ensure all ALBs are connected only to HTTPS listeners"
+ id: "CKV2_AWS_1"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: "filter"
+ value:
+ - "aws_lb"
+ attribute: "resource_type"
+ operator: "within"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb"
+ attribute: "load_balancer_type"
+ operator: "equals"
+ value: "application"
+ - or:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "not_exists"
+ - and:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "certificate_arn"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "ssl_policy"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "protocol"
+ operator: "equals"
+ value: "HTTPS"
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "default_action.redirect.protocol"
+ operator: "equals"
+ value: "HTTPS"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "default_action.redirect.protocol"
+ operator: "not_exists"
+ - or:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb_listener_rule"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "not_exists"
+ - and:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb_listener_rule"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "exists"
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener_rule"
+ attribute: "default_action.redirect.protocol"
+ operator: "equals"
+ value: "HTTPS"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener_rule"
+ attribute: "default_action.redirect.protocol"
+ operator: "not_exists"
+```
+
+## Complex Definition - Connection State Block and Filter and Attribute Blocks - Example 2
+
+```yaml
+---
+metadata:
+ name: "Ensure resources allows encrypted ingress communication (SSH)"
+ id: "CKV2_AWS_1"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: "filter"
+ attribute: "resource_type"
+ value:
+ - "aws_instance"
+ - "aws_elb"
+ - "aws_lb"
+ - "aws_db_instance"
+ - "aws_elasticache_cluster"
+ - "aws_emr_cluster"
+ - "aws_redshift_cluster"
+ - "aws_elasticsearch_domain"
+ - "aws_rds_cluster"
+ - "aws_efs_mount_target"
+ - "aws_efs_file_system"
+ - "aws_ecs_service"
+ operator: "within"
+ - cond_type: "connection"
+ resource_types:
+ - "aws_instance"
+ - "aws_elb"
+ - "aws_lb"
+ - "aws_db_instance"
+ - "aws_elasticache_cluster"
+ - "aws_emr_cluster"
+ - "aws_redshift_cluster"
+ - "aws_elasticsearch_domain"
+ - "aws_rds_cluster"
+ - "aws_efs_mount_target"
+ - "aws_efs_file_system"
+ - "aws_ecs_service"
+ connected_resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ operator: "exists"
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ attribute: "ingress.from_port"
+ operator: "equals"
+ value: "22"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ value: "22"
+ operator: "equals"
+ attribute: "ingress.to_port"
+ - or:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_security_group_rule"
+ connected_resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ operator: "not_exists"
+ - and:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_security_group_rule"
+ connected_resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group_rule"
+ attribute: "type"
+ operator: "equals"
+ value: "ingress"
+ - or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group_rule"
+ attribute: "to_port"
+ operator: "equals"
+ value: "22"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_security_group_rule"
+ attribute: "from_port"
+ operator: "equals"
+ value: "22"
+```
diff --git a/docs/3.Custom Policies/Python Custom Policies.md b/docs/3.Custom Policies/Python Custom Policies.md
new file mode 100644
index 0000000000..31d174658d
--- /dev/null
+++ b/docs/3.Custom Policies/Python Custom Policies.md
@@ -0,0 +1,202 @@
+---
+layout: default
+published: true
+title: Python Custom Policies
+nav_order: 2
+---
+
+# Create Custom Policy - Python - Attribute Check
+
+Custom Policies created in code (in Python) support checking the state of a resource’s attributes.
+A Python-based Custom Policy for Checkov consists of sections for Metadata and Policy Definition.
+
+Read also how to [create custom YAML Policies for attribute and composite scanning](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html).
+
+## Writing a Python custom Checkov policy
+
+Specify a `name`, `ID`, `relevant resources` and `categories`.
+
+| Parameter | Description | Example/Comments |
+| -------- | -------- | -------- |
+| ``name`` | A new policy's unique purpose. It should ideally specify the positive desired outcome of the policy. | |
+| ``id`` | A mandatory unique identifier of a policy. Native policies written by Bridgecrew contributors will follow the following convention:
+``CKV_providerType_serialNumber`` | `CKV_AWS_9` , `CKV_GCP_12` |
+| ``supported_resources`` | Infrastructure objects, as described in the scanned IaC's language. This usually contains one specific resource block. If you support multiple resources, you can use `*` to match any type of entity in that specific domain. | `*` use depends on which check base class you extend; see note below table. `?ws_*` will match anything where the second character is a `'w'`, the third is a `'s'` and the fourth is a `'_'`. |
+| ``categories`` | Categorization of a scan. Usually used to produce compliance reports, pipeline analytics and infrastructure health metrics, etc. | |
+
+**Note for Supported Resources Parameter:** If you extend `checkov.terraform.checks.resource.base_resource_check.BaseResourceCheck`, the check is registered for all Terraform resources.
+
+The following example produces a policy that ensures that new RDS services spun-up are encrypted at rest, given a scanned Terraform configuration ([CKV_AWS_16](https://github.com/bridgecrewio/checkov/blob/master/checkov/terraform/checks/resource/aws/RDSEncryption.py)).
+1. Create a new file in the AWS check directory ``checkov/terraform/checks/resource/aws/RDSEncryption.py``.
+2. Import the following:
+
+```python
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+```
+
+3. Define the meta entities for this check as described in the table above.
+
+```python
+class RDSEncryption(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure all data stored in the RDS is securely encrypted at rest"
+ id = "CKV_AWS_16"
+ supported_resources = ['aws_db_instance']
+ categories = [CheckCategories.ENCRYPTION]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+```
+
+4. Define a simple check of the ```aws_db_instance``` resource block to determine if ```aws_db_instance``` is disabled. If it is disabled, that needs to cause a ```CheckResult.FAILED``` to occur.
+
+```python
+def scan_resource_conf(self, conf):
+ """
+ Looks for encryption configuration at aws_db_instance:
+ https://www.terraform.io/docs/providers/aws/d/db_instance.html
+ :param conf: aws_db_instance configuration
+ :return:
+ """
+ if 'storage_encrypted' in conf.keys():
+ key = conf['storage_encrypted'][0]
+ if key:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+```
+
+5. Conclude the policy name and operationalize it with the statement:
+
+```python
+check = RDSEncryption()
+```
+
+### Run a new scan
+
+To run a scan with the new policy, use the ```checkov``` command.
+
+```python
+checkov -d /user/tf
+```
+
+
+#Working with Custom Policies
+
+Checkov is delivered with a set of built-in policies that check for compliance and security best practices at its core. In addition, Checkov enables you to load additional checks, that give the user the ability to author and execute custom policies.
+
+## Example
+This example uses the following directory structure:
+
+```text
+├── main.tf
+├── variables.tf
+└── outputs.tf
+```
+
+The example assumes a unique need to enforce bucket ACL policies only when the tag `Scope=PCI` is present. That being the case, the following bucket definition must trigger a failed check result:
+
+```python
+# Snippet from main.tf
+resource "aws_s3_bucket" "credit_cards_bucket" {
+ region = var.region
+ bucket = local.bucket_name
+ acl = "public-read"
+ force_destroy = true
+
+ tags = {
+ Scope = "PCI",
+
+ }
+}
+```
+
+To trigger the failed check result, you need to add a new check to ensure PCI related S3 buckets will stay private.
+1. Create a new python folder named `my_extra_checks` containing the new check:
+
+```text
+├── main.tf
+├── variables.tf
+└── outputs.tf
+└── my_extra_checks
+ └── __init__.py
+ └── S3PCIPrivateACL.py
+```
+
+ a. The first time you setup the custom checks folder, you need to also create a file named `__init__.py`.
+
+```python
+from os.path import dirname, basename, isfile, join
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+```
+
+ b. Complete the matching logic in `S3PCIPrivateACL.py`:
+
+```python
+from lark import Token
+
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.models.enums import CheckResult, CheckCategories
+
+
+class S3PCIPrivateACL(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)"
+ id = "CKV_AWS_999"
+ supported_resources = ['aws_s3_bucket']
+ # CheckCategories are defined in models/enums.py
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ """
+ Looks for ACL configuration at aws_s3_bucket and Tag values:
+ https://www.terraform.io/docs/providers/aws/r/s3_bucket.html
+ :param conf: aws_s3_bucket configuration
+ :return:
+ """
+ if 'tags' in conf.keys():
+ environment_tag = Token("IDENTIFIER", "Scope")
+ if environment_tag in conf['tags'][0].keys():
+ if conf['tags'][0][environment_tag] == "PCI":
+ if 'acl' in conf.keys():
+ acl_block = conf['acl']
+ if acl_block in [["public-read"], ["public-read-write"], ["website"]]:
+ return CheckResult.FAILED
+ return CheckResult.PASSED
+
+
+scanner = S3PCIPrivateACL()
+```
+
+2. With the new custom check in place, run Checkov:
+
+```python
+# install from pypi using pip
+pip install checkov
+
+
+# select an input folder that contains your terraform files and enable loading of extra checks
+checkov -d . --external-checks-dir my_extra_checks
+```
+Verify the results:
+
+```python
+Check: "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)"
+ FAILED for resource: aws_s3_bucket.credit_cards_bucket
+ File: /main.tf:80-90
+
+ 80 | resource "aws_s3_bucket" "credit_cards_bucket" {
+ 81 | region = var.region
+ 82 | bucket = local.bucket_name
+ 83 | acl = "public-read"
+ 84 | force_destroy = true
+ 85 |
+ 86 | tags = {
+ 87 | Scope = "PCI",
+ 88 |
+ 89 | }
+ 90 | }
+```
+
+**Attention:** Policies cannot share the same file name. If two policies with the same file name exist, only the first one will be loaded.
diff --git a/docs/3.Custom Policies/Sharing Custom Policies.md b/docs/3.Custom Policies/Sharing Custom Policies.md
new file mode 100644
index 0000000000..cd4bf39599
--- /dev/null
+++ b/docs/3.Custom Policies/Sharing Custom Policies.md
@@ -0,0 +1,28 @@
+---
+layout: default
+published: true
+title: Sharing Custom Policies
+nav_order: 5
+---
+
+# Sharing Custom Policies
+
+[Custom Policies](https://www.checkov.io/3.Custom%20Policies/Custom%20Policies%20Overview.html) can be reused across multiple projects.
+
+You can download a git repository containing custom checks:
+
+```python
+checkov --external-checks-git https://github.com/bridgecrewio/checkov.git
+```
+
+## Sub-directories
+
+If you want to download only a specific subdirectory from a GitHub repository, you can specify a subdirectory after a double-slash` //`. Checkov will first download the URL specified before the double-slash (as if you didn’t specify a double-slash), but will then copy the path after the double slash into a temporal directory.
+
+```text
+checkov --external-checks-git https://github.com/bridgecrewio/checkov.git//tests/terraform/checks/resource/registry/example_external_dir/extra_checks
+```
+
+For example, if you’re downloading this GitHub repository, but you only want to download the “extra_checks” directory, you can do the following:
+
+`https://github.com/bridgecrewio/checkov.git//extra_checks`
diff --git a/docs/3.Custom Policies/YAML Custom Policies.md b/docs/3.Custom Policies/YAML Custom Policies.md
new file mode 100644
index 0000000000..40d3cd13ee
--- /dev/null
+++ b/docs/3.Custom Policies/YAML Custom Policies.md
@@ -0,0 +1,198 @@
+---
+layout: default
+published: true
+title: YAML Custom Policies
+nav_order: 3
+---
+
+# Create Custom Policy - YAML - Attribute Check and Composite
+
+Custom policies created in YAML support checking a resource’s connection state and the use of complex AND/OR logic. Read also how to [create custom Python Policies for attribute scanning](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html).
+
+A YAML-based custom policy for Checkov consists of sections for the **Metadata** and **Policy Definition**.
+
+
+
+**Metadata**
+
+The Metadata includes:
+
+* Policy Name
+* ID - `CKV2__`
+* Category
+
+The possible values for category are:
+
+* GENERAL_SECURITY
+* LOGGING
+* ENCRYPTION
+* NETWORKING
+* IAM
+* BACKUP_AND_RECOVERY
+* CONVENTION
+* SECRETS
+* KUBERNETES
+
+## Policy Definition
+
+The policy definition consists of:
+
+* **Definition Block(s)** - either *Attribute Block(s)* or *Connection State Block(s)* or both
+* **Logical Operator(s)** (optional)
+* **Filter**(optional)
+
+
+## Types of Definition Blocks
+
+* **Attribute Blocks:** The policy describes resources with a certain configuration as defined by a configuration **attribute** and its value (per Terraform), or by the presence/absence of an attribute.
+* **Connection State Blocks** - The policy describes resources in a particular **Connection state**; either connected or not connected to another type of resource (for example, a security group).
+
+### Using AND/OR Logic
+A policy definition may include multiple blocks (**Attribute**, **Connection state** or both), associated by **AND/OR** logic.
+
+## Attribute Blocks
+
+An **Attribute Block** in a policy's definition indicates that a resource will be non-compliant if a certain configuration attribute does not have a specified value or if it exists/doesn't exist.
+
+Bridgecrew's custom policies in code utilize the Terraform attribute library and syntax. These policies are checked during scans of both build-time and runtime resources and for all supported cloud providers.
+
+### Attribute Block Example
+
+The Attribute Block in the `definition` in the example below is used to ensure that a proper back-up policy is configured for Redshift clusters:
+
+```yaml
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_redshift_cluster"
+ attribute: "automated_snapshot_retention_period"
+ operator: "not_equals"
+ value: "0"
+```
+
+### Attribute Condition: Operators
+
+| Operator | Value in YAML |
+| ----- | ----- |
+| Equals | `equals` |
+| Not Equals | `not_equals` |
+| Exists | `exists` |
+| Not Exists | `not_exists` |
+| Contains | `contains` |
+| Not Contains | `not_contains` |
+| Starts With | `starting_with` |
+| Not Starts with | `not_starting_with` |
+| Ends With | `ending_with` |
+| Not Ends With | `not_ending_with` |
+
+### Attribute Condition: Keys and Values
+
+| Key | Type | Value(s) |
+| --- | --- | --- |
+| `cond_type` | string | Must be `attribute` |
+| `resource_type` | collection of strings | Use either `all` or `[resource types from list]` |
+| `attribute` | string | Attribute of defined resource types. For example, `automated_snapshot_retention_period` |
+| `operator` | string | - `equals`, `not_equals`, `exists`, `not exists`, `contains`, `not_contains`, `starting_with`, `not_starting_with`, `ending_with`, `not_ending_with` |
+| `value` (not relevant for operator: `exists`/`not_exists`) | string | User input. |
+
+
+## Connection State Block
+
+A Connection State Block indicates a type of resource that has or does not have a connection to another type of resource.
+In the example presented in the table below, in order to be compliant, `aws_lb` and `aws_elb` must have connections to either `aws_security_group` or `aws_default_security_group`.
+
+| Group A | Group B |
+| --- | --- |
+|`aws_lb` `aws_elb` | `aws_security_group` `aws_default_security_group` |
+
+
+### Connection State Example
+
+The Connection State Block below indicates that to be compliant with the policy, resources of type `aws_lb` or of type `aws_elb` must be connected to either a resource of type `aws_security_group` or a resource of type `aws_default_security_group`.
+
+```yaml
+definition:
+ cond_type: "connection"
+ resource_types:
+ - "aws_elb"
+ - "aws_lb"
+ connected_resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ operator: "exists"
+```
+
+### Connection State Condition: Operators
+
+| Operator | Value |
+| ----- | ----- |
+| Exists | `exists` |
+| Not Exists | `not_exists` |
+
+### Connection State Condition: Keys and Values
+
+| Key | Type | Values |
+| ----- | ----- | ----- |
+| `cond_type` | string | Must be `connection` |
+| `resource_types` | | Use either `all` or `[included resource type from list]` |
+| `connected_resource_types` | collection of strings | Use either `all` or `[included resource type from list]` |
+| `operator` | string | `exists`/`not exists` |
+
+## Filters
+
+Filters can be used to limit the types of resources relevant to a condition. Filters are most commonly used for Connection Blocks (for Attribute Blocks you can easily limit the resource type with the `resource_type` parameter).
+For example, you may want to enforce a policy only for a specific resource type (or types) from specific groups defined in the conditions. Filters are available only for AND logic at the top level.
+
+### Filter Example
+
+The Custom Policy in this example ensures that all ELBs are attached to security groups as shown in the table below. In line with best practices, connections of this nature should be defined using the `security_groups` key.
+
+| Group A | Group B |
+| ----- | ----- |
+| `aws_elb` | `aws_security_group` `aws_default_security_group` |
+| Not Exists | `not_exists` |
+
+```yaml
+defintion:
+ and:
+ - cond_type: "filter"
+ resource_types:
+ - "aws_elb"
+ attribute: “resource_type”
+ operator: "within”
+ - cond_type: "connection"
+ resource_types:
+ - "aws_elb"
+ connected_resource_types:
+ - "aws_security_group"
+ - "aws_default_security_group"
+ operator: "exists"
+```
+
+*Note: The condition above uses AND logic. See [additional examples](https://www.checkov.io/3.Custom%20Policies/Examples.html) for complex logic in policy definitions.*
+
+## Using AND/OR Logic
+
+The Bridgecrew platform allows you to combine definition blocks using AND/OR operators.
+
+* The top-level logical operator is the first key below \"definition\" (and not an item in a collection). It defines the relationship of all of the definition blocks in the specific YAML policy definition.
+* Filter blocks apply (only) to the top-level and constitute an AND condition. For example, if you'd like to indicate a requirement for a Connection State between types of resources, but only within a certain subset of all of those resources.
+Every other logical operator applies within a collection. For example, you can use AND/OR logic in a collection of key-value pairs.
+
+### Example
+
+The logic in the policy definition shown below is:
+`AND[block 1,block 2,OR[block 3,block 4]]`.
+
+```yaml
+#....
+defintion:
+ and:
+ - #filter block 1
+ - #block 2
+ - or:
+ - #block 3
+ - #block 4
+```
+
+[See all examples of Custom Policies in code](https://www.checkov.io/3.Custom%20Policies/Examples.html)
diff --git a/docs/3.Custom Policies/policy-definition.png b/docs/3.Custom Policies/policy-definition.png
new file mode 100644
index 0000000000..d732f3c151
Binary files /dev/null and b/docs/3.Custom Policies/policy-definition.png differ
diff --git a/docs/3.Scans/resource-scans.md b/docs/3.Scans/resource-scans.md
deleted file mode 100644
index 4e7af99084..0000000000
--- a/docs/3.Scans/resource-scans.md
+++ /dev/null
@@ -1,518 +0,0 @@
----
-layout: default
-title: Resource scans
-nav_order: 1
----
-
-# Resource scans (auto generated)
-
-| | Id | Type | Entity | Policy | IaC |
-|-----|--------------|-------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
-| 0 | CKV_AWS_1 | data | aws_iam_policy_document | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
-| 1 | CKV_AWS_1 | resource | serverless_aws | Ensure IAM policies that allow full "*-*" administrative privileges are not created | serverless |
-| 2 | CKV_AWS_2 | resource | aws_lb_listener | Ensure ALB protocol is HTTPS | Terraform |
-| 3 | CKV_AWS_2 | resource | AWS::ElasticLoadBalancingV2::Listener | Ensure ALB protocol is HTTPS | Cloudformation |
-| 4 | CKV_AWS_3 | resource | aws_ebs_volume | Ensure all data stored in the EBS is securely encrypted | Terraform |
-| 5 | CKV_AWS_3 | resource | AWS::EC2::Volume | Ensure all data stored in the EBS is securely encrypted | Cloudformation |
-| 6 | CKV_AWS_5 | resource | aws_elasticsearch_domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Terraform |
-| 7 | CKV_AWS_5 | resource | AWS::Elasticsearch::Domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Cloudformation |
-| 8 | CKV_AWS_6 | resource | aws_elasticsearch_domain | Ensure all Elasticsearch has node-to-node encryption enabled | Terraform |
-| 9 | CKV_AWS_6 | resource | AWS::Elasticsearch::Domain | Ensure all Elasticsearch has node-to-node encryption enabled | Cloudformation |
-| 10 | CKV_AWS_7 | resource | aws_kms_key | Ensure rotation for customer created CMKs is enabled | Terraform |
-| 11 | CKV_AWS_7 | resource | AWS::KMS::Key | Ensure rotation for customer created CMKs is enabled | Cloudformation |
-| 12 | CKV_AWS_8 | resource | aws_instance | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
-| 13 | CKV_AWS_8 | resource | aws_launch_configuration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
-| 14 | CKV_AWS_8 | resource | AWS::AutoScaling::LaunchConfiguration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Cloudformation |
-| 15 | CKV_AWS_9 | resource | aws_iam_account_password_policy | Ensure IAM password policy expires passwords within 90 days or less | Terraform |
-| 16 | CKV_AWS_10 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires minimum length of 14 or greater | Terraform |
-| 17 | CKV_AWS_11 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one lowercase letter | Terraform |
-| 18 | CKV_AWS_12 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one number | Terraform |
-| 19 | CKV_AWS_13 | resource | aws_iam_account_password_policy | Ensure IAM password policy prevents password reuse | Terraform |
-| 20 | CKV_AWS_14 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one symbol | Terraform |
-| 21 | CKV_AWS_15 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one uppercase letter | Terraform |
-| 22 | CKV_AWS_16 | resource | aws_db_instance | Ensure all data stored in the RDS is securely encrypted at rest | Terraform |
-| 23 | CKV_AWS_16 | resource | AWS::RDS::DBInstance | Ensure all data stored in the RDS is securely encrypted at rest | Cloudformation |
-| 24 | CKV_AWS_17 | resource | aws_db_instance | Ensure all data stored in the RDS bucket is not public accessible | Terraform |
-| 25 | CKV_AWS_17 | resource | aws_rds_cluster_instance | Ensure all data stored in the RDS bucket is not public accessible | Terraform |
-| 26 | CKV_AWS_17 | resource | AWS::RDS::DBInstance | Ensure all data stored in the RDS bucket is not public accessible | Cloudformation |
-| 27 | CKV_AWS_18 | resource | aws_s3_bucket | Ensure the S3 bucket has access logging enabled | Terraform |
-| 28 | CKV_AWS_18 | resource | AWS::S3::Bucket | Ensure the S3 bucket has access logging enabled | Cloudformation |
-| 29 | CKV_AWS_19 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket is securely encrypted at rest | Terraform |
-| 30 | CKV_AWS_19 | resource | AWS::S3::Bucket | Ensure the S3 bucket has server-side-encryption enabled | Cloudformation |
-| 31 | CKV_AWS_20 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public READ access. | Terraform |
-| 32 | CKV_AWS_20 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow READ permissions to everyone | Cloudformation |
-| 33 | CKV_AWS_21 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket have versioning enabled | Terraform |
-| 34 | CKV_AWS_21 | resource | AWS::S3::Bucket | Ensure the S3 bucket has versioning enabled | Cloudformation |
-| 35 | CKV_AWS_22 | resource | aws_sagemaker_notebook_instance | Ensure all data stored in the Sagemaker Notebook is securely encrypted at rest | Terraform |
-| 36 | CKV_AWS_23 | resource | aws_security_group | Ensure every security groups rule has a description | Terraform |
-| 37 | CKV_AWS_23 | resource | aws_security_group_rule | Ensure every security groups rule has a description | Terraform |
-| 38 | CKV_AWS_23 | resource | aws_db_security_group | Ensure every security groups rule has a description | Terraform |
-| 39 | CKV_AWS_23 | resource | aws_elasticache_security_group | Ensure every security groups rule has a description | Terraform |
-| 40 | CKV_AWS_23 | resource | aws_redshift_security_group | Ensure every security groups rule has a description | Terraform |
-| 41 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroup | Ensure every security groups rule has a description | Cloudformation |
-| 42 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupIngress | Ensure every security groups rule has a description | Cloudformation |
-| 43 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupEgress | Ensure every security groups rule has a description | Cloudformation |
-| 44 | CKV_AWS_24 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
-| 45 | CKV_AWS_24 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
-| 46 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
-| 47 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
-| 48 | CKV_AWS_25 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
-| 49 | CKV_AWS_25 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
-| 50 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
-| 51 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
-| 52 | CKV_AWS_26 | resource | aws_sns_topic | Ensure all data stored in the SNS topic is encrypted | Terraform |
-| 53 | CKV_AWS_26 | resource | AWS::SNS::Topic | Ensure all data stored in the SNS topic is encrypted | Cloudformation |
-| 54 | CKV_AWS_27 | resource | aws_sqs_queue | Ensure all data stored in the SQS queue is encrypted | Terraform |
-| 55 | CKV_AWS_27 | resource | AWS::SQS::Queue | Ensure all data stored in the SQS queue is encrypted | Cloudformation |
-| 56 | CKV_AWS_28 | resource | aws_dynamodb_table | Ensure Dynamodb point in time recovery (backup) is enabled | Terraform |
-| 57 | CKV_AWS_28 | resource | AWS::DynamoDB::Table | Ensure Dynamodb point in time recovery (backup) is enabled | Cloudformation |
-| 58 | CKV_AWS_29 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Terraform |
-| 59 | CKV_AWS_29 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Cloudformation |
-| 60 | CKV_AWS_30 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Terraform |
-| 61 | CKV_AWS_30 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Cloudformation |
-| 62 | CKV_AWS_31 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Terraform |
-| 63 | CKV_AWS_31 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Cloudformation |
-| 64 | CKV_AWS_32 | resource | aws_ecr_repository_policy | Ensure ECR policy is not set to public | Terraform |
-| 65 | CKV_AWS_32 | resource | AWS::ECR::Repository | Ensure ECR policy is not set to public | Cloudformation |
-| 66 | CKV_AWS_33 | resource | aws_ecr_repository | Ensure ECR image scanning on push is enabled | Terraform |
-| 67 | CKV_AWS_33 | resource | AWS::KMS::Key | Ensure KMS key policy does not contain wildcard (*) principal | Cloudformation |
-| 68 | CKV_AWS_34 | resource | aws_cloudfront_distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Terraform |
-| 69 | CKV_AWS_34 | resource | AWS::CloudFront::Distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Cloudformation |
-| 70 | CKV_AWS_35 | resource | aws_cloudtrail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Terraform |
-| 71 | CKV_AWS_35 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Cloudformation |
-| 72 | CKV_AWS_36 | resource | aws_cloudtrail | Ensure CloudTrail log file validation is enabled | Terraform |
-| 73 | CKV_AWS_36 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail log file validation is enabled | Cloudformation |
-| 74 | CKV_AWS_37 | resource | aws_eks_cluster | Ensure Amazon EKS control plane logging enabled for all log types | Terraform |
-| 75 | CKV_AWS_38 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint not accessible to 0.0.0.0/0 | Terraform |
-| 76 | CKV_AWS_39 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint disabled | Terraform |
-| 77 | CKV_AWS_40 | resource | aws_iam_user_policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
-| 78 | CKV_AWS_40 | resource | aws_iam_user_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
-| 79 | CKV_AWS_40 | resource | aws_iam_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
-| 80 | CKV_AWS_40 | resource | AWS::IAM::Policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Cloudformation |
-| 81 | CKV_AWS_41 | provider | aws | Ensure no hard coded AWS access key and secret key exists in provider | Terraform |
-| 82 | CKV_AWS_41 | resource | serverless_aws | Ensure no hard coded AWS access key and secret key exists in provider | serverless |
-| 83 | CKV_AWS_42 | resource | aws_efs_file_system | Ensure EFS is securely encrypted | Terraform |
-| 84 | CKV_AWS_42 | resource | AWS::EFS::FileSystem | Ensure EFS is securely encrypted | Cloudformation |
-| 85 | CKV_AWS_43 | resource | aws_kinesis_stream | Ensure Kinesis Stream is securely encrypted | Terraform |
-| 86 | CKV_AWS_43 | resource | AWS::Kinesis::Stream | Ensure Kinesis Stream is securely encrypted | Cloudformation |
-| 87 | CKV_AWS_44 | resource | aws_neptune_cluster | Ensure Neptune storage is securely encrypted | Terraform |
-| 88 | CKV_AWS_44 | resource | AWS::Neptune::DBCluster | Ensure Neptune storage is securely encrypted | Cloudformation |
-| 89 | CKV_AWS_45 | resource | aws_lambda_function | Ensure no hard coded AWS access key and secret key exists in lambda environment | Terraform |
-| 90 | CKV_AWS_46 | resource | aws_instance | Ensure no hard coded AWS access key and secret key exists in EC2 user data | Terraform |
-| 91 | CKV_AWS_47 | resource | aws_dax_cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Terraform |
-| 92 | CKV_AWS_47 | resource | AWS::DAX::Cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Cloudformation |
-| 93 | CKV_AWS_48 | resource | aws_mq_broker | Ensure MQ Broker logging is enabled | Terraform |
-| 94 | CKV_AWS_49 | data | aws_iam_policy_document | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
-| 95 | CKV_AWS_49 | resource | serverless_aws | Ensure no IAM policies documents allow "*" as a statement's actions | serverless |
-| 96 | CKV_AWS_50 | resource | aws_lambda_function | X-ray tracing is enabled for Lambda | Terraform |
-| 97 | CKV_AWS_51 | resource | aws_ecr_repository | Ensure ECR Image Tags are immutable | Terraform |
-| 98 | CKV_AWS_51 | resource | AWS::ECR::Repository | Ensure ECR Image Tags are immutable | Cloudformation |
-| 99 | CKV_AWS_52 | resource | aws_s3_bucket | Ensure S3 bucket has MFA delete enabled | Terraform |
-| 100 | CKV_AWS_53 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public ACLS enabled | Terraform |
-| 101 | CKV_AWS_53 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public ACLS enabled | Cloudformation |
-| 102 | CKV_AWS_54 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public policy enabled | Terraform |
-| 103 | CKV_AWS_54 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public policy enabled | Cloudformation |
-| 104 | CKV_AWS_55 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has ignore public ACLs enabled | Terraform |
-| 105 | CKV_AWS_55 | resource | AWS::S3::Bucket | Ensure S3 bucket has ignore public ACLs enabled | Cloudformation |
-| 106 | CKV_AWS_56 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has 'restrict_public_bucket' enabled | Terraform |
-| 107 | CKV_AWS_56 | resource | AWS::S3::Bucket | Ensure S3 bucket has 'restrict_public_bucket' enabled | Cloudformation |
-| 108 | CKV_AWS_57 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public WRITE access. | Terraform |
-| 109 | CKV_AWS_57 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow WRITE permissions to everyone | Cloudformation |
-| 110 | CKV_AWS_58 | resource | aws_eks_cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Terraform |
-| 111 | CKV_AWS_58 | resource | AWS::EKS::Cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Cloudformation |
-| 112 | CKV_AWS_59 | resource | aws_api_gateway_method | Ensure there is no open access to back-end resources through API | Terraform |
-| 113 | CKV_AWS_59 | resource | AWS::ApiGateway::Method | Ensure there is no open access to back-end resources through API | Cloudformation |
-| 114 | CKV_AWS_60 | resource | aws_iam_role | Ensure IAM role allows only specific services or principals to assume it | Terraform |
-| 115 | CKV_AWS_61 | resource | aws_iam_role | Ensure IAM role allows only specific principals in account to assume it | Terraform |
-| 116 | CKV_AWS_61 | resource | AWS::IAM::Role | Ensure IAM role allows only specific principals in account to assume it | Cloudformation |
-| 117 | CKV_AWS_62 | resource | aws_iam_role_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
-| 118 | CKV_AWS_62 | resource | aws_iam_user_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
-| 119 | CKV_AWS_62 | resource | aws_iam_group_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
-| 120 | CKV_AWS_62 | resource | aws_iam_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
-| 121 | CKV_AWS_63 | resource | aws_iam_role_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
-| 122 | CKV_AWS_63 | resource | aws_iam_user_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
-| 123 | CKV_AWS_63 | resource | aws_iam_group_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
-| 124 | CKV_AWS_63 | resource | aws_iam_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
-| 125 | CKV_AWS_64 | resource | aws_redshift_cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Terraform |
-| 126 | CKV_AWS_64 | resource | AWS::Redshift::Cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Cloudformation |
-| 127 | CKV_AWS_65 | resource | aws_ecs_cluster | Ensure container insights are enabled on ECS cluster | Terraform |
-| 128 | CKV_AWS_65 | resource | AWS::ECS::Cluster | Ensure container insights are enabled on ECS cluster | Cloudformation |
-| 129 | CKV_AWS_66 | resource | aws_cloudwatch_log_group | Ensure cloudwatch log groups specify retention days | Terraform |
-| 130 | CKV_AWS_66 | resource | AWS::Logs::LogGroup | Ensure cloudwatch log groups specify retention days | Cloudformation |
-| 131 | CKV_AWS_67 | resource | aws_cloudtrail | Ensure CloudTrail is enabled in all Regions | Terraform |
-| 132 | CKV_AWS_67 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail is enabled in all Regions | Cloudformation |
-| 133 | CKV_AWS_68 | resource | aws_cloudfront_distribution | CloudFront Distribution should have WAF enabled | Terraform |
-| 134 | CKV_AWS_68 | resource | AWS::CloudFront::Distribution | CloudFront Distribution should have WAF enabled | Cloudformation |
-| 135 | CKV_AWS_69 | resource | aws_mq_broker | Ensure MQ Broker is not publicly exposed | Terraform |
-| 136 | CKV_AWS_70 | resource | aws_s3_bucket | Ensure S3 bucket does not allow an action with any Principal | Terraform |
-| 137 | CKV_AWS_70 | resource | aws_s3_bucket_policy | Ensure S3 bucket does not allow an action with any Principal | Terraform |
-| 138 | CKV_AWS_71 | resource | aws_redshift_cluster | Ensure Redshift Cluster logging is enabled | Terraform |
-| 139 | CKV_AWS_72 | resource | aws_sqs_queue_policy | Ensure SQS policy does not allow ALL (*) actions. | Terraform |
-| 140 | CKV_AWS_73 | resource | aws_api_gateway_stage | Ensure API Gateway has X-Ray Tracing enabled | Terraform |
-| 141 | CKV_AWS_73 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has X-Ray Tracing enabled | Cloudformation |
-| 142 | CKV_AWS_74 | resource | aws_docdb_cluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Terraform |
-| 143 | CKV_AWS_74 | resource | AWS::DocDB::DBCluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Cloudformation |
-| 144 | CKV_AWS_75 | resource | aws_globalaccelerator_accelerator | Ensure Global Accelerator accelerator has flow logs enabled | Terraform |
-| 145 | CKV_AWS_76 | resource | aws_api_gateway_stage | Ensure API Gateway has Access Logging enabled | Terraform |
-| 146 | CKV_AWS_76 | resource | aws_apigatewayv2_stage | Ensure API Gateway has Access Logging enabled | Terraform |
-| 147 | CKV_AWS_76 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has Access Logging enabled | Cloudformation |
-| 148 | CKV_AWS_77 | resource | aws_athena_database | Ensure Athena Database is encrypted at rest (default is unencrypted) | Terraform |
-| 149 | CKV_AWS_78 | resource | aws_codebuild_project | Ensure that CodeBuild Project encryption is not disabled | Terraform |
-| 150 | CKV_AWS_78 | resource | AWS::CodeBuild::Project | Ensure that CodeBuild Project encryption is not disabled | Cloudformation |
-| 151 | CKV_AWS_79 | resource | aws_instance | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
-| 152 | CKV_AWS_79 | resource | aws_launch_template | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
-| 153 | CKV_AWS_80 | resource | aws_msk_cluster | Ensure MSK Cluster logging is enabled | Terraform |
-| 154 | CKV_AWS_81 | resource | aws_msk_cluster | Ensure MSK Cluster encryption in rest and transit is enabled | Terraform |
-| 155 | CKV_AWS_82 | resource | aws_athena_workgroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Terraform |
-| 156 | CKV_AWS_82 | resource | AWS::Athena::WorkGroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Cloudformation |
-| 157 | CKV_AWS_83 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain enforces HTTPS | Terraform |
-| 158 | CKV_AWS_84 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain Logging is enabled | Terraform |
-| 159 | CKV_AWS_85 | resource | aws_docdb_cluster | Ensure DocDB Logging is enabled | Terraform |
-| 160 | CKV_AWS_85 | resource | AWS::DocDB::DBCluster | Ensure DocDB Logging is enabled | Cloudformation |
-| 161 | CKV_AWS_86 | resource | aws_cloudfront_distribution | Ensure Cloudfront distribution has Access Logging enabled | Terraform |
-| 162 | CKV_AWS_86 | resource | AWS::CloudFront::Distribution | Ensure Cloudfront distribution has Access Logging enabled | Cloudformation |
-| 163 | CKV_AWS_87 | resource | aws_redshift_cluster | Redshift cluster should not be publicly accessible | Terraform |
-| 164 | CKV_AWS_88 | resource | aws_instance | EC2 instance should not have public IP. | Terraform |
-| 165 | CKV_AWS_88 | resource | aws_launch_template | EC2 instance should not have public IP. | Terraform |
-| 166 | CKV_AWS_89 | resource | aws_dms_replication_instance | DMS replication instance should not be publicly accessible | Terraform |
-| 167 | CKV_AWS_90 | resource | aws_docdb_cluster_parameter_group | Ensure DocDB TLS is not disabled | Terraform |
-| 168 | CKV_AWS_90 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB TLS is not disabled | Cloudformation |
-| 169 | CKV_AWS_91 | resource | aws_lb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
-| 170 | CKV_AWS_91 | resource | aws_alb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
-| 171 | CKV_AWS_92 | resource | aws_elb | Ensure the ELB has access logging enabled | Terraform |
-| 172 | CKV_AWS_93 | resource | aws_s3_bucket | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
-| 173 | CKV_AWS_93 | resource | aws_s3_bucket_policy | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
-| 174 | CKV_AWS_94 | resource | aws_glue_data_catalog_encryption_settings | Ensure Glue Data Catalog Encryption is enabled | Terraform |
-| 175 | CKV_AWS_95 | resource | AWS::ApiGatewayV2::Stage | Ensure API Gateway V2 has Access Logging enabled | Cloudformation |
-| 176 | CKV_AWS_96 | resource | aws_rds_cluster | Ensure all data stored in Aurora is securely encrypted at rest | Terraform |
-| 177 | CKV_AWS_96 | resource | AWS::RDS::DBCluster | Ensure all data stored in Aurrora is securely encrypted at rest | Cloudformation |
-| 178 | CKV_AWS_97 | resource | aws_ecs_task_definition | Ensure Encryption in transit is enabled for ECS Task defintion EFS volumes | Terraform |
-| 179 | CKV_AWS_98 | resource | aws_sagemaker_endpoint_configuration | Ensure all data stored in the Sagemaker Endpoint is securely encrypted at rest | Terraform |
-| 180 | CKV_AWS_99 | resource | aws_glue_security_configuration | Ensure Glue Security Configuration Encryption is enabled | Terraform |
-| 181 | CKV_AWS_100 | resource | aws_eks_node_group | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Terraform |
-| 182 | CKV_AZURE_1 | resource | azurerm_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
-| 183 | CKV_AZURE_1 | resource | azurerm_linux_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
-| 184 | CKV_AZURE_1 | resource | Microsoft.Compute/virtualMachines | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | arm |
-| 185 | CKV_AZURE_2 | resource | azurerm_managed_disk | Ensure Azure managed disk have encryption enabled | Terraform |
-| 186 | CKV_AZURE_2 | resource | Microsoft.Compute/disks | Ensure Azure managed disk have encryption enabled | arm |
-| 187 | CKV_AZURE_3 | resource | azurerm_storage_account | Ensure that 'Secure transfer required' is set to 'Enabled' | Terraform |
-| 188 | CKV_AZURE_3 | resource | Microsoft.Storage/storageAccounts | Ensure that 'supportsHttpsTrafficOnly' is set to 'true' | arm |
-| 189 | CKV_AZURE_4 | resource | azurerm_kubernetes_cluster | Ensure AKS logging to Azure Monitoring is Configured | Terraform |
-| 190 | CKV_AZURE_4 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS logging to Azure Monitoring is Configured | arm |
-| 191 | CKV_AZURE_5 | resource | azurerm_kubernetes_cluster | Ensure RBAC is enabled on AKS clusters | Terraform |
-| 192 | CKV_AZURE_5 | resource | Microsoft.ContainerService/managedClusters | Ensure RBAC is enabled on AKS clusters | arm |
-| 193 | CKV_AZURE_6 | resource | azurerm_kubernetes_cluster | Ensure AKS has an API Server Authorized IP Ranges enabled | Terraform |
-| 194 | CKV_AZURE_6 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS has an API Server Authorized IP Ranges enabled | arm |
-| 195 | CKV_AZURE_7 | resource | azurerm_kubernetes_cluster | Ensure AKS cluster has Network Policy configured | Terraform |
-| 196 | CKV_AZURE_7 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS cluster has Network Policy configured | arm |
-| 197 | CKV_AZURE_8 | resource | azurerm_kubernetes_cluster | Ensure Kube Dashboard is disabled | Terraform |
-| 198 | CKV_AZURE_8 | resource | Microsoft.ContainerService/managedClusters | Ensure Kubernetes Dashboard is disabled | arm |
-| 199 | CKV_AZURE_9 | resource | azurerm_network_security_rule | Ensure that RDP access is restricted from the internet | Terraform |
-| 200 | CKV_AZURE_9 | resource | azurerm_network_security_group | Ensure that RDP access is restricted from the internet | Terraform |
-| 201 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups | Ensure that RDP access is restricted from the internet | arm |
-| 202 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that RDP access is restricted from the internet | arm |
-| 203 | CKV_AZURE_10 | resource | azurerm_network_security_rule | Ensure that SSH access is restricted from the internet | Terraform |
-| 204 | CKV_AZURE_10 | resource | azurerm_network_security_group | Ensure that SSH access is restricted from the internet | Terraform |
-| 205 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups | Ensure that SSH access is restricted from the internet | arm |
-| 206 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that SSH access is restricted from the internet | arm |
-| 207 | CKV_AZURE_11 | resource | azurerm_mariadb_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
-| 208 | CKV_AZURE_11 | resource | azurerm_sql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
-| 209 | CKV_AZURE_11 | resource | azurerm_postgresql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
-| 210 | CKV_AZURE_11 | resource | azurerm_mysql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
-| 211 | CKV_AZURE_11 | resource | Microsoft.Sql/servers | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | arm |
-| 212 | CKV_AZURE_12 | resource | azurerm_network_watcher_flow_log | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | Terraform |
-| 213 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
-| 214 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
-| 215 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
-| 216 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
-| 217 | CKV_AZURE_13 | resource | azurerm_app_service | Ensure App Service Authentication is set on Azure App Service | Terraform |
-| 218 | CKV_AZURE_13 | resource | Microsoft.Web/sites/config | Ensure App Service Authentication is set on Azure App Service | arm |
-| 219 | CKV_AZURE_13 | resource | config | Ensure App Service Authentication is set on Azure App Service | arm |
-| 220 | CKV_AZURE_14 | resource | azurerm_app_service | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | Terraform |
-| 221 | CKV_AZURE_14 | resource | Microsoft.Web/sites | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | arm |
-| 222 | CKV_AZURE_15 | resource | azurerm_app_service | Ensure web app is using the latest version of TLS encryption | Terraform |
-| 223 | CKV_AZURE_15 | resource | Microsoft.Web/sites | Ensure web app is using the latest version of TLS encryption | arm |
-| 224 | CKV_AZURE_16 | resource | azurerm_app_service | Ensure that Register with Azure Active Directory is enabled on App Service | Terraform |
-| 225 | CKV_AZURE_16 | resource | Microsoft.Web/sites | Ensure that Register with Azure Active Directory is enabled on App Service | arm |
-| 226 | CKV_AZURE_17 | resource | azurerm_app_service | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | Terraform |
-| 227 | CKV_AZURE_17 | resource | Microsoft.Web/sites | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | arm |
-| 228 | CKV_AZURE_18 | resource | azurerm_app_service | Ensure that 'HTTP Version' is the latest if used to run the web app | Terraform |
-| 229 | CKV_AZURE_18 | resource | Microsoft.Web/sites | Ensure that 'HTTP Version' is the latest if used to run the web app | arm |
-| 230 | CKV_AZURE_19 | resource | azurerm_security_center_subscription_pricing | Ensure that standard pricing tier is selected | Terraform |
-| 231 | CKV_AZURE_19 | resource | Microsoft.Security/pricings | Ensure that standard pricing tier is selected | arm |
-| 232 | CKV_AZURE_20 | resource | azurerm_security_center_contact | Ensure that security contact 'Phone number' is set | Terraform |
-| 233 | CKV_AZURE_20 | resource | Microsoft.Security/securityContacts | Ensure that security contact 'Phone number' is set | arm |
-| 234 | CKV_AZURE_21 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
-| 235 | CKV_AZURE_21 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
-| 236 | CKV_AZURE_22 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
-| 237 | CKV_AZURE_22 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
-| 238 | CKV_AZURE_23 | resource | azurerm_sql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
-| 239 | CKV_AZURE_23 | resource | azurerm_mssql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
-| 240 | CKV_AZURE_23 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' is set to 'Enabled' for SQL servers | arm |
-| 241 | CKV_AZURE_24 | resource | azurerm_sql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
-| 242 | CKV_AZURE_24 | resource | azurerm_mssql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
-| 243 | CKV_AZURE_24 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | arm |
-| 244 | CKV_AZURE_25 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Threat Detection types' is set to 'All' | Terraform |
-| 245 | CKV_AZURE_25 | resource | Microsoft.Sql/servers/databases | Ensure that 'Threat Detection types' is set to 'All' | arm |
-| 246 | CKV_AZURE_26 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Send Alerts To' is enabled for MSSQL servers | Terraform |
-| 247 | CKV_AZURE_26 | resource | Microsoft.Sql/servers/databases | Ensure that 'Send Alerts To' is enabled for MSSQL servers | arm |
-| 248 | CKV_AZURE_27 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | Terraform |
-| 249 | CKV_AZURE_27 | resource | Microsoft.Sql/servers/databases | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | arm |
-| 250 | CKV_AZURE_28 | resource | azurerm_mysql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | Terraform |
-| 251 | CKV_AZURE_28 | resource | Microsoft.DBforMySQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | arm |
-| 252 | CKV_AZURE_29 | resource | azurerm_postgresql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | Terraform |
-| 253 | CKV_AZURE_29 | resource | Microsoft.DBforPostgreSQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | arm |
-| 254 | CKV_AZURE_30 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | Terraform |
-| 255 | CKV_AZURE_30 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
-| 256 | CKV_AZURE_30 | resource | configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
-| 257 | CKV_AZURE_31 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_connections' is set to 'ON' for PostgreSQL Database Server | Terraform |
-| 258 | CKV_AZURE_31 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
-| 259 | CKV_AZURE_31 | resource | configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
-| 260 | CKV_AZURE_32 | resource | azurerm_postgresql_configuration | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | Terraform |
-| 261 | CKV_AZURE_32 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
-| 262 | CKV_AZURE_32 | resource | configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
-| 263 | CKV_AZURE_33 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Queue service for read, write and delete requests | Terraform |
-| 264 | CKV_AZURE_33 | resource | Microsoft.Storage/storageAccounts/queueServices/providers/diagnosticsettings | Ensure Storage logging is enabled for Queue service for read, write and delete requests | arm |
-| 265 | CKV_AZURE_34 | resource | azurerm_storage_container | Ensure that 'Public access level' is set to Private for blob containers | Terraform |
-| 266 | CKV_AZURE_34 | resource | Microsoft.Storage/storageAccounts/blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
-| 267 | CKV_AZURE_34 | resource | containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
-| 268 | CKV_AZURE_34 | resource | blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
-| 269 | CKV_AZURE_35 | resource | azurerm_storage_account | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
-| 270 | CKV_AZURE_35 | resource | azurerm_storage_account_network_rules | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
-| 271 | CKV_AZURE_35 | resource | Microsoft.Storage/storageAccounts | Ensure default network access rule for Storage Accounts is set to deny | arm |
-| 272 | CKV_AZURE_36 | resource | azurerm_storage_account | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
-| 273 | CKV_AZURE_36 | resource | azurerm_storage_account_network_rules | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
-| 274 | CKV_AZURE_36 | resource | Microsoft.Storage/storageAccounts | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | arm |
-| 275 | CKV_AZURE_37 | resource | azurerm_monitor_log_profile | Ensure that Activity Log Retention is set 365 days or greater | Terraform |
-| 276 | CKV_AZURE_37 | resource | microsoft.insights/logprofiles | Ensure that Activity Log Retention is set 365 days or greater | arm |
-| 277 | CKV_AZURE_38 | resource | azurerm_monitor_log_profile | Ensure audit profile captures all the activities | Terraform |
-| 278 | CKV_AZURE_38 | resource | microsoft.insights/logprofiles | Ensure audit profile captures all the activities | arm |
-| 279 | CKV_AZURE_39 | resource | azurerm_role_definition | Ensure that no custom subscription owner roles are created | Terraform |
-| 280 | CKV_AZURE_39 | resource | Microsoft.Authorization/roleDefinitions | Ensure that no custom subscription owner roles are created | arm |
-| 281 | CKV_AZURE_40 | resource | azurerm_key_vault_key | Ensure that the expiration date is set on all keys | Terraform |
-| 282 | CKV_AZURE_41 | resource | azurerm_key_vault_secret | Ensure that the expiration date is set on all secrets | Terraform |
-| 283 | CKV_AZURE_41 | resource | Microsoft.KeyVault/vaults/secrets | Ensure that the expiration date is set on all secrets | arm |
-| 284 | CKV_AZURE_42 | resource | azurerm_key_vault | Ensure the key vault is recoverable | Terraform |
-| 285 | CKV_AZURE_42 | resource | Microsoft.KeyVault/vaults | Ensure the key vault is recoverable | arm |
-| 286 | CKV_AZURE_43 | resource | azurerm_storage_account | Ensure the Storage Account naming rules | Terraform |
-| 287 | CKV_AZURE_44 | resource | azurerm_storage_account | Ensure Storage Account is using the latest version of TLS encryption | Terraform |
-| 288 | CKV_AZURE_45 | resource | azurerm_virtual_machine | Ensure that no sensitive credentials are exposed in VM custom_data | Terraform |
-| 289 | CKV_GCP_1 | resource | google_container_cluster | Ensure Stackdriver Logging is set to Enabled on Kubernetes Engine Clusters | Terraform |
-| 290 | CKV_GCP_2 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted ssh access | Terraform |
-| 291 | CKV_GCP_3 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted rdp access | Terraform |
-| 292 | CKV_GCP_4 | resource | google_compute_ssl_policy | Ensure no HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites | Terraform |
-| 293 | CKV_GCP_5 | resource | google_storage_bucket | Ensure Google storage bucket have encryption enabled | Terraform |
-| 294 | CKV_GCP_6 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance requires all incoming connections to use SSL | Terraform |
-| 295 | CKV_GCP_7 | resource | google_container_cluster | Ensure Legacy Authorization is set to Disabled on Kubernetes Engine Clusters | Terraform |
-| 296 | CKV_GCP_8 | resource | google_container_cluster | Ensure Stackdriver Monitoring is set to Enabled on Kubernetes Engine Clusters | Terraform |
-| 297 | CKV_GCP_9 | resource | google_container_node_pool | Ensure 'Automatic node repair' is enabled for Kubernetes Clusters | Terraform |
-| 298 | CKV_GCP_10 | resource | google_container_node_pool | Ensure 'Automatic node upgrade' is enabled for Kubernetes Clusters | Terraform |
-| 299 | CKV_GCP_11 | resource | google_sql_database_instance | Ensure that Cloud SQL database Instances are not open to the world | Terraform |
-| 300 | CKV_GCP_12 | resource | google_container_cluster | Ensure Network Policy is enabled on Kubernetes Engine Clusters | Terraform |
-| 301 | CKV_GCP_13 | resource | google_container_cluster | Ensure a client certificate is used by clients to authenticate to Kubernetes Engine Clusters | Terraform |
-| 302 | CKV_GCP_14 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance have backup configuration enabled | Terraform |
-| 303 | CKV_GCP_15 | resource | google_bigquery_dataset | Ensure that BigQuery datasets are not anonymously or publicly accessible | Terraform |
-| 304 | CKV_GCP_16 | resource | google_dns_managed_zone | Ensure that DNSSEC is enabled for Cloud DNS | Terraform |
-| 305 | CKV_GCP_17 | resource | google_dns_managed_zone | Ensure that RSASHA1 is not used for the zone-signing and key-signing keys in Cloud DNS DNSSEC | Terraform |
-| 306 | CKV_GCP_18 | resource | google_container_cluster | Ensure GKE Control Plane is not public | Terraform |
-| 307 | CKV_GCP_19 | resource | google_container_cluster | Ensure GKE basic auth is disabled | Terraform |
-| 308 | CKV_GCP_20 | resource | google_container_cluster | Ensure master authorized networks is set to enabled in GKE clusters | Terraform |
-| 309 | CKV_GCP_21 | resource | google_container_cluster | Ensure Kubernetes Clusters are configured with Labels | Terraform |
-| 310 | CKV_GCP_22 | resource | google_container_node_pool | Ensure Container-Optimized OS (cos) is used for Kubernetes Engine Clusters Node image | Terraform |
-| 311 | CKV_GCP_23 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Alias IP ranges enabled | Terraform |
-| 312 | CKV_GCP_24 | resource | google_container_cluster | Ensure PodSecurityPolicy controller is enabled on the Kubernetes Engine Clusters | Terraform |
-| 313 | CKV_GCP_25 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Private cluster enabled | Terraform |
-| 314 | CKV_GCP_26 | resource | google_compute_subnetwork | Ensure that VPC Flow Logs is enabled for every subnet in a VPC Network | Terraform |
-| 315 | CKV_GCP_27 | resource | google_project | Ensure that the default network does not exist in a project | Terraform |
-| 316 | CKV_GCP_28 | resource | google_storage_bucket_iam_member | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
-| 317 | CKV_GCP_28 | resource | google_storage_bucket_iam_binding | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
-| 318 | CKV_GCP_29 | resource | google_storage_bucket | Ensure that Cloud Storage buckets have uniform bucket-level access enabled | Terraform |
-| 319 | CKV_GCP_30 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account | Terraform |
-| 320 | CKV_GCP_31 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account with full access to all Cloud APIs | Terraform |
-| 321 | CKV_GCP_32 | resource | google_compute_instance | Ensure 'Block Project-wide SSH keys' is enabled for VM instances | Terraform |
-| 322 | CKV_GCP_33 | resource | google_compute_project_metadata | Ensure oslogin is enabled for a Project | Terraform |
-| 323 | CKV_GCP_34 | resource | google_compute_instance | Ensure that no instance in the project overrides the project setting for enabling OSLogin(OSLogin needs to be enabled in project metadata for all instances) | Terraform |
-| 324 | CKV_GCP_35 | resource | google_compute_instance | Ensure 'Enable connecting to serial ports' is not enabled for VM Instance | Terraform |
-| 325 | CKV_GCP_36 | resource | google_compute_instance | Ensure that IP forwarding is not enabled on Instances | Terraform |
-| 326 | CKV_GCP_37 | resource | google_compute_disk | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
-| 327 | CKV_GCP_38 | resource | google_compute_instance | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
-| 328 | CKV_GCP_39 | resource | google_compute_instance | Ensure Compute instances are launched with Shielded VM enabled | Terraform |
-| 329 | CKV_GCP_40 | resource | google_compute_instance | Ensure that Compute instances do not have public IP addresses | Terraform |
-| 330 | CKV_GCP_41 | resource | google_project_iam_binding | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
-| 331 | CKV_GCP_41 | resource | google_project_iam_member | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
-| 332 | CKV_GCP_42 | resource | google_project_iam_member | Ensure that Service Account has no Admin privileges | Terraform |
-| 333 | CKV_GCP_43 | resource | google_kms_crypto_key | Ensure KMS encryption keys are rotated within a period of 90 days | Terraform |
-| 334 | CKV_GCP_44 | resource | google_folder_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
-| 335 | CKV_GCP_44 | resource | google_folder_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
-| 336 | CKV_GCP_45 | resource | google_organization_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
-| 337 | CKV_GCP_45 | resource | google_organization_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
-| 338 | CKV_GCP_46 | resource | google_project_iam_binding | Ensure Default Service account is not used at a project level | Terraform |
-| 339 | CKV_GCP_46 | resource | google_project_iam_member | Ensure Default Service account is not used at a project level | Terraform |
-| 340 | CKV_GCP_47 | resource | google_organization_iam_member | Ensure default service account is not used at an organization level | Terraform |
-| 341 | CKV_GCP_47 | resource | google_organization_iam_binding | Ensure default service account is not used at an organization level | Terraform |
-| 342 | CKV_GCP_48 | resource | google_folder_iam_member | Ensure Default Service account is not used at a folder level | Terraform |
-| 343 | CKV_GCP_48 | resource | google_folder_iam_binding | Ensure Default Service account is not used at a folder level | Terraform |
-| 344 | CKV_GCP_49 | resource | google_project_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
-| 345 | CKV_GCP_49 | resource | google_project_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
-| 346 | CKV_GCP_50 | resource | google_sql_database_instance | Ensure MySQL database 'local_infile' flag is set to 'off' | Terraform |
-| 347 | CKV_GCP_51 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_checkpoints' flag is set to 'on' | Terraform |
-| 348 | CKV_GCP_52 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_connections' flag is set to 'on' | Terraform |
-| 349 | CKV_GCP_53 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_disconnections' flag is set to 'on' | Terraform |
-| 350 | CKV_GCP_54 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_lock_waits' flag is set to 'on' | Terraform |
-| 351 | CKV_GCP_55 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_messages' flag is set to a valid value | Terraform |
-| 352 | CKV_GCP_56 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_temp_files flag is set to '0' | Terraform |
-| 353 | CKV_GCP_57 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_duration_statement' flag is set to '-1' | Terraform |
-| 354 | CKV_GCP_58 | resource | google_sql_database_instance | Ensure SQL database 'cross db ownership chaining' flag is set to 'off' | Terraform |
-| 355 | CKV_GCP_59 | resource | google_sql_database_instance | Ensure SQL database 'contained database authentication' flag is set to 'off' | Terraform |
-| 356 | CKV_GCP_60 | resource | google_sql_database_instance | Ensure SQL database do not have public IP | Terraform |
-| 357 | CKV_GIT_1 | resource | github_repository | Ensure Repository is Private | Terraform |
-| 358 | CKV_K8S_1 | PodSecurityPolicy | PodSecurityPolicy | Do not admit containers wishing to share the host process ID namespace | Kubernetes |
-| 359 | CKV_K8S_2 | PodSecurityPolicy | PodSecurityPolicy | Do not admit privileged containers | Kubernetes |
-| 360 | CKV_K8S_3 | PodSecurityPolicy | PodSecurityPolicy | Do not admit containers wishing to share the host IPC namespace | Kubernetes |
-| 361 | CKV_K8S_4 | PodSecurityPolicy | PodSecurityPolicy | Do not admit containers wishing to share the host network namespace | Kubernetes |
-| 362 | CKV_K8S_5 | PodSecurityPolicy | PodSecurityPolicy | Containers should not run with allowPrivilegeEscalation | Kubernetes |
-| 363 | CKV_K8S_6 | PodSecurityPolicy | PodSecurityPolicy | Do not admit root containers | Kubernetes |
-| 364 | CKV_K8S_7 | PodSecurityPolicy | PodSecurityPolicy | Do not admit containers with the NET_RAW capability | Kubernetes |
-| 365 | CKV_K8S_8 | PodSecurityPolicy | containers | Liveness Probe Should be Configured | Kubernetes |
-| 366 | CKV_K8S_9 | PodSecurityPolicy | containers | Readiness Probe Should be Configured | Kubernetes |
-| 367 | CKV_K8S_10 | PodSecurityPolicy | containers | CPU requests should be set | Kubernetes |
-| 368 | CKV_K8S_10 | PodSecurityPolicy | initContainers | CPU requests should be set | Kubernetes |
-| 369 | CKV_K8S_11 | PodSecurityPolicy | containers | CPU limits should be set | Kubernetes |
-| 370 | CKV_K8S_11 | PodSecurityPolicy | initContainers | CPU limits should be set | Kubernetes |
-| 371 | CKV_K8S_12 | PodSecurityPolicy | containers | Memory requests should be set | Kubernetes |
-| 372 | CKV_K8S_12 | PodSecurityPolicy | initContainers | Memory requests should be set | Kubernetes |
-| 373 | CKV_K8S_13 | PodSecurityPolicy | containers | Memory limits should be set | Kubernetes |
-| 374 | CKV_K8S_13 | PodSecurityPolicy | initContainers | Memory limits should be set | Kubernetes |
-| 375 | CKV_K8S_14 | PodSecurityPolicy | containers | Image Tag should be fixed - not latest or blank | Kubernetes |
-| 376 | CKV_K8S_14 | PodSecurityPolicy | initContainers | Image Tag should be fixed - not latest or blank | Kubernetes |
-| 377 | CKV_K8S_15 | PodSecurityPolicy | containers | Image Pull Policy should be Always | Kubernetes |
-| 378 | CKV_K8S_15 | PodSecurityPolicy | initContainers | Image Pull Policy should be Always | Kubernetes |
-| 379 | CKV_K8S_16 | PodSecurityPolicy | containers | Container should not be privileged | Kubernetes |
-| 380 | CKV_K8S_16 | PodSecurityPolicy | initContainers | Container should not be privileged | Kubernetes |
-| 381 | CKV_K8S_17 | PodSecurityPolicy | Pod | Containers should not share the host process ID namespace | Kubernetes |
-| 382 | CKV_K8S_17 | PodSecurityPolicy | Deployment | Containers should not share the host process ID namespace | Kubernetes |
-| 383 | CKV_K8S_17 | PodSecurityPolicy | DaemonSet | Containers should not share the host process ID namespace | Kubernetes |
-| 384 | CKV_K8S_17 | PodSecurityPolicy | StatefulSet | Containers should not share the host process ID namespace | Kubernetes |
-| 385 | CKV_K8S_17 | PodSecurityPolicy | ReplicaSet | Containers should not share the host process ID namespace | Kubernetes |
-| 386 | CKV_K8S_17 | PodSecurityPolicy | ReplicationController | Containers should not share the host process ID namespace | Kubernetes |
-| 387 | CKV_K8S_17 | PodSecurityPolicy | Job | Containers should not share the host process ID namespace | Kubernetes |
-| 388 | CKV_K8S_17 | PodSecurityPolicy | CronJob | Containers should not share the host process ID namespace | Kubernetes |
-| 389 | CKV_K8S_18 | PodSecurityPolicy | Pod | Containers should not share the host IPC namespace | Kubernetes |
-| 390 | CKV_K8S_18 | PodSecurityPolicy | Deployment | Containers should not share the host IPC namespace | Kubernetes |
-| 391 | CKV_K8S_18 | PodSecurityPolicy | DaemonSet | Containers should not share the host IPC namespace | Kubernetes |
-| 392 | CKV_K8S_18 | PodSecurityPolicy | StatefulSet | Containers should not share the host IPC namespace | Kubernetes |
-| 393 | CKV_K8S_18 | PodSecurityPolicy | ReplicaSet | Containers should not share the host IPC namespace | Kubernetes |
-| 394 | CKV_K8S_18 | PodSecurityPolicy | ReplicationController | Containers should not share the host IPC namespace | Kubernetes |
-| 395 | CKV_K8S_18 | PodSecurityPolicy | Job | Containers should not share the host IPC namespace | Kubernetes |
-| 396 | CKV_K8S_18 | PodSecurityPolicy | CronJob | Containers should not share the host IPC namespace | Kubernetes |
-| 397 | CKV_K8S_19 | PodSecurityPolicy | Pod | Containers should not share the host network namespace | Kubernetes |
-| 398 | CKV_K8S_19 | PodSecurityPolicy | Deployment | Containers should not share the host network namespace | Kubernetes |
-| 399 | CKV_K8S_19 | PodSecurityPolicy | DaemonSet | Containers should not share the host network namespace | Kubernetes |
-| 400 | CKV_K8S_19 | PodSecurityPolicy | StatefulSet | Containers should not share the host network namespace | Kubernetes |
-| 401 | CKV_K8S_19 | PodSecurityPolicy | ReplicaSet | Containers should not share the host network namespace | Kubernetes |
-| 402 | CKV_K8S_19 | PodSecurityPolicy | ReplicationController | Containers should not share the host network namespace | Kubernetes |
-| 403 | CKV_K8S_19 | PodSecurityPolicy | Job | Containers should not share the host network namespace | Kubernetes |
-| 404 | CKV_K8S_19 | PodSecurityPolicy | CronJob | Containers should not share the host network namespace | Kubernetes |
-| 405 | CKV_K8S_20 | PodSecurityPolicy | containers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
-| 406 | CKV_K8S_20 | PodSecurityPolicy | initContainers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
-| 407 | CKV_K8S_21 | PodSecurityPolicy | Pod | The default namespace should not be used | Kubernetes |
-| 408 | CKV_K8S_21 | PodSecurityPolicy | Deployment | The default namespace should not be used | Kubernetes |
-| 409 | CKV_K8S_21 | PodSecurityPolicy | DaemonSet | The default namespace should not be used | Kubernetes |
-| 410 | CKV_K8S_21 | PodSecurityPolicy | StatefulSet | The default namespace should not be used | Kubernetes |
-| 411 | CKV_K8S_21 | PodSecurityPolicy | ReplicaSet | The default namespace should not be used | Kubernetes |
-| 412 | CKV_K8S_21 | PodSecurityPolicy | ReplicationController | The default namespace should not be used | Kubernetes |
-| 413 | CKV_K8S_21 | PodSecurityPolicy | Job | The default namespace should not be used | Kubernetes |
-| 414 | CKV_K8S_21 | PodSecurityPolicy | CronJob | The default namespace should not be used | Kubernetes |
-| 415 | CKV_K8S_21 | PodSecurityPolicy | RoleBinding | The default namespace should not be used | Kubernetes |
-| 416 | CKV_K8S_21 | PodSecurityPolicy | Service | The default namespace should not be used | Kubernetes |
-| 417 | CKV_K8S_21 | PodSecurityPolicy | Secret | The default namespace should not be used | Kubernetes |
-| 418 | CKV_K8S_21 | PodSecurityPolicy | ServiceAccount | The default namespace should not be used | Kubernetes |
-| 419 | CKV_K8S_21 | PodSecurityPolicy | Role | The default namespace should not be used | Kubernetes |
-| 420 | CKV_K8S_21 | PodSecurityPolicy | ConfigMap | The default namespace should not be used | Kubernetes |
-| 421 | CKV_K8S_21 | PodSecurityPolicy | Ingress | The default namespace should not be used | Kubernetes |
-| 422 | CKV_K8S_22 | PodSecurityPolicy | containers | Use read-only filesystem for containers where possible | Kubernetes |
-| 423 | CKV_K8S_22 | PodSecurityPolicy | initContainers | Use read-only filesystem for containers where possible | Kubernetes |
-| 424 | CKV_K8S_23 | PodSecurityPolicy | Pod | Minimize the admission of root containers | Kubernetes |
-| 425 | CKV_K8S_23 | PodSecurityPolicy | Deployment | Minimize the admission of root containers | Kubernetes |
-| 426 | CKV_K8S_23 | PodSecurityPolicy | DaemonSet | Minimize the admission of root containers | Kubernetes |
-| 427 | CKV_K8S_23 | PodSecurityPolicy | StatefulSet | Minimize the admission of root containers | Kubernetes |
-| 428 | CKV_K8S_23 | PodSecurityPolicy | ReplicaSet | Minimize the admission of root containers | Kubernetes |
-| 429 | CKV_K8S_23 | PodSecurityPolicy | ReplicationController | Minimize the admission of root containers | Kubernetes |
-| 430 | CKV_K8S_23 | PodSecurityPolicy | Job | Minimize the admission of root containers | Kubernetes |
-| 431 | CKV_K8S_23 | PodSecurityPolicy | CronJob | Minimize the admission of root containers | Kubernetes |
-| 432 | CKV_K8S_24 | PodSecurityPolicy | PodSecurityPolicy | Do not allow containers with added capability | Kubernetes |
-| 433 | CKV_K8S_25 | PodSecurityPolicy | containers | Minimize the admission of containers with added capability | Kubernetes |
-| 434 | CKV_K8S_25 | PodSecurityPolicy | initContainers | Minimize the admission of containers with added capability | Kubernetes |
-| 435 | CKV_K8S_26 | PodSecurityPolicy | containers | Do not specify hostPort unless absolutely necessary | Kubernetes |
-| 436 | CKV_K8S_26 | PodSecurityPolicy | initContainers | Do not specify hostPort unless absolutely necessary | Kubernetes |
-| 437 | CKV_K8S_27 | PodSecurityPolicy | Pod | Do not expose the docker daemon socket to containers | Kubernetes |
-| 438 | CKV_K8S_27 | PodSecurityPolicy | Deployment | Do not expose the docker daemon socket to containers | Kubernetes |
-| 439 | CKV_K8S_27 | PodSecurityPolicy | DaemonSet | Do not expose the docker daemon socket to containers | Kubernetes |
-| 440 | CKV_K8S_27 | PodSecurityPolicy | StatefulSet | Do not expose the docker daemon socket to containers | Kubernetes |
-| 441 | CKV_K8S_27 | PodSecurityPolicy | ReplicaSet | Do not expose the docker daemon socket to containers | Kubernetes |
-| 442 | CKV_K8S_27 | PodSecurityPolicy | ReplicationController | Do not expose the docker daemon socket to containers | Kubernetes |
-| 443 | CKV_K8S_27 | PodSecurityPolicy | Job | Do not expose the docker daemon socket to containers | Kubernetes |
-| 444 | CKV_K8S_27 | PodSecurityPolicy | CronJob | Do not expose the docker daemon socket to containers | Kubernetes |
-| 445 | CKV_K8S_28 | PodSecurityPolicy | containers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
-| 446 | CKV_K8S_28 | PodSecurityPolicy | initContainers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
-| 447 | CKV_K8S_29 | PodSecurityPolicy | Pod | Apply security context to your pods and containers | Kubernetes |
-| 448 | CKV_K8S_29 | PodSecurityPolicy | Deployment | Apply security context to your pods and containers | Kubernetes |
-| 449 | CKV_K8S_29 | PodSecurityPolicy | DaemonSet | Apply security context to your pods and containers | Kubernetes |
-| 450 | CKV_K8S_29 | PodSecurityPolicy | StatefulSet | Apply security context to your pods and containers | Kubernetes |
-| 451 | CKV_K8S_29 | PodSecurityPolicy | ReplicaSet | Apply security context to your pods and containers | Kubernetes |
-| 452 | CKV_K8S_29 | PodSecurityPolicy | ReplicationController | Apply security context to your pods and containers | Kubernetes |
-| 453 | CKV_K8S_29 | PodSecurityPolicy | Job | Apply security context to your pods and containers | Kubernetes |
-| 454 | CKV_K8S_29 | PodSecurityPolicy | CronJob | Apply security context to your pods and containers | Kubernetes |
-| 455 | CKV_K8S_30 | PodSecurityPolicy | containers | Apply security context to your pods and containers | Kubernetes |
-| 456 | CKV_K8S_30 | PodSecurityPolicy | initContainers | Apply security context to your pods and containers | Kubernetes |
-| 457 | CKV_K8S_31 | PodSecurityPolicy | Pod | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 458 | CKV_K8S_31 | PodSecurityPolicy | Deployment | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 459 | CKV_K8S_31 | PodSecurityPolicy | DaemonSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 460 | CKV_K8S_31 | PodSecurityPolicy | StatefulSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 461 | CKV_K8S_31 | PodSecurityPolicy | ReplicaSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 462 | CKV_K8S_31 | PodSecurityPolicy | ReplicationController | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 463 | CKV_K8S_31 | PodSecurityPolicy | Job | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 464 | CKV_K8S_31 | PodSecurityPolicy | CronJob | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
-| 465 | CKV_K8S_32 | PodSecurityPolicy | PodSecurityPolicy | Ensure default seccomp profile set to docker/default or runtime/default | Kubernetes |
-| 466 | CKV_K8S_33 | PodSecurityPolicy | containers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
-| 467 | CKV_K8S_33 | PodSecurityPolicy | initContainers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
-| 468 | CKV_K8S_34 | PodSecurityPolicy | containers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
-| 469 | CKV_K8S_34 | PodSecurityPolicy | initContainers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
-| 470 | CKV_K8S_35 | PodSecurityPolicy | containers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
-| 471 | CKV_K8S_35 | PodSecurityPolicy | initContainers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
-| 472 | CKV_K8S_36 | PodSecurityPolicy | PodSecurityPolicy | Minimize the admission of containers with capabilities assigned | Kubernetes |
-| 473 | CKV_K8S_37 | PodSecurityPolicy | containers | Minimize the admission of containers with capabilities assigned | Kubernetes |
-| 474 | CKV_K8S_37 | PodSecurityPolicy | initContainers | Minimize the admission of containers with capabilities assigned | Kubernetes |
-| 475 | CKV_K8S_38 | PodSecurityPolicy | Pod | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 476 | CKV_K8S_38 | PodSecurityPolicy | Deployment | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 477 | CKV_K8S_38 | PodSecurityPolicy | DaemonSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 478 | CKV_K8S_38 | PodSecurityPolicy | StatefulSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 479 | CKV_K8S_38 | PodSecurityPolicy | ReplicaSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 480 | CKV_K8S_38 | PodSecurityPolicy | ReplicationController | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 481 | CKV_K8S_38 | PodSecurityPolicy | Job | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 482 | CKV_K8S_38 | PodSecurityPolicy | CronJob | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
-| 483 | CKV_K8S_39 | PodSecurityPolicy | containers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
-| 484 | CKV_K8S_39 | PodSecurityPolicy | initContainers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
-| 485 | CKV_K8S_40 | PodSecurityPolicy | Pod | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 486 | CKV_K8S_40 | PodSecurityPolicy | Deployment | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 487 | CKV_K8S_40 | PodSecurityPolicy | DaemonSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 488 | CKV_K8S_40 | PodSecurityPolicy | StatefulSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 489 | CKV_K8S_40 | PodSecurityPolicy | ReplicaSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 490 | CKV_K8S_40 | PodSecurityPolicy | ReplicationController | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 491 | CKV_K8S_40 | PodSecurityPolicy | Job | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 492 | CKV_K8S_40 | PodSecurityPolicy | CronJob | Containers should run as a high UID to avoid host conflict | Kubernetes |
-| 493 | CKV_K8S_41 | PodSecurityPolicy | ServiceAccount | Ensure that default service accounts are not actively used | Kubernetes |
-| 494 | CKV_K8S_42 | PodSecurityPolicy | RoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
-| 495 | CKV_K8S_42 | PodSecurityPolicy | ClusterRoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
-| 496 | CKV_K8S_43 | PodSecurityPolicy | containers | Image should use digest | Kubernetes |
-| 497 | CKV_K8S_43 | PodSecurityPolicy | initContainers | Image should use digest | Kubernetes |
-| 498 | CKV_K8S_44 | PodSecurityPolicy | Service | Ensure that the Tiller Service (Helm v2) is deleted | Kubernetes |
-| 499 | CKV_K8S_45 | PodSecurityPolicy | containers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
-| 500 | CKV_K8S_45 | PodSecurityPolicy | initContainers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
-| 501 | CKV_LIN_1 | provider | linode | Ensure no hard coded Linode tokens exist in provider | Terraform |
-| 502 | CKV_LIN_2 | resource | linode_instance | Ensure SSH key set in authorized_keys | Terraform |
-
-
----
-
-
diff --git a/docs/4.Integrations/bitbucket-cloud.md b/docs/4.Integrations/Bitbucket Cloud Pipelines.md
similarity index 92%
rename from docs/4.Integrations/bitbucket-cloud.md
rename to docs/4.Integrations/Bitbucket Cloud Pipelines.md
index 8296f351a8..632ed63f0c 100644
--- a/docs/4.Integrations/bitbucket-cloud.md
+++ b/docs/4.Integrations/Bitbucket Cloud Pipelines.md
@@ -1,3 +1,10 @@
+---
+layout: default
+published: true
+title: Bitbucket Cloud Pipelines
+nav_order: 2
+---
+
# Integrate Checkov with Bitbucket Cloud Pipelines
You can integrate checkov into your Bitbucket Cloud pipelines. This provides a simple, automatic way of applying policies to your Terraform code both during merge request review and as part of your build process.
@@ -44,4 +51,4 @@ Once I have corrected the configuration, checkov verifies that all is well.
## Further Reading
-See the [Bitbucket pipelines documentation](https://confluence.atlassian.com/bitbucket/build-test-and-deploy-with-pipelines-792496469.html) for additional information.
\ No newline at end of file
+See the [Bitbucket pipelines documentation](https://confluence.atlassian.com/bitbucket/build-test-and-deploy-with-pipelines-792496469.html) for additional information.
diff --git a/docs/4.Integrations/Docker.md b/docs/4.Integrations/Docker.md
new file mode 100644
index 0000000000..c52fa568f5
--- /dev/null
+++ b/docs/4.Integrations/Docker.md
@@ -0,0 +1,15 @@
+---
+layout: default
+published: true
+title: Docker
+nav_order: 7
+---
+
+# Using Checkov with Docker
+
+```coffeescript
+docker pull bridgecrew/checkov
+docker run --tty --volume /user/tf:/tf bridgecrew/checkov --directory /tf
+```
+
+If you are using Python 3.6 (which is the default version in Ubuntu 18.04) Checkov will not work and it will fail with `ModuleNotFoundError: No module named 'dataclasses'`. In this case, you can use the Docker version instead.\n\nIn certain cases, when redirecting `docker run --tty` output to a file - for example, if you want to save the Checkov JUnit output to a file - will cause extra control characters to be printed. This can break file parsing. If you encounter this, remove the --tty flag.
diff --git a/docs/4.Integrations/GitHub Actions.md b/docs/4.Integrations/GitHub Actions.md
new file mode 100644
index 0000000000..edfb72ff5c
--- /dev/null
+++ b/docs/4.Integrations/GitHub Actions.md
@@ -0,0 +1,85 @@
+---
+layout: default
+published: true
+title: Github Actions
+nav_order: 3
+---
+
+# Integrate Checkov with Github Actions
+
+Integrating Checkov into GitHub Actions provides a simple, automatic way of applying policies to your Terraform code both during pull request review and as part of any build process.
+
+## Use a Checkov Action from the Marketplace
+
+Check out our [pre-made action](https://github.com/bridgecrewio/checkov-action).
+
+## Create Your Own Action: Basic Set-up
+
+Add a new step in the `workflow.yml`.
+
+```tree
+├───.github
+│ └───workflows
+```
+
+Here is a basic example:
+
+```yaml
+---
+name: Checkov
+on:
+ push:
+ branches:
+ - master
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.8
+ - name: Test with Checkov
+ id: checkov
+ uses: bridgecrewio/checkov-action@master
+ with:
+ directory: example/examplea
+ framework: terraform
+```
+
+## Example Results
+
+Any time after you push your code to GitHub, it will run your job. If Checkov finds any errors, it will fail the build.
+
+### Action Failure
+
+In the original examples code, the file **aws_efs_file_system.sharedstore.tf** is not set to encrypted:
+
+```python
+resource "aws_efs_file_system" "sharedstore" {
+ creation_token = var.efs["creation_token"]
+
+ lifecycle_policy {
+ transition_to_ia = var.efs["transition_to_ia"]
+ }
+
+ kms_key_id = var.efs["kms_key_id"]
+ encrypted = false
+ performance_mode = var.efs["performance_mode"]
+ provisioned_throughput_in_mibps = var.efs["provisioned_throughput_in_mibps"]
+ throughput_mode = var.efs["throughput_mode"]
+}
+```
+
+This will fail a Checkov test:
+
+
+
+### Pipeline Success
+
+The previous error can be fixed by setting the value of encryption to **true**.
+
+
+[Read more details on using Python in GitHub Actions.](https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions)
diff --git a/docs/4.Integrations/gitlab.md b/docs/4.Integrations/GitLab CLI.md
similarity index 60%
rename from docs/4.Integrations/gitlab.md
rename to docs/4.Integrations/GitLab CLI.md
index e3f3ecd9f8..a312f6b210 100644
--- a/docs/4.Integrations/gitlab.md
+++ b/docs/4.Integrations/GitLab CLI.md
@@ -1,21 +1,26 @@
+---
+layout: default
+published: true
+title: GitLab CI
+nav_order: 4
+---
+
# Integrate Checkov with GitLab CI
-You can integrate checkov into your GitLab CI pipelines. This provides a simple, automatic way of applying policies to your Terraform code both during merge request review and as part of your build process.
+Integrating Checkov into your GitLab CI pipelines provides a simple, automatic way of applying policies to your Terraform code both during merge request review and as part of your build process.
## Basic Setup
+Add a new job in `.gitlab-ci.yml` in your repository (at whatever stage is appropriate for you).
-Add a new job in the `.gitlab-ci.yml` file in your repository as part of whichever stage is appropriate for you.
+Here is a basic example:
-Here is a minimalistic example:
```yaml
stages:
- test
-variables:
- ALLOWFAILURE: true #True for AutoDevOps compatibility
-
+
checkov:
stage: test
- allow_failure: $ALLOWFAILURE
+ allow_failure: true #True for AutoDevOps compatibility
image:
name: bridgecrew/checkov:latest
entrypoint:
@@ -43,37 +48,32 @@ checkov:
```
## Example Results
-
-When your pipeline executes, it will run this job. If checkov finds any issues, it will fail the build.
+When your pipeline executes, it will run this job. If Checkov finds any issues, it will fail the build.
### Pipeline Failure
-
For example, I have an S3 bucket that does not have versioning enabled. Checkov detects this and fails the job and pipeline.
-
+[](gitlab_failed_job.png)
This will comment on an associated merge request or fail the build depending on the context.
GitLab will collect the results into the normal unit testing area of the pipeline and/or the merge request.
### Pipeline Success
+Once you correct the configuration, Checkov verifies that no errors have been found.
-Once I have corrected the configuration, checkov verifies that all is well.
-
-
+[](gitlab_results.png)
## Colored Output
+Note that in the examples above, the output of the test results does not display colors. This is because GitLab Runner runs without an interactive TTY. Although Checkov does not currently support an environment variable to force colored output, the `script` command can be used to emulate `tty` so colors are displayed:
-Note that in the above examples the output of the test results does not display colors. This is because GitLab Runner runs without an interactive TTY. Although checkov does not currently support an environment variable to force colored output, the `script` command can be used to emulate `tty` so colors are displayed:
```yaml
stages:
- test
-variables:
- ALLOWFAILURE: true #True for AutoDevOps compatibility
checkov:
stage: test
- allow_failure: $ALLOWFAILURE
+ allow_failure: true #True for AutoDevOps compatibility
image:
name: bridgecrew/checkov:latest
entrypoint:
@@ -102,7 +102,5 @@ checkov:
- "checkov.test.xml"
```
-## Further Reading
-
See the [GitLab CI documentation](https://docs.gitlab.com/ee/ci/) for additional information.
-The there is also a working example of using GitLab CI with Checkov here: https://gitlab.com/guided-explorations/ci-cd-plugin-extensions/checkov-iac-sast - this example also shows how to use the same checkov yaml as an includable extension so that all your jobs reuse the same job definition.
+The there is also a working example of using GitLab CI with Checkov [here](https://gitlab.com/guided-explorations/ci-cd-plugin-extensions/checkov-iac-sast). This example shows how to use the same Checkov YAML file as an includable extension so that all your jobs reuse the same job definition.
diff --git a/docs/4.Integrations/Jenkins.md b/docs/4.Integrations/Jenkins.md
index 2f99676872..ef4864e1ac 100644
--- a/docs/4.Integrations/Jenkins.md
+++ b/docs/4.Integrations/Jenkins.md
@@ -1,12 +1,13 @@
-# Integrate Checkov with Jenkins
-
-## Background
-
-Checkov was built to help developers avoid common misconfigurations as they build robust infrastructure-as-code. This simple integration into Jenkins will result in build failures whenever developers create and modify infrastructure as code monitored by Checkov.
-
-To prevent developer frustration from failed builds, we recommend training and encouraging usage of Checkov's inline suppressions.
+---
+layout: default
+published: true
+title: Jenkins
+nav_order: 1
+---
+# Integrate Checkov with Jenkins
+This simple integration into Jenkins will result in build failures whenever developers create and modify infrastructure as code monitored by Checkov. To prevent developer frustration from failed builds, we recommend training and encouraging usage of Checkov's inline suppressions.
## Tutorial
diff --git a/docs/4.Integrations/kubernetes.md b/docs/4.Integrations/Kubernetes.md
similarity index 57%
rename from docs/4.Integrations/kubernetes.md
rename to docs/4.Integrations/Kubernetes.md
index 33d2864ac1..0252b97eb9 100644
--- a/docs/4.Integrations/kubernetes.md
+++ b/docs/4.Integrations/Kubernetes.md
@@ -1,24 +1,29 @@
-# Integrate Checkov with Kubernetes
+---
+layout: default
+published: true
+title: Kubernetes
+nav_order: 5
+---
-## Background
+# Integrate Checkov with Kubernetes
Checkov is built to scan static code and is typically used at build time. However, resources running in a Kubernetes cluster
-can be described in the same way as at build time. This allows Checkov to run in a cluster with read-only access and report
- on the same violations.
+can be described in the same way as at build-time. This allows Checkov to run in a cluster with read-only access and report
+on the same violations.
## Execution
-To run Checkov in your cluster you must have Kubernetes CLI access to the cluster.
+To run Checkov in your cluster, you must have Kubernetes CLI access to the cluster.
-To execute a job against your cluster, run the following manifest.
+To execute a job against your cluster, run the following manifest:
-```$bash
+```bash
kubectl apply -f https://raw.githubusercontent.com/bridgecrewio/checkov/master/kubernetes/checkov-job.yaml
```
-Review the output of the job.
+Review the output of the job:
-```$bash
+```bash
kubectl get jobs -n checkov
kubectl logs job/checkov -n checkov
```
diff --git a/docs/4.Integrations/Terraform Scanning.md b/docs/4.Integrations/Terraform Scanning.md
new file mode 100644
index 0000000000..be1418ab72
--- /dev/null
+++ b/docs/4.Integrations/Terraform Scanning.md
@@ -0,0 +1,53 @@
+---
+layout: default
+published: true
+title: Terraform Scanning
+nav_order: 8
+---
+
+# Terraform Plan and External Terraform Module Scanning
+
+## Evaluate Checkov Policies on Terraform Plan
+Checkov supports the evaluation of policies on resources declared in `.tf` files. It can also be used to evaluate `terraform plan` expressed in a json file. Plan evaluation provides Checkov additional dependencies and context that can result in a more complete scan result. Since Terraform plan files may contain arguments (like secrets) that are injected dynamically, it is advised to run a plan evaluation using Checkov in a secure CI/CD pipeline setting.
+
+### Example
+
+```json
+terraform init
+terraform plan --out tfplan.binary
+terraform show -json tfplan.binary > tfplan.json
+
+checkov -f tfplan.json
+```
+
+The output would look like:
+
+
+## Scanning Third-Party Terraform Modules
+Third-party Terraform modules often reduce complexity for deploying services made up of many objects.
+
+For example, the third-party EKS module by howdio reduces the terraform required to the nine lines below, however, in doing so abstracts the terraform configuration away from a regular Checkov scan on the current directory.
+
+```python
+module "eks" {
+ source = "howdio/eks/aws"
+
+ name = "examplecluster"
+ default_vpc = true
+
+ enable_kubectl = true
+ enable_dashboard = true
+}
+```
+
+To ensure coverage of objects within these modules, you can instruct Checkov to scan the `.terraform` directory, after a `terraform init`, which will have retrieved the third-party modules and any associated `.tf` files:
+
+```python
+terraform init
+checkov -d . # Your TF files.
+checkov -d .terraform # Module TF files.
+```
+
+
+
+It is worth noting however, that when scanning the `.terraform` directory, Checkov cannot differentiate between third-party and internally written modules. That said, you will benefit from scanning coverage across all of them.
diff --git a/docs/4.Integrations/bc-api-integration.png b/docs/4.Integrations/bc-api-integration.png
deleted file mode 100644
index 00576ca537..0000000000
Binary files a/docs/4.Integrations/bc-api-integration.png and /dev/null differ
diff --git a/docs/4.Integrations/bridgecrew-cloud.md b/docs/4.Integrations/bridgecrew-cloud.md
deleted file mode 100644
index 72ccc5f2aa..0000000000
--- a/docs/4.Integrations/bridgecrew-cloud.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# Integrate Checkov with Bridgecrew Cloud
-You can integrate checkov with Bridgecrew's platform. This allows you to include checkov's scan results of a repository
-into your Bridgecrew account.
-
-## Setup
-First, you need to acquire a Bridgecrew issued API token. To do so, follow these steps:
-- Register (for free) to Bridgecrew's platform at [bridgecrew.cloud](https://www.bridgecrew.cloud/)
-- After signing in, navigate to the [integrations page](https://www.bridgecrew.cloud/integrations), and click the API Token integration:
-
-- Acquire the issued API key (under the `Bridgecrew Token` title) for execution
-
-## Execution
-After acquiring the issued API key, run checkov as follows:
-
-- `checkov -d --bc-api-key --repo-id --branch `
-
-Or by using the `-f` file flag:
-- `checkov -f ... --bc-api-key --repo-id --branch `
-
-### Arguments:
-- `` - Bridgecrew issued API key
-- `` - Identifying string of the scanned repository, following the standard Git repository naming scheme: `/`
-- `` - Branch name to be persisted on platform, defaults to the master branch. NOTE: please make sure the scanned directory (supplied with `-d` flag)
-is currently checked out from the given branch name.
-
-## Bridgecrew cloud view
-After successfully terminating, the scan results are persisted on [Bridgecrew Cloud](https://www.bridgecrew.cloud), and are available as possible violations
-that can be seen
-in the [incidents view](https://www.bridgecrew.cloud/incidents):
-
-
-## Example usage
-The following command scans the repository identified as `foo/bar`, on branch `develop`, using a Bridgecrew API key:
-`checkov -d . --bc-api-key 84b8f259-a3dv-5c1e-9422-1bdc9aec0487 --repo-id foo/bar --branch develop`
diff --git a/docs/4.Integrations/github-actions.md b/docs/4.Integrations/github-actions.md
deleted file mode 100644
index 75691d6804..0000000000
--- a/docs/4.Integrations/github-actions.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Integrate Checkov with Github Actions
-
-You can integrate Checkov into Github Actions. This provides a simple, automatic way of applying policies to your Terraform code both during merge request review and as part of any build process.
-
-## Use a checkov action from the marketplace:
-go to https://github.com/bridgecrewio/checkov-action and use a pre-made action!
-
-## Create your own action: Basic Set-up
-
-Add a new step in the `workflow.yml`.
-
-```tree
-├───.github
-│ └───workflows
-```
-
-Here is a basic example:
-
-```yaml
----
-name: Checkov
-on:
- push:
- branches:
- - master
-jobs:
- build:
-
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: [3.7]
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1
- with:
- python-version: ${{ matrix.python-version }}
- - name: Test with Checkov
- run: |
- pip install checkov
- checkov -d .
-```
-
-## Example Results
-
-Any time after you push your code to Github, it will run this job. If Checkov finds any issues, it will fail the build.
-
-### Action Failure
-
-In the original examples code, the file **aws_efs_file_system.sharedstore.tf**
-
-```terraform
-resource "aws_efs_file_system" "sharedstore" {
- creation_token = var.efs["creation_token"]
-
- lifecycle_policy {
- transition_to_ia = var.efs["transition_to_ia"]
- }
-
- kms_key_id = var.efs["kms_key_id"]
- encrypted = false
- performance_mode = var.efs["performance_mode"]
- provisioned_throughput_in_mibps = var.efs["provisioned_throughput_in_mibps"]
- throughput_mode = var.efs["throughput_mode"]
-}
-```
-
-Is not set to be encrypted. This will fail a Checkov test:
-
-
-
-### Pipeline Success
-
-The previous error can be fixed by setting the value of encyption to **true**.
-
-
-## Further Reading
-
-For more details on using Python in Github Actions .
-
-The test code sample:
\ No newline at end of file
diff --git a/docs/4.Integrations/pre-commit.md b/docs/4.Integrations/pre-commit.md
index 341cf070de..d7520264e3 100644
--- a/docs/4.Integrations/pre-commit.md
+++ b/docs/4.Integrations/pre-commit.md
@@ -1,8 +1,13 @@
-# Integrate Checkov with pre-commit
+---
+layout: default
+published: true
+title: Pre-Commit
+nav_order: 6
+---
-## Pre-commit Setup
+# Pre-Commit
-To use checkov with [pre-commit](https://pre-commit.com), just add the following to your local repo's `.pre-commit-config.yaml` file. Make sure to change rev: to be either a git commit sha or tag of checkov containing `.pre-commit-hooks.yaml`.
+To use Checkov with [pre-commit](https://pre-commit.com), just add the following to your local repo's `.pre-commit-config.yaml` file:
```yaml
- repo: https://github.com/bridgecrewio/checkov.git
@@ -11,9 +16,11 @@ To use checkov with [pre-commit](https://pre-commit.com), just add the following
- id: checkov
```
-## How to add custom parameters
+Make sure to change `rev:` to be either a git commit sha or tag of checkov containing `.pre-commit-hooks.yaml`.
-You can provide arguments to `checkov` using the args property. For example, the following will print checkov output, and proceed regardless of success/failure to the next pre-commit check.
+## Adding Custom Parameters
+
+You can use the `args` property to input arguments to Checkov. In the example below, Checkov output will be printed, and then Checkov will proceed to the next pre-commit check *regardless of success/failure*.
```yaml
repos:
diff --git a/docs/404.md b/docs/404.md
new file mode 100644
index 0000000000..fccfd74469
--- /dev/null
+++ b/docs/404.md
@@ -0,0 +1,5 @@
+---
+permalink: /404.html
+---
+
+Page not found :(
\ No newline at end of file
diff --git a/docs/5.Contribution/New-Check.md b/docs/5.Contribution/New-Check.md
deleted file mode 100644
index 8b17317665..0000000000
--- a/docs/5.Contribution/New-Check.md
+++ /dev/null
@@ -1,126 +0,0 @@
-# Contributing a new check
-
-## Background
-
-Checkov contributors are encouraged to contribute new checks to help increase our existing coverage of infrastructure-as-code.
-
-In our documentation, a check is sometimes referred loosely also as a Policy. We expect to solve a real-world hardening, assessment, auditing or forensic gap you encountered. In other words, a new check should reflect a policy you think should be globally accepted when provisioning and changing infrastructure.
-
-This guide covers all the necessary stages required to building and contributing a new check, which are:
-
-1. Prerequisites
-2. Implementation
-3. Testing
-4. Pull Request
-
-# Video guide
-
-
-
-# Scripted guide
-## Contribution Stages
-1. Prerequisites
- * Install Checkov as described in the [Installation](#installation) subsection.
- * Read about check's structure and functionality in the [Prerequisites](#prerequisites) section.
- * Identify the check's `type` and `provider`, as described [here](#check-structure).
- * If available, provide the IaC configuration documentation that relates to the check, as described [here](#review-iac-configuration-documentation).
- * Provide an example Terraform or CloudFormation configuration file, as described [here](#example-Terraform-configuration).
-2. Implementation
- * Implement the check as described in the [Implementation](#implementation) section.
-3. Testing
- * Provide a unit test suite of the check as described in the [Testing](#testing) section.
-4. Pull Request
- * Open a PR that contains the implementation code and testing suite, with the following information:
- * Check `id`
- * Check `name`
- * check's IaC type
- * Check type and provider
- * IaC configuration documentation (If available)
- * Example Terraform configuration file
- * Any additional information that would help other members to better understand the check
-
-## Prerequisites
-
-### Installation
-
-First, make sure you installed and configured Checkov correctly. If you are unsure, go back and read the [Getting Started](../1.Introduction/Getting%20Started.html).
-
-Preferably by now you have either scanned a folder containing Terraform state-files or went ahead and integrated Checkov as part of your CI/CD pipeline.
-
-### Check structure
-
-Each check consists of the following mandatory properties:
-
-``name`` : A new check's unique purpose. It should ideally specify the positive desired outcome of the policy.
-
-``id``: A mandatory unique identifier of a policy. Policies written by Checkov maintainers follow the following convention ``CKV_providerType_serialNumber``. (e.g. `CKV_AWS_9` , `CKV_GCP_12`)
-
-``categories``: A categorization of a scan. This is usually helpful when producing compliance reports, pipeline analytics and health metrics. Check out our existing categories before creating a new one.
-
-When contributing a new check, please increment the `id`'s serial number to be `x+1`, where `x` is the serial number of the latest implemented check, with respect to the check's provider.
-
-A more specific type of check may also include additional attributes. For example, a check that scans a Terraform resource configuration also contains the `supported_resources` attribute, which is a list of the supported resource types of the check.
-
-### Check result
-
-The result of a scan should be a binary result of either `PASSED` or `FAILED`. We have also included an `UNKNOWN` option, which means that it is unknown if the scanned configuration complied with the check. If your check could have edge cases that might not be supported by the scanner's current logic, consider support the `UNKNOWN` option.
-
-Additionally, a check can be suppressed by Checkov on a given configuration by inserting a skip comment inside a specific configuration scope. Then, the check's result on the suppressed configuration would be `SKIPPED`.
-Read more about Checkov's [Suppressions](../3.Scans/resource-scans.md) for further details.
-
-## IaC type scanner
-Identify which IaC type would be tested under the check. currently, Checkov scans either Terraform or CloudFormation configuration files.
-Place your following code under `checkov/` folder, where `` is either `terraform` or `cloudformation`.
-
-### Check type and provider
-
-Checks are divided first to folders grouped by their type, and are after divided by their provider.
-
-Checks should relate to a common IaC configuration type of a specific public cloud provider.
-For example, a check that validates the encryption configuration of an S3 bucket is considered to be of type `resource`, and of `aws` provider.
-
-Identify the type and provider of the new check in order to place it correctly under the project structure.
-For example, the mentioned above check is already implemented in Checkov under `checkov/terraform/checks/resource/aws/S3Encryption.py`.
-
-Notice that checks are divided first into folders grouped by their type, and are after that divided by their provider.
-
-### Review IaC configuration documentation
-
-If available, please provide the official [Terraform](https://www.terraform.io/docs) or [CloudFormation](https://docs.aws.amazon.com/cloudformation/) documentation of the checked configuration. This helps users to better understand the check's scanned configuration and it's usage.
-
-For example, the mentioned above check's configuration documentation can be found [here](https://www.terraform.io/docs/providers/aws/r/s3_bucket.html)
-
-### Example IaC configuration
-
-In order to develop the check, a relevant example configuration should be presented as an input to Checkov.
-Provide an example configuration (e.g. `example.tf, template.json`) that contains both passing and failing configurations with respect to
-the check's logic.
-The file can be served as an input to the appropriate check's unit tests.
-
-
-
-## Implementation
-
-After identifying the check's IaC type and provider, place the file containing it's code inside `checkov//checks//`, where `` is the check's type and `` is the check's provider.
-
-A check is a class implementing an `abstract` base check class that corresponds to some provider and type.
-
-For example, all checks of `resource` type and `aws` provider are implementing the resource base check class found at
-`checkov/terraform/checks/resource/base_check.py`. The resource check needs to implement it's base check's abstract method named
-`scan_resource_conf`, which accepts as an input a dictionary of all the key-valued resource attributes, and outputs a `CheckResult`.
-
-For a full implementation example of a check, please refer the [Policies documentation](../1.Introduction/Policies.md).
-
-
-## Testing
-
-Assuming the implemented check's class is file is found in `checkov/terraform/checks//` directory, named `.py`, create an appropriate unit test file in `tests/terraform/checks//` directory, named `test_.py`.
-
-The test suite should cover different check results; Test if the check outputs `PASSED` on a compliant configuration,
-and test if it output `FAILED` on a non-compliant configuration. You are also encouraged to test more specific
-components of the check, according to their complexity.
-
diff --git a/docs/5.Policy Index/all.md b/docs/5.Policy Index/all.md
new file mode 100644
index 0000000000..18755ddb7f
--- /dev/null
+++ b/docs/5.Policy Index/all.md
@@ -0,0 +1,1204 @@
+---
+layout: default
+title: all resource scans
+nav_order: 1
+---
+
+# all resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|------|---------------|----------------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
+| 0 | CKV_AWS_1 | data | aws_iam_policy_document | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 1 | CKV_AWS_1 | resource | serverless_aws | Ensure IAM policies that allow full "*-*" administrative privileges are not created | serverless |
+| 2 | CKV_AWS_2 | resource | aws_lb_listener | Ensure ALB protocol is HTTPS | Terraform |
+| 3 | CKV_AWS_2 | resource | AWS::ElasticLoadBalancingV2::Listener | Ensure ALB protocol is HTTPS | Cloudformation |
+| 4 | CKV_AWS_3 | resource | aws_ebs_volume | Ensure all data stored in the EBS is securely encrypted | Terraform |
+| 5 | CKV_AWS_3 | resource | AWS::EC2::Volume | Ensure all data stored in the EBS is securely encrypted | Cloudformation |
+| 6 | CKV_AWS_5 | resource | aws_elasticsearch_domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Terraform |
+| 7 | CKV_AWS_5 | resource | AWS::Elasticsearch::Domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Cloudformation |
+| 8 | CKV_AWS_6 | resource | aws_elasticsearch_domain | Ensure all Elasticsearch has node-to-node encryption enabled | Terraform |
+| 9 | CKV_AWS_6 | resource | AWS::Elasticsearch::Domain | Ensure all Elasticsearch has node-to-node encryption enabled | Cloudformation |
+| 10 | CKV_AWS_7 | resource | aws_kms_key | Ensure rotation for customer created CMKs is enabled | Terraform |
+| 11 | CKV_AWS_7 | resource | AWS::KMS::Key | Ensure rotation for customer created CMKs is enabled | Cloudformation |
+| 12 | CKV_AWS_8 | resource | aws_instance | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
+| 13 | CKV_AWS_8 | resource | aws_launch_configuration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
+| 14 | CKV_AWS_8 | resource | AWS::AutoScaling::LaunchConfiguration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Cloudformation |
+| 15 | CKV_AWS_9 | resource | aws_iam_account_password_policy | Ensure IAM password policy expires passwords within 90 days or less | Terraform |
+| 16 | CKV_AWS_10 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires minimum length of 14 or greater | Terraform |
+| 17 | CKV_AWS_11 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one lowercase letter | Terraform |
+| 18 | CKV_AWS_12 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one number | Terraform |
+| 19 | CKV_AWS_13 | resource | aws_iam_account_password_policy | Ensure IAM password policy prevents password reuse | Terraform |
+| 20 | CKV_AWS_14 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one symbol | Terraform |
+| 21 | CKV_AWS_15 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one uppercase letter | Terraform |
+| 22 | CKV_AWS_16 | resource | aws_db_instance | Ensure all data stored in the RDS is securely encrypted at rest | Terraform |
+| 23 | CKV_AWS_16 | resource | AWS::RDS::DBInstance | Ensure all data stored in the RDS is securely encrypted at rest | Cloudformation |
+| 24 | CKV_AWS_17 | resource | aws_db_instance | Ensure all data stored in RDS is not publicly accessible | Terraform |
+| 25 | CKV_AWS_17 | resource | aws_rds_cluster_instance | Ensure all data stored in RDS is not publicly accessible | Terraform |
+| 26 | CKV_AWS_17 | resource | AWS::RDS::DBInstance | Ensure all data stored in RDS is not publicly accessible | Cloudformation |
+| 27 | CKV_AWS_18 | resource | aws_s3_bucket | Ensure the S3 bucket has access logging enabled | Terraform |
+| 28 | CKV_AWS_18 | resource | AWS::S3::Bucket | Ensure the S3 bucket has access logging enabled | Cloudformation |
+| 29 | CKV_AWS_19 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket is securely encrypted at rest | Terraform |
+| 30 | CKV_AWS_19 | resource | AWS::S3::Bucket | Ensure the S3 bucket has server-side-encryption enabled | Cloudformation |
+| 31 | CKV_AWS_20 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public READ access. | Terraform |
+| 32 | CKV_AWS_20 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow READ permissions to everyone | Cloudformation |
+| 33 | CKV_AWS_21 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket have versioning enabled | Terraform |
+| 34 | CKV_AWS_21 | resource | AWS::S3::Bucket | Ensure the S3 bucket has versioning enabled | Cloudformation |
+| 35 | CKV_AWS_22 | resource | aws_sagemaker_notebook_instance | Ensure SageMaker Notebook is encrypted at rest using KMS CMK | Terraform |
+| 36 | CKV_AWS_23 | resource | aws_security_group | Ensure every security groups rule has a description | Terraform |
+| 37 | CKV_AWS_23 | resource | aws_security_group_rule | Ensure every security groups rule has a description | Terraform |
+| 38 | CKV_AWS_23 | resource | aws_db_security_group | Ensure every security groups rule has a description | Terraform |
+| 39 | CKV_AWS_23 | resource | aws_elasticache_security_group | Ensure every security groups rule has a description | Terraform |
+| 40 | CKV_AWS_23 | resource | aws_redshift_security_group | Ensure every security groups rule has a description | Terraform |
+| 41 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroup | Ensure every security groups rule has a description | Cloudformation |
+| 42 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupIngress | Ensure every security groups rule has a description | Cloudformation |
+| 43 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupEgress | Ensure every security groups rule has a description | Cloudformation |
+| 44 | CKV_AWS_24 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
+| 45 | CKV_AWS_24 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
+| 46 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 47 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 48 | CKV_AWS_25 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
+| 49 | CKV_AWS_25 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
+| 50 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 51 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 52 | CKV_AWS_26 | resource | aws_sns_topic | Ensure all data stored in the SNS topic is encrypted | Terraform |
+| 53 | CKV_AWS_26 | resource | AWS::SNS::Topic | Ensure all data stored in the SNS topic is encrypted | Cloudformation |
+| 54 | CKV_AWS_27 | resource | aws_sqs_queue | Ensure all data stored in the SQS queue is encrypted | Terraform |
+| 55 | CKV_AWS_27 | resource | AWS::SQS::Queue | Ensure all data stored in the SQS queue is encrypted | Cloudformation |
+| 56 | CKV_AWS_28 | resource | aws_dynamodb_table | Ensure Dynamodb point in time recovery (backup) is enabled | Terraform |
+| 57 | CKV_AWS_28 | resource | AWS::DynamoDB::Table | Ensure Dynamodb point in time recovery (backup) is enabled | Cloudformation |
+| 58 | CKV_AWS_29 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Terraform |
+| 59 | CKV_AWS_29 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Cloudformation |
+| 60 | CKV_AWS_30 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Terraform |
+| 61 | CKV_AWS_30 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Cloudformation |
+| 62 | CKV_AWS_31 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Terraform |
+| 63 | CKV_AWS_31 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Cloudformation |
+| 64 | CKV_AWS_32 | resource | aws_ecr_repository_policy | Ensure ECR policy is not set to public | Terraform |
+| 65 | CKV_AWS_32 | resource | AWS::ECR::Repository | Ensure ECR policy is not set to public | Cloudformation |
+| 66 | CKV_AWS_33 | resource | aws_kms_key | Ensure KMS key policy does not contain wildcard (*) principal | Terraform |
+| 67 | CKV_AWS_33 | resource | AWS::KMS::Key | Ensure KMS key policy does not contain wildcard (*) principal | Cloudformation |
+| 68 | CKV_AWS_34 | resource | aws_cloudfront_distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Terraform |
+| 69 | CKV_AWS_34 | resource | AWS::CloudFront::Distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Cloudformation |
+| 70 | CKV_AWS_35 | resource | aws_cloudtrail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Terraform |
+| 71 | CKV_AWS_35 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Cloudformation |
+| 72 | CKV_AWS_36 | resource | aws_cloudtrail | Ensure CloudTrail log file validation is enabled | Terraform |
+| 73 | CKV_AWS_36 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail log file validation is enabled | Cloudformation |
+| 74 | CKV_AWS_37 | resource | aws_eks_cluster | Ensure Amazon EKS control plane logging enabled for all log types | Terraform |
+| 75 | CKV_AWS_38 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint not accessible to 0.0.0.0/0 | Terraform |
+| 76 | CKV_AWS_39 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint disabled | Terraform |
+| 77 | CKV_AWS_40 | resource | aws_iam_user_policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 78 | CKV_AWS_40 | resource | aws_iam_user_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 79 | CKV_AWS_40 | resource | aws_iam_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 80 | CKV_AWS_40 | resource | AWS::IAM::Policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Cloudformation |
+| 81 | CKV_AWS_41 | provider | aws | Ensure no hard coded AWS access key and secret key exists in provider | Terraform |
+| 82 | CKV_AWS_41 | resource | serverless_aws | Ensure no hard coded AWS access key and secret key exists in provider | serverless |
+| 83 | CKV_AWS_42 | resource | aws_efs_file_system | Ensure EFS is securely encrypted | Terraform |
+| 84 | CKV_AWS_42 | resource | AWS::EFS::FileSystem | Ensure EFS is securely encrypted | Cloudformation |
+| 85 | CKV_AWS_43 | resource | aws_kinesis_stream | Ensure Kinesis Stream is securely encrypted | Terraform |
+| 86 | CKV_AWS_43 | resource | AWS::Kinesis::Stream | Ensure Kinesis Stream is securely encrypted | Cloudformation |
+| 87 | CKV_AWS_44 | resource | aws_neptune_cluster | Ensure Neptune storage is securely encrypted | Terraform |
+| 88 | CKV_AWS_44 | resource | AWS::Neptune::DBCluster | Ensure Neptune storage is securely encrypted | Cloudformation |
+| 89 | CKV_AWS_45 | resource | aws_lambda_function | Ensure no hard-coded secrets exist in lambda environment | Terraform |
+| 90 | CKV_AWS_45 | resource | AWS::Lambda::Function | Ensure no hard-coded secrets exist in lambda environment | Cloudformation |
+| 91 | CKV_AWS_46 | resource | aws_instance | Ensure no hard-coded secrets exist in EC2 user data | Terraform |
+| 92 | CKV_AWS_46 | resource | AWS::EC2::Instance | Ensure no hard-coded secrets exist in EC2 user data | Cloudformation |
+| 93 | CKV_AWS_47 | resource | aws_dax_cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Terraform |
+| 94 | CKV_AWS_47 | resource | AWS::DAX::Cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Cloudformation |
+| 95 | CKV_AWS_48 | resource | aws_mq_broker | Ensure MQ Broker logging is enabled | Terraform |
+| 96 | CKV_AWS_49 | data | aws_iam_policy_document | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 97 | CKV_AWS_49 | resource | serverless_aws | Ensure no IAM policies documents allow "*" as a statement's actions | serverless |
+| 98 | CKV_AWS_50 | resource | aws_lambda_function | X-ray tracing is enabled for Lambda | Terraform |
+| 99 | CKV_AWS_51 | resource | aws_ecr_repository | Ensure ECR Image Tags are immutable | Terraform |
+| 100 | CKV_AWS_51 | resource | AWS::ECR::Repository | Ensure ECR Image Tags are immutable | Cloudformation |
+| 101 | CKV_AWS_53 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public ACLS enabled | Terraform |
+| 102 | CKV_AWS_53 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public ACLS enabled | Cloudformation |
+| 103 | CKV_AWS_54 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public policy enabled | Terraform |
+| 104 | CKV_AWS_54 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public policy enabled | Cloudformation |
+| 105 | CKV_AWS_55 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has ignore public ACLs enabled | Terraform |
+| 106 | CKV_AWS_55 | resource | AWS::S3::Bucket | Ensure S3 bucket has ignore public ACLs enabled | Cloudformation |
+| 107 | CKV_AWS_56 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has 'restrict_public_bucket' enabled | Terraform |
+| 108 | CKV_AWS_56 | resource | AWS::S3::Bucket | Ensure S3 bucket has 'restrict_public_bucket' enabled | Cloudformation |
+| 109 | CKV_AWS_57 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public WRITE access. | Terraform |
+| 110 | CKV_AWS_57 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow WRITE permissions to everyone | Cloudformation |
+| 111 | CKV_AWS_58 | resource | aws_eks_cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Terraform |
+| 112 | CKV_AWS_58 | resource | AWS::EKS::Cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Cloudformation |
+| 113 | CKV_AWS_59 | resource | aws_api_gateway_method | Ensure there is no open access to back-end resources through API | Terraform |
+| 114 | CKV_AWS_59 | resource | AWS::ApiGateway::Method | Ensure there is no open access to back-end resources through API | Cloudformation |
+| 115 | CKV_AWS_60 | resource | aws_iam_role | Ensure IAM role allows only specific services or principals to assume it | Terraform |
+| 116 | CKV_AWS_60 | resource | AWS::IAM::Role | Ensure IAM role allows only specific services or principals to assume it | Cloudformation |
+| 117 | CKV_AWS_61 | resource | aws_iam_role | Ensure IAM role allows only specific principals in account to assume it | Terraform |
+| 118 | CKV_AWS_61 | resource | AWS::IAM::Role | Ensure IAM role allows only specific principals in account to assume it | Cloudformation |
+| 119 | CKV_AWS_62 | resource | aws_iam_role_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 120 | CKV_AWS_62 | resource | aws_iam_user_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 121 | CKV_AWS_62 | resource | aws_iam_group_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 122 | CKV_AWS_62 | resource | aws_iam_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 123 | CKV_AWS_63 | resource | aws_iam_role_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 124 | CKV_AWS_63 | resource | aws_iam_user_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 125 | CKV_AWS_63 | resource | aws_iam_group_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 126 | CKV_AWS_63 | resource | aws_iam_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 127 | CKV_AWS_64 | resource | aws_redshift_cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Terraform |
+| 128 | CKV_AWS_64 | resource | AWS::Redshift::Cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Cloudformation |
+| 129 | CKV_AWS_65 | resource | aws_ecs_cluster | Ensure container insights are enabled on ECS cluster | Terraform |
+| 130 | CKV_AWS_65 | resource | AWS::ECS::Cluster | Ensure container insights are enabled on ECS cluster | Cloudformation |
+| 131 | CKV_AWS_66 | resource | aws_cloudwatch_log_group | Ensure that CloudWatch Log Group specifies retention days | Terraform |
+| 132 | CKV_AWS_66 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group specifies retention days | Cloudformation |
+| 133 | CKV_AWS_67 | resource | aws_cloudtrail | Ensure CloudTrail is enabled in all Regions | Terraform |
+| 134 | CKV_AWS_67 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail is enabled in all Regions | Cloudformation |
+| 135 | CKV_AWS_68 | resource | aws_cloudfront_distribution | CloudFront Distribution should have WAF enabled | Terraform |
+| 136 | CKV_AWS_68 | resource | AWS::CloudFront::Distribution | CloudFront Distribution should have WAF enabled | Cloudformation |
+| 137 | CKV_AWS_69 | resource | aws_mq_broker | Ensure MQ Broker is not publicly exposed | Terraform |
+| 138 | CKV_AWS_69 | resource | AWS::AmazonMQ::Broker | Ensure Amazon MQ Broker should not have public access | Cloudformation |
+| 139 | CKV_AWS_70 | resource | aws_s3_bucket | Ensure S3 bucket does not allow an action with any Principal | Terraform |
+| 140 | CKV_AWS_70 | resource | aws_s3_bucket_policy | Ensure S3 bucket does not allow an action with any Principal | Terraform |
+| 141 | CKV_AWS_71 | resource | aws_redshift_cluster | Ensure Redshift Cluster logging is enabled | Terraform |
+| 142 | CKV_AWS_71 | resource | AWS::Redshift::Cluster | Ensure Redshift Cluster logging is enabled | Cloudformation |
+| 143 | CKV_AWS_72 | resource | aws_sqs_queue_policy | Ensure SQS policy does not allow ALL (*) actions. | Terraform |
+| 144 | CKV_AWS_73 | resource | aws_api_gateway_stage | Ensure API Gateway has X-Ray Tracing enabled | Terraform |
+| 145 | CKV_AWS_73 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has X-Ray Tracing enabled | Cloudformation |
+| 146 | CKV_AWS_74 | resource | aws_docdb_cluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Terraform |
+| 147 | CKV_AWS_74 | resource | AWS::DocDB::DBCluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Cloudformation |
+| 148 | CKV_AWS_75 | resource | aws_globalaccelerator_accelerator | Ensure Global Accelerator accelerator has flow logs enabled | Terraform |
+| 149 | CKV_AWS_76 | resource | aws_api_gateway_stage | Ensure API Gateway has Access Logging enabled | Terraform |
+| 150 | CKV_AWS_76 | resource | aws_apigatewayv2_stage | Ensure API Gateway has Access Logging enabled | Terraform |
+| 151 | CKV_AWS_76 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has Access Logging enabled | Cloudformation |
+| 152 | CKV_AWS_77 | resource | aws_athena_database | Ensure Athena Database is encrypted at rest (default is unencrypted) | Terraform |
+| 153 | CKV_AWS_78 | resource | aws_codebuild_project | Ensure that CodeBuild Project encryption is not disabled | Terraform |
+| 154 | CKV_AWS_78 | resource | AWS::CodeBuild::Project | Ensure that CodeBuild Project encryption is not disabled | Cloudformation |
+| 155 | CKV_AWS_79 | resource | aws_instance | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
+| 156 | CKV_AWS_79 | resource | aws_launch_template | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
+| 157 | CKV_AWS_79 | resource | AWS::EC2::LaunchTemplate | Ensure Instance Metadata Service Version 1 is not enabled | Cloudformation |
+| 158 | CKV_AWS_80 | resource | aws_msk_cluster | Ensure MSK Cluster logging is enabled | Terraform |
+| 159 | CKV_AWS_81 | resource | aws_msk_cluster | Ensure MSK Cluster encryption in rest and transit is enabled | Terraform |
+| 160 | CKV_AWS_82 | resource | aws_athena_workgroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Terraform |
+| 161 | CKV_AWS_82 | resource | AWS::Athena::WorkGroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Cloudformation |
+| 162 | CKV_AWS_83 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain enforces HTTPS | Terraform |
+| 163 | CKV_AWS_83 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain enforces HTTPS | Cloudformation |
+| 164 | CKV_AWS_84 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain Logging is enabled | Terraform |
+| 165 | CKV_AWS_84 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain Logging is enabled | Cloudformation |
+| 166 | CKV_AWS_85 | resource | aws_docdb_cluster | Ensure DocDB Logging is enabled | Terraform |
+| 167 | CKV_AWS_85 | resource | AWS::DocDB::DBCluster | Ensure DocDB Logging is enabled | Cloudformation |
+| 168 | CKV_AWS_86 | resource | aws_cloudfront_distribution | Ensure Cloudfront distribution has Access Logging enabled | Terraform |
+| 169 | CKV_AWS_86 | resource | AWS::CloudFront::Distribution | Ensure Cloudfront distribution has Access Logging enabled | Cloudformation |
+| 170 | CKV_AWS_87 | resource | aws_redshift_cluster | Redshift cluster should not be publicly accessible | Terraform |
+| 171 | CKV_AWS_87 | resource | AWS::Redshift::Cluster | Redshift cluster should not be publicly accessible | Cloudformation |
+| 172 | CKV_AWS_88 | resource | aws_instance | EC2 instance should not have public IP. | Terraform |
+| 173 | CKV_AWS_88 | resource | aws_launch_template | EC2 instance should not have public IP. | Terraform |
+| 174 | CKV_AWS_88 | resource | AWS::EC2::LaunchTemplate | EC2 instance should not have public IP. | Cloudformation |
+| 175 | CKV_AWS_88 | resource | AWS::EC2::Instance | EC2 instance should not have public IP. | Cloudformation |
+| 176 | CKV_AWS_89 | resource | aws_dms_replication_instance | DMS replication instance should not be publicly accessible | Terraform |
+| 177 | CKV_AWS_89 | resource | AWS::DMS::ReplicationInstance | DMS replication instance should not be publicly accessible | Cloudformation |
+| 178 | CKV_AWS_90 | resource | aws_docdb_cluster_parameter_group | Ensure DocDB TLS is not disabled | Terraform |
+| 179 | CKV_AWS_90 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB TLS is not disabled | Cloudformation |
+| 180 | CKV_AWS_91 | resource | aws_lb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
+| 181 | CKV_AWS_91 | resource | aws_alb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
+| 182 | CKV_AWS_91 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure the ELBv2 (Application/Network) has access logging enabled | Cloudformation |
+| 183 | CKV_AWS_92 | resource | aws_elb | Ensure the ELB has access logging enabled | Terraform |
+| 184 | CKV_AWS_92 | resource | AWS::ElasticLoadBalancing::LoadBalancer | Ensure the ELB has access logging enabled | Cloudformation |
+| 185 | CKV_AWS_93 | resource | aws_s3_bucket | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
+| 186 | CKV_AWS_93 | resource | aws_s3_bucket_policy | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
+| 187 | CKV_AWS_94 | resource | aws_glue_data_catalog_encryption_settings | Ensure Glue Data Catalog Encryption is enabled | Terraform |
+| 188 | CKV_AWS_94 | resource | AWS::Glue::DataCatalogEncryptionSettings | Ensure Glue Data Catalog Encryption is enabled | Cloudformation |
+| 189 | CKV_AWS_95 | resource | AWS::ApiGatewayV2::Stage | Ensure API Gateway V2 has Access Logging enabled | Cloudformation |
+| 190 | CKV_AWS_96 | resource | aws_rds_cluster | Ensure all data stored in Aurora is securely encrypted at rest | Terraform |
+| 191 | CKV_AWS_96 | resource | AWS::RDS::DBCluster | Ensure all data stored in Aurrora is securely encrypted at rest | Cloudformation |
+| 192 | CKV_AWS_97 | resource | aws_ecs_task_definition | Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions | Terraform |
+| 193 | CKV_AWS_97 | resource | AWS::ECS::TaskDefinition | Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions | Cloudformation |
+| 194 | CKV_AWS_98 | resource | aws_sagemaker_endpoint_configuration | Ensure all data stored in the Sagemaker Endpoint is securely encrypted at rest | Terraform |
+| 195 | CKV_AWS_99 | resource | aws_glue_security_configuration | Ensure Glue Security Configuration Encryption is enabled | Terraform |
+| 196 | CKV_AWS_99 | resource | AWS::Glue::SecurityConfiguration | Ensure Glue Security Configuration Encryption is enabled | Cloudformation |
+| 197 | CKV_AWS_100 | resource | aws_eks_node_group | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Terraform |
+| 198 | CKV_AWS_100 | resource | AWS::EKS::Nodegroup | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Cloudformation |
+| 199 | CKV_AWS_101 | resource | aws_neptune_cluster | Ensure Neptune logging is enabled | Terraform |
+| 200 | CKV_AWS_101 | resource | AWS::Neptune::DBCluster | Ensure Neptune logging is enabled | Cloudformation |
+| 201 | CKV_AWS_102 | resource | aws_neptune_cluster_instance | Ensure Neptune Cluster instance is not publicly available | Terraform |
+| 202 | CKV_AWS_103 | resource | aws_lb_listener | Ensure that load balancer is using TLS 1.2 | Terraform |
+| 203 | CKV_AWS_104 | resource | aws_docdb_cluster_parameter_group | Ensure DocDB has audit logs enabled | Terraform |
+| 204 | CKV_AWS_104 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB has audit logs enabled | Cloudformation |
+| 205 | CKV_AWS_105 | resource | aws_redshift_parameter_group | Ensure Redshift uses SSL | Terraform |
+| 206 | CKV_AWS_105 | resource | AWS::Redshift::ClusterParameterGroup | Ensure Redshift uses SSL | Cloudformation |
+| 207 | CKV_AWS_106 | resource | aws_ebs_encryption_by_default | Ensure EBS default encryption is enabled | Terraform |
+| 208 | CKV_AWS_107 | data | aws_iam_policy_document | Ensure IAM policies does not allow credentials exposure | Terraform |
+| 209 | CKV_AWS_107 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 210 | CKV_AWS_107 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 211 | CKV_AWS_107 | resource | AWS::IAM::Group | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 212 | CKV_AWS_107 | resource | AWS::IAM::Role | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 213 | CKV_AWS_107 | resource | AWS::IAM::User | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 214 | CKV_AWS_108 | data | aws_iam_policy_document | Ensure IAM policies does not allow data exfiltration | Terraform |
+| 215 | CKV_AWS_108 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 216 | CKV_AWS_108 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 217 | CKV_AWS_108 | resource | AWS::IAM::Group | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 218 | CKV_AWS_108 | resource | AWS::IAM::Role | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 219 | CKV_AWS_108 | resource | AWS::IAM::User | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 220 | CKV_AWS_109 | data | aws_iam_policy_document | Ensure IAM policies does not allow permissions management / resource exposure without constraints | Terraform |
+| 221 | CKV_AWS_109 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 222 | CKV_AWS_109 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 223 | CKV_AWS_109 | resource | AWS::IAM::Group | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 224 | CKV_AWS_109 | resource | AWS::IAM::Role | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 225 | CKV_AWS_109 | resource | AWS::IAM::User | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 226 | CKV_AWS_110 | data | aws_iam_policy_document | Ensure IAM policies does not allow privilege escalation | Terraform |
+| 227 | CKV_AWS_110 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 228 | CKV_AWS_110 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 229 | CKV_AWS_110 | resource | AWS::IAM::Group | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 230 | CKV_AWS_110 | resource | AWS::IAM::Role | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 231 | CKV_AWS_110 | resource | AWS::IAM::User | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 232 | CKV_AWS_111 | data | aws_iam_policy_document | Ensure IAM policies does not allow write access without constraints | Terraform |
+| 233 | CKV_AWS_111 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 234 | CKV_AWS_111 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 235 | CKV_AWS_111 | resource | AWS::IAM::Group | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 236 | CKV_AWS_111 | resource | AWS::IAM::Role | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 237 | CKV_AWS_111 | resource | AWS::IAM::User | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 238 | CKV_AWS_112 | resource | aws_ssm_document | Ensure Session Manager data is encrypted in transit | Terraform |
+| 239 | CKV_AWS_113 | resource | aws_ssm_document | Ensure Session Manager logs are enabled and encrypted | Terraform |
+| 240 | CKV_AWS_114 | resource | aws_emr_cluster | Ensure that EMR clusters with Kerberos have Kerberos Realm set | Terraform |
+| 241 | CKV_AWS_115 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured for function-level concurrent execution limit | Terraform |
+| 242 | CKV_AWS_116 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) | Terraform |
+| 243 | CKV_AWS_117 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured inside a VPC | Terraform |
+| 244 | CKV_AWS_118 | resource | aws_db_instance | Ensure that enhanced monitoring is enabled for Amazon RDS instances | Terraform |
+| 245 | CKV_AWS_118 | resource | aws_rds_cluster_instance | Ensure that enhanced monitoring is enabled for Amazon RDS instances | Terraform |
+| 246 | CKV_AWS_119 | resource | aws_dynamodb_table | Ensure DynamoDB Tables are encrypted using KMS | Terraform |
+| 247 | CKV_AWS_120 | resource | aws_api_gateway_stage | Ensure API Gateway caching is enabled | Terraform |
+| 248 | CKV_AWS_120 | resource | AWS::ApiGateway::Stage | Ensure API Gateway caching is enabled | Cloudformation |
+| 249 | CKV_AWS_121 | resource | aws_config_configuration_aggregator | Ensure AWS Config is enabled in all regions | Terraform |
+| 250 | CKV_AWS_122 | resource | aws_sagemaker_notebook_instance | Ensure that direct internet access is disabled for an Amazon SageMaker Notebook Instance | Terraform |
+| 251 | CKV_AWS_123 | resource | aws_vpc_endpoint_service | Ensure that VPC Endpoint Service is configured for Manual Acceptance | Terraform |
+| 252 | CKV_AWS_123 | resource | AWS::EC2::VPCEndpointService | Ensure that VPC Endpoint Service is configured for Manual Acceptance | Cloudformation |
+| 253 | CKV_AWS_124 | resource | aws_cloudformation_stack | Ensure that CloudFormation stacks are sending event notifications to an SNS topic | Terraform |
+| 254 | CKV_AWS_126 | resource | aws_instance | Ensure that detailed monitoring is enabled for EC2 instances | Terraform |
+| 255 | CKV_AWS_127 | resource | aws_elb | Ensure that Elastic Load Balancer(s) uses SSL certificates provided by AWS Certificate Manager | Terraform |
+| 256 | CKV_AWS_128 | resource | aws_rds_cluster | Ensure that an Amazon RDS Clusters have AWS Identity and Access Management (IAM) authentication enabled | Terraform |
+| 257 | CKV_AWS_129 | resource | aws_db_instance | Ensure that respective logs of Amazon Relational Database Service (Amazon RDS) are enabled | Terraform |
+| 258 | CKV_AWS_130 | resource | aws_subnet | Ensure VPC subnets do not assign public IP by default | Terraform |
+| 259 | CKV_AWS_131 | resource | aws_lb | Ensure that ALB drops HTTP headers | Terraform |
+| 260 | CKV_AWS_131 | resource | aws_alb | Ensure that ALB drops HTTP headers | Terraform |
+| 261 | CKV_AWS_131 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure that ALB drops HTTP headers | Cloudformation |
+| 262 | CKV_AWS_133 | resource | aws_rds_cluster | Ensure that RDS instances has backup policy | Terraform |
+| 263 | CKV_AWS_134 | resource | aws_elasticache_cluster | Ensure that Amazon ElastiCache Redis clusters have automatic backup turned on | Terraform |
+| 264 | CKV_AWS_135 | resource | aws_instance | Ensure that EC2 is EBS optimized | Terraform |
+| 265 | CKV_AWS_136 | resource | aws_ecr_repository | Ensure that ECR repositories are encrypted using KMS | Terraform |
+| 266 | CKV_AWS_136 | resource | AWS::ECR::Repository | Ensure that ECR repositories are encrypted using KMS | Cloudformation |
+| 267 | CKV_AWS_137 | resource | aws_elasticsearch_domain | Ensure that Elasticsearch is configured inside a VPC | Terraform |
+| 268 | CKV_AWS_138 | resource | aws_elb | Ensure that ELB is cross-zone-load-balancing enabled | Terraform |
+| 269 | CKV_AWS_139 | resource | aws_rds_cluster | Ensure that RDS clusters have deletion protection enabled | Terraform |
+| 270 | CKV_AWS_140 | resource | aws_rds_global_cluster | Ensure that RDS global clusters are encrypted | Terraform |
+| 271 | CKV_AWS_141 | resource | aws_redshift_cluster | Ensured that redshift cluster allowing version upgrade by default | Terraform |
+| 272 | CKV_AWS_142 | resource | aws_redshift_cluster | Ensure that Redshift cluster is encrypted by KMS | Terraform |
+| 273 | CKV_AWS_143 | resource | aws_s3_bucket | Ensure that S3 bucket has lock configuration enabled by default | Terraform |
+| 274 | CKV_AWS_144 | resource | aws_s3_bucket | Ensure that S3 bucket has cross-region replication enabled | Terraform |
+| 275 | CKV_AWS_145 | resource | aws_s3_bucket | Ensure that S3 buckets are encrypted with KMS by default | Terraform |
+| 276 | CKV_AWS_146 | resource | aws_db_cluster_snapshot | Ensure that RDS database cluster snapshot is encrypted | Terraform |
+| 277 | CKV_AWS_147 | resource | aws_codebuild_project | Ensure that CodeBuild projects are encrypted | Terraform |
+| 278 | CKV_AWS_148 | resource | aws_default_vpc | Ensure no default VPC is planned to be provisioned | Terraform |
+| 279 | CKV_AWS_149 | resource | aws_secretsmanager_secret | Ensure that Secrets Manager secret is encrypted using KMS | Terraform |
+| 280 | CKV_AWS_150 | resource | aws_lb | Ensure that Load Balancer has deletion protection enabled | Terraform |
+| 281 | CKV_AWS_150 | resource | aws_alb | Ensure that Load Balancer has deletion protection enabled | Terraform |
+| 282 | CKV_AWS_151 | resource | aws_eks_cluster | Ensure Kubernetes Secrets are encrypted using Customer Master Keys (CMKs) managed in AWS KMS | Terraform |
+| 283 | CKV_AWS_152 | resource | aws_lb | Ensure that Load Balancer (Network/Gateway) has cross-zone load balancing enabled | Terraform |
+| 284 | CKV_AWS_152 | resource | aws_alb | Ensure that Load Balancer (Network/Gateway) has cross-zone load balancing enabled | Terraform |
+| 285 | CKV_AWS_153 | resource | aws_autoscaling_group | Autoscaling groups should supply tags to launch configurations | Terraform |
+| 286 | CKV_AWS_154 | resource | aws_redshift_cluster | Ensure Redshift is not deployed outside of a VPC | Terraform |
+| 287 | CKV_AWS_154 | resource | AWS::Redshift::Cluster | Ensure Redshift is not deployed outside of a VPC | Cloudformation |
+| 288 | CKV_AWS_155 | resource | aws_workspaces_workspace | Ensure that Workspace user volumes are encrypted | Terraform |
+| 289 | CKV_AWS_155 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace user volumes are encrypted | Cloudformation |
+| 290 | CKV_AWS_156 | resource | aws_workspaces_workspace | Ensure that Workspace root volumes are encrypted | Terraform |
+| 291 | CKV_AWS_156 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace root volumes are encrypted | Cloudformation |
+| 292 | CKV_AWS_157 | resource | aws_db_instance | Ensure that RDS instances have Multi-AZ enabled | Terraform |
+| 293 | CKV_AWS_157 | resource | AWS::RDS::DBInstance | Ensure that RDS instances have Multi-AZ enabled | Cloudformation |
+| 294 | CKV_AWS_158 | resource | aws_cloudwatch_log_group | Ensure that CloudWatch Log Group is encrypted by KMS | Terraform |
+| 295 | CKV_AWS_158 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group is encrypted by KMS | Cloudformation |
+| 296 | CKV_AWS_159 | resource | aws_athena_workgroup | Ensure that Athena Workgroup is encrypted | Terraform |
+| 297 | CKV_AWS_160 | resource | aws_timestreamwrite_database | Ensure that Timestream database is encrypted with KMS CMK | Terraform |
+| 298 | CKV_AWS_160 | resource | AWS::Timestream::Database | Ensure that Timestream database is encrypted with KMS CMK | Cloudformation |
+| 299 | CKV_AWS_161 | resource | aws_db_instance | Ensure RDS database has IAM authentication enabled | Terraform |
+| 300 | CKV_AWS_161 | resource | AWS::RDS::DBInstance | Ensure RDS database has IAM authentication enabled | Cloudformation |
+| 301 | CKV_AWS_162 | resource | aws_rds_cluster | Ensure RDS cluster has IAM authentication enabled | Terraform |
+| 302 | CKV_AWS_162 | resource | AWS::RDS::DBCluster | Ensure RDS cluster has IAM authentication enabled | Cloudformation |
+| 303 | CKV_AWS_163 | resource | aws_ecr_repository | Ensure ECR image scanning on push is enabled | Terraform |
+| 304 | CKV_AWS_163 | resource | AWS::ECR::Repository | Ensure ECR image scanning on push is enabled | Cloudformation |
+| 305 | CKV_AWS_164 | resource | aws_transfer_server | Ensure Transfer Server is not exposed publicly. | Terraform |
+| 306 | CKV_AWS_164 | resource | AWS::Transfer::Server | Ensure Transfer Server is not exposed publicly. | Cloudformation |
+| 307 | CKV_AWS_165 | resource | aws_dynamodb_global_table | Ensure Dynamodb point in time recovery (backup) is enabled for global tables | Terraform |
+| 308 | CKV_AWS_165 | resource | AWS::DynamoDB::GlobalTable | Ensure Dynamodb global table point in time recovery (backup) is enabled | Cloudformation |
+| 309 | CKV_AWS_166 | resource | aws_backup_vault | Ensure Backup Vault is encrypted at rest using KMS CMK | Terraform |
+| 310 | CKV_AWS_166 | resource | AWS::Backup::BackupVault | Ensure Backup Vault is encrypted at rest using KMS CMK | Cloudformation |
+| 311 | CKV_AWS_167 | resource | aws_glacier_vault | Ensure Glacier Vault access policy is not public by only allowing specific services or principals to access it | Terraform |
+| 312 | CKV_AWS_168 | resource | aws_sqs_queue_policy | Ensure SQS queue policy is not public by only allowing specific services or principals to access it | Terraform |
+| 313 | CKV_AWS_169 | resource | aws_sns_topic_policy | Ensure SNS topic policy is not public by only allowing specific services or principals to access it | Terraform |
+| 314 | CKV_AWS_170 | resource | aws_qldb_ledger | Ensure QLDB ledger permissions mode is set to STANDARD | Terraform |
+| 315 | CKV_AWS_170 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger permissions mode is set to STANDARD | Cloudformation |
+| 316 | CKV_AWS_171 | resource | aws_emr_security_configuration | Ensure Cluster security configuration encryption is using SSE-KMS | Terraform |
+| 317 | CKV_AWS_172 | resource | aws_qldb_ledger | Ensure QLDB ledger has deletion protection enabled | Terraform |
+| 318 | CKV_AWS_172 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger has deletion protection enabled | Cloudformation |
+| 319 | CKV2_AWS_1 | resource | aws_subnet | Ensure that all NACL are attached to subnets | Terraform |
+| 320 | CKV2_AWS_1 | resource | aws_network_acl | Ensure that all NACL are attached to subnets | Terraform |
+| 321 | CKV2_AWS_2 | resource | aws_volume_attachment | Ensure that only encrypted EBS volumes are attached to EC2 instances | Terraform |
+| 322 | CKV2_AWS_2 | resource | aws_ebs_volume | Ensure that only encrypted EBS volumes are attached to EC2 instances | Terraform |
+| 323 | CKV2_AWS_3 | resource | aws_guardduty_organization_configuration | Ensure GuardDuty is enabled to specific org/region | Terraform |
+| 324 | CKV2_AWS_3 | resource | aws_guardduty_detector | Ensure GuardDuty is enabled to specific org/region | Terraform |
+| 325 | CKV2_AWS_4 | resource | aws_api_gateway_stage | Ensure API Gateway stage have logging level defined as appropriate | Terraform |
+| 326 | CKV2_AWS_4 | resource | aws_api_gateway_method_settings | Ensure API Gateway stage have logging level defined as appropriate | Terraform |
+| 327 | CKV2_AWS_5 | resource | aws_security_group | Ensure that Security Groups are attached to an other resource | Terraform |
+| 328 | CKV2_AWS_6 | resource | aws_s3_bucket | Ensure that S3 bucket has a Public Access block | Terraform |
+| 329 | CKV2_AWS_6 | resource | aws_s3_bucket_public_access_block | Ensure that S3 bucket has a Public Access block | Terraform |
+| 330 | CKV2_AWS_7 | resource | aws_security_group | Ensure that Amazon EMR clusters' security groups are not open to the world | Terraform |
+| 331 | CKV2_AWS_7 | resource | aws_emr_cluster | Ensure that Amazon EMR clusters' security groups are not open to the world | Terraform |
+| 332 | CKV2_AWS_8 | resource | aws_rds_cluster | Ensure that RDS clusters has backup plan of AWS Backup | Terraform |
+| 333 | CKV2_AWS_9 | resource | aws_backup_selection | Ensure that EBS are added in the backup plans of AWS Backup | Terraform |
+| 334 | CKV2_AWS_10 | resource | aws_cloudtrail | Ensure CloudTrail trails are integrated with CloudWatch Logs | Terraform |
+| 335 | CKV2_AWS_11 | resource | aws_vpc | Ensure VPC flow logging is enabled in all VPCs | Terraform |
+| 336 | CKV2_AWS_12 | resource | aws_vpc | Ensure the default security group of every VPC restricts all traffic | Terraform |
+| 337 | CKV2_AWS_12 | resource | aws_default_security_group | Ensure the default security group of every VPC restricts all traffic | Terraform |
+| 338 | CKV2_AWS_13 | resource | aws_redshift_cluster | Ensure that Redshift clusters has backup plan of AWS Backup | Terraform |
+| 339 | CKV2_AWS_14 | resource | aws_iam_group_membership | Ensure that IAM groups includes at least one IAM user | Terraform |
+| 340 | CKV2_AWS_14 | resource | aws_iam_group | Ensure that IAM groups includes at least one IAM user | Terraform |
+| 341 | CKV2_AWS_15 | resource | aws_autoscaling_group | Ensure that auto Scaling groups that are associated with a load balancer, are using Elastic Load Balancing health checks. | Terraform |
+| 342 | CKV2_AWS_15 | resource | aws_elb | Ensure that auto Scaling groups that are associated with a load balancer, are using Elastic Load Balancing health checks. | Terraform |
+| 343 | CKV2_AWS_16 | resource | aws_appautoscaling_target | Ensure that Auto Scaling is enabled on your DynamoDB tables | Terraform |
+| 344 | CKV2_AWS_16 | resource | aws_dynamodb_table | Ensure that Auto Scaling is enabled on your DynamoDB tables | Terraform |
+| 345 | CKV2_AWS_18 | resource | aws_backup_selection | Ensure that Elastic File System (Amazon EFS) file systems are added in the backup plans of AWS Backup | Terraform |
+| 346 | CKV2_AWS_19 | resource | aws_eip | Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances | Terraform |
+| 347 | CKV2_AWS_19 | resource | aws_eip_association | Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances | Terraform |
+| 348 | CKV2_AWS_20 | resource | aws_lb | Ensure that ALB redirects HTTP requests into HTTPS ones | Terraform |
+| 349 | CKV2_AWS_20 | resource | aws_lb_listener | Ensure that ALB redirects HTTP requests into HTTPS ones | Terraform |
+| 350 | CKV2_AWS_21 | resource | aws_iam_group_membership | Ensure that all IAM users are members of at least one IAM group. | Terraform |
+| 351 | CKV2_AWS_22 | resource | aws_iam_user | Ensure an IAM User does not have access to the console | Terraform |
+| 352 | CKV2_AWS_23 | resource | aws_route53_record | Route53 A Record has Attached Resource | Terraform |
+| 353 | CKV2_AWS_27 | resource | aws_db_instance | Postgres RDS has Query Logging enabled | Terraform |
+| 354 | CKV2_AWS_27 | resource | aws_rds_cluster_parameter_group | Postgres RDS has Query Logging enabled | Terraform |
+| 355 | CKV2_AWS_28 | resource | aws_lb | Ensure public facing ALB are protected by WAF | Terraform |
+| 356 | CKV2_AWS_29 | resource | aws_api_gateway_stage | Ensure public API gateway are protected by WAF | Terraform |
+| 357 | CKV2_AWS_29 | resource | aws_api_gateway_rest_api | Ensure public API gateway are protected by WAF | Terraform |
+| 358 | CKV_AZURE_1 | resource | azurerm_linux_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 359 | CKV_AZURE_1 | resource | azurerm_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 360 | CKV_AZURE_1 | resource | Microsoft.Compute/virtualMachines | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | arm |
+| 361 | CKV_AZURE_2 | resource | azurerm_managed_disk | Ensure Azure managed disk has encryption enabled | Terraform |
+| 362 | CKV_AZURE_2 | resource | Microsoft.Compute/disks | Ensure Azure managed disk have encryption enabled | arm |
+| 363 | CKV_AZURE_3 | resource | azurerm_storage_account | Ensure that 'Secure transfer required' is set to 'Enabled' | Terraform |
+| 364 | CKV_AZURE_3 | resource | Microsoft.Storage/storageAccounts | Ensure that 'supportsHttpsTrafficOnly' is set to 'true' | arm |
+| 365 | CKV_AZURE_4 | resource | azurerm_kubernetes_cluster | Ensure AKS logging to Azure Monitoring is Configured | Terraform |
+| 366 | CKV_AZURE_4 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS logging to Azure Monitoring is Configured | arm |
+| 367 | CKV_AZURE_5 | resource | azurerm_kubernetes_cluster | Ensure RBAC is enabled on AKS clusters | Terraform |
+| 368 | CKV_AZURE_5 | resource | Microsoft.ContainerService/managedClusters | Ensure RBAC is enabled on AKS clusters | arm |
+| 369 | CKV_AZURE_6 | resource | azurerm_kubernetes_cluster | Ensure AKS has an API Server Authorized IP Ranges enabled | Terraform |
+| 370 | CKV_AZURE_6 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS has an API Server Authorized IP Ranges enabled | arm |
+| 371 | CKV_AZURE_7 | resource | azurerm_kubernetes_cluster | Ensure AKS cluster has Network Policy configured | Terraform |
+| 372 | CKV_AZURE_7 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS cluster has Network Policy configured | arm |
+| 373 | CKV_AZURE_8 | resource | azurerm_kubernetes_cluster | Ensure Kube Dashboard is disabled | Terraform |
+| 374 | CKV_AZURE_8 | resource | Microsoft.ContainerService/managedClusters | Ensure Kubernetes Dashboard is disabled | arm |
+| 375 | CKV_AZURE_9 | resource | azurerm_network_security_rule | Ensure that RDP access is restricted from the internet | Terraform |
+| 376 | CKV_AZURE_9 | resource | azurerm_network_security_group | Ensure that RDP access is restricted from the internet | Terraform |
+| 377 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups | Ensure that RDP access is restricted from the internet | arm |
+| 378 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that RDP access is restricted from the internet | arm |
+| 379 | CKV_AZURE_10 | resource | azurerm_network_security_rule | Ensure that SSH access is restricted from the internet | Terraform |
+| 380 | CKV_AZURE_10 | resource | azurerm_network_security_group | Ensure that SSH access is restricted from the internet | Terraform |
+| 381 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups | Ensure that SSH access is restricted from the internet | arm |
+| 382 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that SSH access is restricted from the internet | arm |
+| 383 | CKV_AZURE_11 | resource | azurerm_mariadb_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 384 | CKV_AZURE_11 | resource | azurerm_sql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 385 | CKV_AZURE_11 | resource | azurerm_postgresql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 386 | CKV_AZURE_11 | resource | azurerm_mysql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 387 | CKV_AZURE_11 | resource | Microsoft.Sql/servers | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | arm |
+| 388 | CKV_AZURE_12 | resource | azurerm_network_watcher_flow_log | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | Terraform |
+| 389 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 390 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 391 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 392 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 393 | CKV_AZURE_13 | resource | azurerm_app_service | Ensure App Service Authentication is set on Azure App Service | Terraform |
+| 394 | CKV_AZURE_13 | resource | Microsoft.Web/sites/config | Ensure App Service Authentication is set on Azure App Service | arm |
+| 395 | CKV_AZURE_13 | resource | config | Ensure App Service Authentication is set on Azure App Service | arm |
+| 396 | CKV_AZURE_14 | resource | azurerm_app_service | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | Terraform |
+| 397 | CKV_AZURE_14 | resource | Microsoft.Web/sites | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | arm |
+| 398 | CKV_AZURE_15 | resource | azurerm_app_service | Ensure web app is using the latest version of TLS encryption | Terraform |
+| 399 | CKV_AZURE_15 | resource | Microsoft.Web/sites | Ensure web app is using the latest version of TLS encryption | arm |
+| 400 | CKV_AZURE_16 | resource | azurerm_app_service | Ensure that Register with Azure Active Directory is enabled on App Service | Terraform |
+| 401 | CKV_AZURE_16 | resource | Microsoft.Web/sites | Ensure that Register with Azure Active Directory is enabled on App Service | arm |
+| 402 | CKV_AZURE_17 | resource | azurerm_app_service | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | Terraform |
+| 403 | CKV_AZURE_17 | resource | Microsoft.Web/sites | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | arm |
+| 404 | CKV_AZURE_18 | resource | azurerm_app_service | Ensure that 'HTTP Version' is the latest if used to run the web app | Terraform |
+| 405 | CKV_AZURE_18 | resource | Microsoft.Web/sites | Ensure that 'HTTP Version' is the latest if used to run the web app | arm |
+| 406 | CKV_AZURE_19 | resource | azurerm_security_center_subscription_pricing | Ensure that standard pricing tier is selected | Terraform |
+| 407 | CKV_AZURE_19 | resource | Microsoft.Security/pricings | Ensure that standard pricing tier is selected | arm |
+| 408 | CKV_AZURE_20 | resource | azurerm_security_center_contact | Ensure that security contact 'Phone number' is set | Terraform |
+| 409 | CKV_AZURE_20 | resource | Microsoft.Security/securityContacts | Ensure that security contact 'Phone number' is set | arm |
+| 410 | CKV_AZURE_21 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
+| 411 | CKV_AZURE_21 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
+| 412 | CKV_AZURE_22 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
+| 413 | CKV_AZURE_22 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
+| 414 | CKV_AZURE_23 | resource | azurerm_sql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
+| 415 | CKV_AZURE_23 | resource | azurerm_mssql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
+| 416 | CKV_AZURE_23 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' is set to 'Enabled' for SQL servers | arm |
+| 417 | CKV_AZURE_24 | resource | azurerm_sql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
+| 418 | CKV_AZURE_24 | resource | azurerm_mssql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
+| 419 | CKV_AZURE_24 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | arm |
+| 420 | CKV_AZURE_25 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Threat Detection types' is set to 'All' | Terraform |
+| 421 | CKV_AZURE_25 | resource | Microsoft.Sql/servers/databases | Ensure that 'Threat Detection types' is set to 'All' | arm |
+| 422 | CKV_AZURE_26 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Send Alerts To' is enabled for MSSQL servers | Terraform |
+| 423 | CKV_AZURE_26 | resource | Microsoft.Sql/servers/databases | Ensure that 'Send Alerts To' is enabled for MSSQL servers | arm |
+| 424 | CKV_AZURE_27 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | Terraform |
+| 425 | CKV_AZURE_27 | resource | Microsoft.Sql/servers/databases | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | arm |
+| 426 | CKV_AZURE_28 | resource | azurerm_mysql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | Terraform |
+| 427 | CKV_AZURE_28 | resource | Microsoft.DBforMySQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | arm |
+| 428 | CKV_AZURE_29 | resource | azurerm_postgresql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | Terraform |
+| 429 | CKV_AZURE_29 | resource | Microsoft.DBforPostgreSQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | arm |
+| 430 | CKV_AZURE_30 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 431 | CKV_AZURE_30 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
+| 432 | CKV_AZURE_30 | resource | configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
+| 433 | CKV_AZURE_31 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_connections' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 434 | CKV_AZURE_31 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
+| 435 | CKV_AZURE_31 | resource | configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
+| 436 | CKV_AZURE_32 | resource | azurerm_postgresql_configuration | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 437 | CKV_AZURE_32 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
+| 438 | CKV_AZURE_32 | resource | configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
+| 439 | CKV_AZURE_33 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Queue service for read, write and delete requests | Terraform |
+| 440 | CKV_AZURE_33 | resource | Microsoft.Storage/storageAccounts/queueServices/providers/diagnosticsettings | Ensure Storage logging is enabled for Queue service for read, write and delete requests | arm |
+| 441 | CKV_AZURE_34 | resource | azurerm_storage_container | Ensure that 'Public access level' is set to Private for blob containers | Terraform |
+| 442 | CKV_AZURE_34 | resource | Microsoft.Storage/storageAccounts/blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 443 | CKV_AZURE_34 | resource | containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 444 | CKV_AZURE_34 | resource | blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 445 | CKV_AZURE_35 | resource | azurerm_storage_account | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
+| 446 | CKV_AZURE_35 | resource | azurerm_storage_account_network_rules | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
+| 447 | CKV_AZURE_35 | resource | Microsoft.Storage/storageAccounts | Ensure default network access rule for Storage Accounts is set to deny | arm |
+| 448 | CKV_AZURE_36 | resource | azurerm_storage_account | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
+| 449 | CKV_AZURE_36 | resource | azurerm_storage_account_network_rules | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
+| 450 | CKV_AZURE_36 | resource | Microsoft.Storage/storageAccounts | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | arm |
+| 451 | CKV_AZURE_37 | resource | azurerm_monitor_log_profile | Ensure that Activity Log Retention is set 365 days or greater | Terraform |
+| 452 | CKV_AZURE_37 | resource | microsoft.insights/logprofiles | Ensure that Activity Log Retention is set 365 days or greater | arm |
+| 453 | CKV_AZURE_38 | resource | azurerm_monitor_log_profile | Ensure audit profile captures all the activities | Terraform |
+| 454 | CKV_AZURE_38 | resource | microsoft.insights/logprofiles | Ensure audit profile captures all the activities | arm |
+| 455 | CKV_AZURE_39 | resource | azurerm_role_definition | Ensure that no custom subscription owner roles are created | Terraform |
+| 456 | CKV_AZURE_39 | resource | Microsoft.Authorization/roleDefinitions | Ensure that no custom subscription owner roles are created | arm |
+| 457 | CKV_AZURE_40 | resource | azurerm_key_vault_key | Ensure that the expiration date is set on all keys | Terraform |
+| 458 | CKV_AZURE_41 | resource | azurerm_key_vault_secret | Ensure that the expiration date is set on all secrets | Terraform |
+| 459 | CKV_AZURE_41 | resource | Microsoft.KeyVault/vaults/secrets | Ensure that the expiration date is set on all secrets | arm |
+| 460 | CKV_AZURE_42 | resource | azurerm_key_vault | Ensure the key vault is recoverable | Terraform |
+| 461 | CKV_AZURE_42 | resource | Microsoft.KeyVault/vaults | Ensure the key vault is recoverable | arm |
+| 462 | CKV_AZURE_43 | resource | azurerm_storage_account | Ensure the Storage Account naming rules | Terraform |
+| 463 | CKV_AZURE_44 | resource | azurerm_storage_account | Ensure Storage Account is using the latest version of TLS encryption | Terraform |
+| 464 | CKV_AZURE_45 | resource | azurerm_virtual_machine | Ensure that no sensitive credentials are exposed in VM custom_data | Terraform |
+| 465 | CKV_AZURE_46 | resource | azurerm_mssql_database_extended_auditing_policy | Specifies a retention period of less than 90 days. | Terraform |
+| 466 | CKV_AZURE_47 | resource | azurerm_mariadb_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MariaDB servers | Terraform |
+| 467 | CKV_AZURE_48 | resource | azurerm_mariadb_server | Ensure 'public network access enabled' is set to 'False' for MariaDB servers | Terraform |
+| 468 | CKV_AZURE_49 | resource | azurerm_linux_virtual_machine_scale_set | Ensure Azure linux scale set does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 469 | CKV_AZURE_50 | resource | azurerm_linux_virtual_machine | Ensure Virtual Machine Extensions are not Installed | Terraform |
+| 470 | CKV_AZURE_50 | resource | azurerm_virtual_machine | Ensure Virtual Machine Extensions are not Installed | Terraform |
+| 471 | CKV_AZURE_52 | resource | azurerm_mssql_server | Ensure MSSQL is using the latest version of TLS encryption | Terraform |
+| 472 | CKV_AZURE_53 | resource | azurerm_mysql_server | Ensure 'public network access enabled' is set to 'False' for mySQL servers | Terraform |
+| 473 | CKV_AZURE_54 | resource | azurerm_mysql_server | Ensure MySQL is using the latest version of TLS encryption | Terraform |
+| 474 | CKV_AZURE_55 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Servers | Terraform |
+| 475 | CKV_AZURE_56 | resource | azurerm_function_app | Ensure that function apps enables Authentication | Terraform |
+| 476 | CKV_AZURE_57 | resource | azurerm_app_service | Ensure that CORS disallows every resource to access app services | Terraform |
+| 477 | CKV_AZURE_58 | resource | azurerm_synapse_workspace | Ensure that Azure Synapse workspaces enables managed virtual networks | Terraform |
+| 478 | CKV_AZURE_59 | resource | azurerm_storage_account | Ensure that Storage accounts disallow public access | Terraform |
+| 479 | CKV_AZURE_60 | resource | azurerm_storage_account | Ensure that storage account enables secure transfer | Terraform |
+| 480 | CKV_AZURE_61 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for App Service | Terraform |
+| 481 | CKV_AZURE_62 | resource | azurerm_function_app | Ensure function apps are not accessible from all regions | Terraform |
+| 482 | CKV_AZURE_63 | resource | azurerm_app_service | Ensure that App service enables HTTP logging | Terraform |
+| 483 | CKV_AZURE_64 | resource | azurerm_storage_sync | Ensure that Azure File Sync disables public network access | Terraform |
+| 484 | CKV_AZURE_65 | resource | azurerm_app_service | Ensure that App service enables detailed error messages | Terraform |
+| 485 | CKV_AZURE_66 | resource | azurerm_app_service | Ensure that App service enables failed request tracing | Terraform |
+| 486 | CKV_AZURE_67 | resource | azurerm_function_app | Ensure that 'HTTP Version' is the latest, if used to run the Function app | Terraform |
+| 487 | CKV_AZURE_68 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server disables public network access | Terraform |
+| 488 | CKV_AZURE_69 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Azure SQL database servers | Terraform |
+| 489 | CKV_AZURE_70 | resource | azurerm_function_app | Ensure that Function apps is only accessible over HTTPS | Terraform |
+| 490 | CKV_AZURE_71 | resource | azurerm_app_service | Ensure that Managed identity provider is enabled for app services | Terraform |
+| 491 | CKV_AZURE_72 | resource | azurerm_app_service | Ensure that remote debugging is not enabled for app services | Terraform |
+| 492 | CKV_AZURE_73 | resource | azurerm_automation_variable_bool | Ensure that Automation account variables are encrypted | Terraform |
+| 493 | CKV_AZURE_73 | resource | azurerm_automation_variable_string | Ensure that Automation account variables are encrypted | Terraform |
+| 494 | CKV_AZURE_73 | resource | azurerm_automation_variable_int | Ensure that Automation account variables are encrypted | Terraform |
+| 495 | CKV_AZURE_73 | resource | azurerm_automation_variable_datetime | Ensure that Automation account variables are encrypted | Terraform |
+| 496 | CKV_AZURE_74 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer uses disk encryption | Terraform |
+| 497 | CKV_AZURE_75 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer uses double encryption | Terraform |
+| 498 | CKV_AZURE_76 | resource | azurerm_batch_account | Ensure that Azure Batch account uses key vault to encrypt data | Terraform |
+| 499 | CKV_AZURE_77 | resource | azurerm_network_security_rule | Ensure that UDP Services are restricted from the Internet | Terraform |
+| 500 | CKV_AZURE_77 | resource | azurerm_network_security_group | Ensure that UDP Services are restricted from the Internet | Terraform |
+| 501 | CKV_AZURE_78 | resource | azurerm_app_service | Ensure FTP deployments are disabled | Terraform |
+| 502 | CKV_AZURE_79 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for SQL servers on machines | Terraform |
+| 503 | CKV_AZURE_80 | resource | azurerm_app_service | Ensure that 'Net Framework' version is the latest, if used as a part of the web app | Terraform |
+| 504 | CKV_AZURE_81 | resource | azurerm_app_service | Ensure that 'PHP version' is the latest, if used to run the web app | Terraform |
+| 505 | CKV_AZURE_82 | resource | azurerm_app_service | Ensure that 'Python version' is the latest, if used to run the web app | Terraform |
+| 506 | CKV_AZURE_83 | resource | azurerm_app_service | Ensure that 'Java version' is the latest, if used to run the web app | Terraform |
+| 507 | CKV_AZURE_84 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Storage | Terraform |
+| 508 | CKV_AZURE_85 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Kubernetes | Terraform |
+| 509 | CKV_AZURE_86 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Container Registries | Terraform |
+| 510 | CKV_AZURE_87 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Key Vault | Terraform |
+| 511 | CKV_AZURE_88 | resource | azurerm_app_service | Ensure that app services use Azure Files | Terraform |
+| 512 | CKV_AZURE_89 | resource | azurerm_redis_cache | Ensure that Azure Cache for Redis disables public network access | Terraform |
+| 513 | CKV_AZURE_90 | resource | azurerm_mysql_server | Ensure that MySQL server disables public network access | Terraform |
+| 514 | CKV_AZURE_91 | resource | azurerm_redis_cache | Ensure that only SSL are enabled for Cache for Redis | Terraform |
+| 515 | CKV_AZURE_92 | resource | azurerm_linux_virtual_machine | Ensure that Virtual Machines use managed disks | Terraform |
+| 516 | CKV_AZURE_92 | resource | azurerm_windows_virtual_machine | Ensure that Virtual Machines use managed disks | Terraform |
+| 517 | CKV_AZURE_93 | resource | azurerm_managed_disk | Ensure that managed disks use a specific set of disk encryption sets for the customer-managed key encryption | Terraform |
+| 518 | CKV_AZURE_94 | resource | azurerm_mysql_server | Ensure that My SQL server enables geo-redundant backups | Terraform |
+| 519 | CKV_AZURE_95 | resource | azurerm_virtual_machine_scale_set | Ensure that automatic OS image patching is enabled for Virtual Machine Scale Sets | Terraform |
+| 520 | CKV_AZURE_96 | resource | azurerm_mysql_server | Ensure that MySQL server enables infrastructure encryption | Terraform |
+| 521 | CKV_AZURE_97 | resource | azurerm_linux_virtual_machine_scale_set | Ensure that Virtual machine scale sets have encryption at host enabled | Terraform |
+| 522 | CKV_AZURE_97 | resource | azurerm_windows_virtual_machine_scale_set | Ensure that Virtual machine scale sets have encryption at host enabled | Terraform |
+| 523 | CKV_AZURE_98 | resource | azurerm_container_group | Ensure that Azure Container group is deployed into virtual network | Terraform |
+| 524 | CKV_AZURE_99 | resource | azurerm_cosmosdb_account | Ensure Cosmos DB accounts have restricted access | Terraform |
+| 525 | CKV_AZURE_100 | resource | azurerm_cosmosdb_account | Ensure that Cosmos DB accounts have customer-managed keys to encrypt data at rest | Terraform |
+| 526 | CKV_AZURE_101 | resource | azurerm_cosmosdb_account | Ensure that Azure Cosmos DB disables public network access | Terraform |
+| 527 | CKV_AZURE_102 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables geo-redundant backups | Terraform |
+| 528 | CKV_AZURE_103 | resource | azurerm_data_factory | Ensure that Azure Data Factory uses Git repository for source control | Terraform |
+| 529 | CKV_AZURE_104 | resource | azurerm_data_factory | Ensure that Azure Data factory public network access is disabled | Terraform |
+| 530 | CKV_AZURE_105 | resource | azurerm_data_lake_store | Ensure that Data Lake Store accounts enables encryption | Terraform |
+| 531 | CKV_AZURE_106 | resource | azurerm_eventgrid_domain | Ensure that Azure Event Grid Domain public network access is disabled | Terraform |
+| 532 | CKV_AZURE_107 | resource | azurerm_api_management | Ensure that API management services use virtual networks | Terraform |
+| 533 | CKV_AZURE_108 | resource | azurerm_iothub | Ensure that Azure IoT Hub disables public network access | Terraform |
+| 534 | CKV_AZURE_109 | resource | azurerm_key_vault | Ensure that key vault allows firewall rules settings | Terraform |
+| 535 | CKV_AZURE_110 | resource | azurerm_key_vault | Ensure that key vault enables purge protection | Terraform |
+| 536 | CKV_AZURE_111 | resource | azurerm_key_vault | Ensure that key vault enables soft delete | Terraform |
+| 537 | CKV_AZURE_112 | resource | azurerm_key_vault_key | Ensure that key vault key is backed by HSM | Terraform |
+| 538 | CKV_AZURE_113 | resource | azurerm_mssql_server | Ensure that SQL server disables public network access | Terraform |
+| 539 | CKV_AZURE_114 | resource | azurerm_key_vault_secret | Ensure that key vault secrets have "content_type" set | Terraform |
+| 540 | CKV_AZURE_115 | resource | azurerm_kubernetes_cluster | Ensure that AKS enables private clusters | Terraform |
+| 541 | CKV_AZURE_116 | resource | azurerm_kubernetes_cluster | Ensure that AKS uses Azure Policies Add-on | Terraform |
+| 542 | CKV_AZURE_117 | resource | azurerm_kubernetes_cluster | Ensure that AKS uses disk encryption set | Terraform |
+| 543 | CKV_AZURE_118 | resource | azurerm_network_interface | Ensure that Network Interfaces disable IP forwarding | Terraform |
+| 544 | CKV_AZURE_119 | resource | azurerm_network_interface | Ensure that Network Interfaces don't use public IPs | Terraform |
+| 545 | CKV_AZURE_120 | resource | azurerm_application_gateway | Ensure that Application Gateway enables WAF | Terraform |
+| 546 | CKV_AZURE_121 | resource | azurerm_frontdoor | Ensure that Azure Front Door enables WAF | Terraform |
+| 547 | CKV_AZURE_122 | resource | azurerm_web_application_firewall_policy | Ensure that Application Gateway uses WAF in "Detection" or "Prevention" modes | Terraform |
+| 548 | CKV_AZURE_123 | resource | azurerm_frontdoor_firewall_policy | Ensure that Azure Front Door uses WAF in "Detection" or "Prevention" modes | Terraform |
+| 549 | CKV_AZURE_124 | resource | azurerm_search_service | Ensure that Azure Cognitive Search disables public network access | Terraform |
+| 550 | CKV_AZURE_125 | resource | azurerm_service_fabric_cluster | Ensures that Service Fabric use three levels of protection available | Terraform |
+| 551 | CKV_AZURE_126 | resource | azurerm_service_fabric_cluster | Ensures that Active Directory is used for authentication for Service Fabric | Terraform |
+| 552 | CKV_AZURE_127 | resource | azurerm_mysql_server | Ensure that My SQL server enables Threat detection policy | Terraform |
+| 553 | CKV_AZURE_128 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables Threat detection policy | Terraform |
+| 554 | CKV_AZURE_129 | resource | azurerm_mariadb_server | Ensure that MariaDB server enables geo-redundant backups | Terraform |
+| 555 | CKV_AZURE_130 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables infrastructure encryption | Terraform |
+| 556 | CKV_AZURE_131 | resource | azurerm_security_center_contact | Ensure that 'Security contact emails' is set | Terraform |
+| 557 | CKV_AZURE_131 | parameter | secureString | SecureString parameter should not have hardcoded default values | arm |
+| 558 | CKV2_AZURE_1 | resource | azurerm_storage_account | Ensure storage for critical data are encrypted with Customer Managed Key | Terraform |
+| 559 | CKV2_AZURE_2 | resource | azurerm_sql_server | Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account | Terraform |
+| 560 | CKV2_AZURE_2 | resource | azurerm_mssql_server_security_alert_policy | Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account | Terraform |
+| 561 | CKV2_AZURE_3 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 562 | CKV2_AZURE_3 | resource | azurerm_sql_server | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 563 | CKV2_AZURE_3 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 564 | CKV2_AZURE_4 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 565 | CKV2_AZURE_4 | resource | azurerm_sql_server | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 566 | CKV2_AZURE_4 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 567 | CKV2_AZURE_5 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 568 | CKV2_AZURE_5 | resource | azurerm_sql_server | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 569 | CKV2_AZURE_5 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 570 | CKV2_AZURE_6 | resource | azurerm_sql_server | Ensure 'Allow access to Azure services' for PostgreSQL Database Server is disabled | Terraform |
+| 571 | CKV2_AZURE_6 | resource | azurerm_sql_firewall_rule | Ensure 'Allow access to Azure services' for PostgreSQL Database Server is disabled | Terraform |
+| 572 | CKV2_AZURE_7 | resource | azurerm_sql_server | Ensure that Azure Active Directory Admin is configured | Terraform |
+| 573 | CKV2_AZURE_8 | resource | azurerm_storage_container | Ensure the storage container storing the activity logs is not publicly accessible | Terraform |
+| 574 | CKV2_AZURE_8 | resource | azurerm_monitor_activity_log_alert | Ensure the storage container storing the activity logs is not publicly accessible | Terraform |
+| 575 | CKV2_AZURE_9 | resource | azurerm_virtual_machine | Ensure Virtual Machines are utilizing Managed Disks | Terraform |
+| 576 | CKV2_AZURE_10 | resource | azurerm_virtual_machine_extension | Ensure that Microsoft Antimalware is configured to automatically updates for Virtual Machines | Terraform |
+| 577 | CKV2_AZURE_10 | resource | azurerm_virtual_machine | Ensure that Microsoft Antimalware is configured to automatically updates for Virtual Machines | Terraform |
+| 578 | CKV2_AZURE_11 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer encryption at rest uses a customer-managed key | Terraform |
+| 579 | CKV2_AZURE_12 | resource | azurerm_virtual_machine | Ensure that virtual machines are backed up using Azure Backup | Terraform |
+| 580 | CKV2_AZURE_13 | resource | azurerm_sql_server | Ensure that sql servers enables data security policy | Terraform |
+| 581 | CKV2_AZURE_13 | resource | azurerm_mssql_server_security_alert_policy | Ensure that sql servers enables data security policy | Terraform |
+| 582 | CKV2_AZURE_14 | resource | azurerm_managed_disk | Ensure that Unattached disks are encrypted | Terraform |
+| 583 | CKV2_AZURE_14 | resource | azurerm_virtual_machine | Ensure that Unattached disks are encrypted | Terraform |
+| 584 | CKV2_AZURE_15 | resource | azurerm_data_factory | Ensure that Azure data factories are encrypted with a customer-managed key | Terraform |
+| 585 | CKV2_AZURE_16 | resource | azurerm_mysql_server_key | Ensure that MySQL server enables customer-managed key for encryption | Terraform |
+| 586 | CKV2_AZURE_16 | resource | azurerm_mysql_server | Ensure that MySQL server enables customer-managed key for encryption | Terraform |
+| 587 | CKV2_AZURE_17 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables customer-managed key for encryption | Terraform |
+| 588 | CKV2_AZURE_17 | resource | azurerm_postgresql_server_key | Ensure that PostgreSQL server enables customer-managed key for encryption | Terraform |
+| 589 | CKV2_AZURE_18 | resource | azurerm_storage_account_customer_managed_key | Ensure that Storage Accounts use customer-managed key for encryption | Terraform |
+| 590 | CKV2_AZURE_18 | resource | azurerm_storage_account | Ensure that Storage Accounts use customer-managed key for encryption | Terraform |
+| 591 | CKV2_AZURE_19 | resource | azurerm_synapse_workspace | Ensure that Azure Synapse workspaces have no IP firewall rules attached | Terraform |
+| 592 | CKV2_AZURE_20 | resource | azurerm_storage_table | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 593 | CKV2_AZURE_20 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 594 | CKV2_AZURE_20 | resource | azurerm_log_analytics_storage_insights | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 595 | CKV2_AZURE_21 | resource | azurerm_storage_container | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 596 | CKV2_AZURE_21 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 597 | CKV2_AZURE_21 | resource | azurerm_log_analytics_storage_insights | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 598 | CKV_DOCKER_1 | dockerfile | EXPOSE | Ensure port 22 is not exposed | dockerfile |
+| 599 | CKV_DOCKER_2 | dockerfile | * | Ensure that HEALTHCHECK instructions have been added to container images | dockerfile |
+| 600 | CKV_DOCKER_3 | dockerfile | * | Ensure that a user for the container has been created | dockerfile |
+| 601 | CKV_DOCKER_4 | dockerfile | ADD | Ensure that COPY is used instead of ADD in Dockerfiles | dockerfile |
+| 602 | CKV_DOCKER_5 | dockerfile | RUN | Ensure update instructions are not use alone in the Dockerfile | dockerfile |
+| 603 | CKV_DOCKER_6 | dockerfile | MAINTAINER | Ensure that LABEL maintainer is used instead of MAINTAINER (deprecated) | dockerfile |
+| 604 | CKV_DOCKER_7 | dockerfile | FROM | Ensure the base image uses a non latest version tag | dockerfile |
+| 605 | CKV_DOCKER_8 | dockerfile | USER | Ensure the last USER is not root | dockerfile |
+| 606 | CKV_GCP_1 | resource | google_container_cluster | Ensure Stackdriver Logging is set to Enabled on Kubernetes Engine Clusters | Terraform |
+| 607 | CKV_GCP_2 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted ssh access | Terraform |
+| 608 | CKV_GCP_3 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted rdp access | Terraform |
+| 609 | CKV_GCP_4 | resource | google_compute_ssl_policy | Ensure no HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites | Terraform |
+| 610 | CKV_GCP_6 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance requires all incoming connections to use SSL | Terraform |
+| 611 | CKV_GCP_7 | resource | google_container_cluster | Ensure Legacy Authorization is set to Disabled on Kubernetes Engine Clusters | Terraform |
+| 612 | CKV_GCP_8 | resource | google_container_cluster | Ensure Stackdriver Monitoring is set to Enabled on Kubernetes Engine Clusters | Terraform |
+| 613 | CKV_GCP_9 | resource | google_container_node_pool | Ensure 'Automatic node repair' is enabled for Kubernetes Clusters | Terraform |
+| 614 | CKV_GCP_10 | resource | google_container_node_pool | Ensure 'Automatic node upgrade' is enabled for Kubernetes Clusters | Terraform |
+| 615 | CKV_GCP_11 | resource | google_sql_database_instance | Ensure that Cloud SQL database Instances are not open to the world | Terraform |
+| 616 | CKV_GCP_12 | resource | google_container_cluster | Ensure Network Policy is enabled on Kubernetes Engine Clusters | Terraform |
+| 617 | CKV_GCP_13 | resource | google_container_cluster | Ensure a client certificate is used by clients to authenticate to Kubernetes Engine Clusters | Terraform |
+| 618 | CKV_GCP_14 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance have backup configuration enabled | Terraform |
+| 619 | CKV_GCP_15 | resource | google_bigquery_dataset | Ensure that BigQuery datasets are not anonymously or publicly accessible | Terraform |
+| 620 | CKV_GCP_16 | resource | google_dns_managed_zone | Ensure that DNSSEC is enabled for Cloud DNS | Terraform |
+| 621 | CKV_GCP_17 | resource | google_dns_managed_zone | Ensure that RSASHA1 is not used for the zone-signing and key-signing keys in Cloud DNS DNSSEC | Terraform |
+| 622 | CKV_GCP_18 | resource | google_container_cluster | Ensure GKE Control Plane is not public | Terraform |
+| 623 | CKV_GCP_19 | resource | google_container_cluster | Ensure GKE basic auth is disabled | Terraform |
+| 624 | CKV_GCP_20 | resource | google_container_cluster | Ensure master authorized networks is set to enabled in GKE clusters | Terraform |
+| 625 | CKV_GCP_21 | resource | google_container_cluster | Ensure Kubernetes Clusters are configured with Labels | Terraform |
+| 626 | CKV_GCP_22 | resource | google_container_node_pool | Ensure Container-Optimized OS (cos) is used for Kubernetes Engine Clusters Node image | Terraform |
+| 627 | CKV_GCP_23 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Alias IP ranges enabled | Terraform |
+| 628 | CKV_GCP_24 | resource | google_container_cluster | Ensure PodSecurityPolicy controller is enabled on the Kubernetes Engine Clusters | Terraform |
+| 629 | CKV_GCP_25 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Private cluster enabled | Terraform |
+| 630 | CKV_GCP_26 | resource | google_compute_subnetwork | Ensure that VPC Flow Logs is enabled for every subnet in a VPC Network | Terraform |
+| 631 | CKV_GCP_27 | resource | google_project | Ensure that the default network does not exist in a project | Terraform |
+| 632 | CKV_GCP_28 | resource | google_storage_bucket_iam_member | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
+| 633 | CKV_GCP_28 | resource | google_storage_bucket_iam_binding | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
+| 634 | CKV_GCP_29 | resource | google_storage_bucket | Ensure that Cloud Storage buckets have uniform bucket-level access enabled | Terraform |
+| 635 | CKV_GCP_30 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account | Terraform |
+| 636 | CKV_GCP_31 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account with full access to all Cloud APIs | Terraform |
+| 637 | CKV_GCP_32 | resource | google_compute_instance | Ensure 'Block Project-wide SSH keys' is enabled for VM instances | Terraform |
+| 638 | CKV_GCP_33 | resource | google_compute_project_metadata | Ensure oslogin is enabled for a Project | Terraform |
+| 639 | CKV_GCP_34 | resource | google_compute_instance | Ensure that no instance in the project overrides the project setting for enabling OSLogin(OSLogin needs to be enabled in project metadata for all instances) | Terraform |
+| 640 | CKV_GCP_35 | resource | google_compute_instance | Ensure 'Enable connecting to serial ports' is not enabled for VM Instance | Terraform |
+| 641 | CKV_GCP_36 | resource | google_compute_instance | Ensure that IP forwarding is not enabled on Instances | Terraform |
+| 642 | CKV_GCP_37 | resource | google_compute_disk | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
+| 643 | CKV_GCP_38 | resource | google_compute_instance | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
+| 644 | CKV_GCP_39 | resource | google_compute_instance | Ensure Compute instances are launched with Shielded VM enabled | Terraform |
+| 645 | CKV_GCP_40 | resource | google_compute_instance | Ensure that Compute instances do not have public IP addresses | Terraform |
+| 646 | CKV_GCP_41 | resource | google_project_iam_member | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
+| 647 | CKV_GCP_41 | resource | google_project_iam_binding | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
+| 648 | CKV_GCP_42 | resource | google_project_iam_member | Ensure that Service Account has no Admin privileges | Terraform |
+| 649 | CKV_GCP_43 | resource | google_kms_crypto_key | Ensure KMS encryption keys are rotated within a period of 90 days | Terraform |
+| 650 | CKV_GCP_44 | resource | google_folder_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
+| 651 | CKV_GCP_44 | resource | google_folder_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
+| 652 | CKV_GCP_45 | resource | google_organization_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
+| 653 | CKV_GCP_45 | resource | google_organization_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
+| 654 | CKV_GCP_46 | resource | google_project_iam_member | Ensure Default Service account is not used at a project level | Terraform |
+| 655 | CKV_GCP_46 | resource | google_project_iam_binding | Ensure Default Service account is not used at a project level | Terraform |
+| 656 | CKV_GCP_47 | resource | google_organization_iam_member | Ensure default service account is not used at an organization level | Terraform |
+| 657 | CKV_GCP_47 | resource | google_organization_iam_binding | Ensure default service account is not used at an organization level | Terraform |
+| 658 | CKV_GCP_48 | resource | google_folder_iam_member | Ensure Default Service account is not used at a folder level | Terraform |
+| 659 | CKV_GCP_48 | resource | google_folder_iam_binding | Ensure Default Service account is not used at a folder level | Terraform |
+| 660 | CKV_GCP_49 | resource | google_project_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
+| 661 | CKV_GCP_49 | resource | google_project_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
+| 662 | CKV_GCP_50 | resource | google_sql_database_instance | Ensure MySQL database 'local_infile' flag is set to 'off' | Terraform |
+| 663 | CKV_GCP_51 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_checkpoints' flag is set to 'on' | Terraform |
+| 664 | CKV_GCP_52 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_connections' flag is set to 'on' | Terraform |
+| 665 | CKV_GCP_53 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_disconnections' flag is set to 'on' | Terraform |
+| 666 | CKV_GCP_54 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_lock_waits' flag is set to 'on' | Terraform |
+| 667 | CKV_GCP_55 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_messages' flag is set to a valid value | Terraform |
+| 668 | CKV_GCP_56 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_temp_files flag is set to '0' | Terraform |
+| 669 | CKV_GCP_57 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_duration_statement' flag is set to '-1' | Terraform |
+| 670 | CKV_GCP_58 | resource | google_sql_database_instance | Ensure SQL database 'cross db ownership chaining' flag is set to 'off' | Terraform |
+| 671 | CKV_GCP_59 | resource | google_sql_database_instance | Ensure SQL database 'contained database authentication' flag is set to 'off' | Terraform |
+| 672 | CKV_GCP_60 | resource | google_sql_database_instance | Ensure SQL database do not have public IP | Terraform |
+| 673 | CKV_GCP_61 | resource | google_container_cluster | Enable VPC Flow Logs and Intranode Visibility | Terraform |
+| 674 | CKV_GCP_62 | resource | google_storage_bucket | Bucket should log access | Terraform |
+| 675 | CKV_GCP_63 | resource | google_storage_bucket | Bucket should not log to itself | Terraform |
+| 676 | CKV_GCP_64 | resource | google_container_cluster | Ensure clusters are created with Private Nodes | Terraform |
+| 677 | CKV_GCP_65 | resource | google_container_cluster | Manage Kubernetes RBAC users with Google Groups for GKE | Terraform |
+| 678 | CKV_GCP_66 | resource | google_container_cluster | Ensure use of Binary Authorization | Terraform |
+| 679 | CKV_GCP_67 | resource | google_container_cluster | Ensure legacy Compute Engine instance metadata APIs are Disabled | Terraform |
+| 680 | CKV_GCP_68 | resource | google_container_cluster | Ensure Secure Boot for Shielded GKE Nodes is Enabled | Terraform |
+| 681 | CKV_GCP_68 | resource | google_container_node_pool | Ensure Secure Boot for Shielded GKE Nodes is Enabled | Terraform |
+| 682 | CKV_GCP_69 | resource | google_container_cluster | Ensure the GKE Metadata Server is Enabled | Terraform |
+| 683 | CKV_GCP_69 | resource | google_container_node_pool | Ensure the GKE Metadata Server is Enabled | Terraform |
+| 684 | CKV_GCP_70 | resource | google_container_cluster | Ensure the GKE Release Channel is set | Terraform |
+| 685 | CKV_GCP_71 | resource | google_container_cluster | Ensure Shielded GKE Nodes are Enabled | Terraform |
+| 686 | CKV_GCP_72 | resource | google_container_cluster | Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled | Terraform |
+| 687 | CKV_GCP_72 | resource | google_container_node_pool | Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled | Terraform |
+| 688 | CKV2_GCP_1 | resource | google_project_default_service_accounts | Ensure GKE clusters are not running using the Compute Engine default service account | Terraform |
+| 689 | CKV2_GCP_2 | resource | google_compute_network | Ensure legacy networks do not exist for a project | Terraform |
+| 690 | CKV2_GCP_3 | resource | google_healthcare_dataset_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 691 | CKV2_GCP_3 | resource | google_folder_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 692 | CKV2_GCP_3 | resource | google_container_analysis_occurrence | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 693 | CKV2_GCP_3 | resource | google_compute_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 694 | CKV2_GCP_3 | resource | google_monitoring_dashboard | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 695 | CKV2_GCP_3 | resource | google_compute_region_instance_group_manager | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 696 | CKV2_GCP_3 | resource | google_access_context_manager_access_level | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 697 | CKV2_GCP_3 | resource | google_monitoring_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 698 | CKV2_GCP_3 | resource | google_monitoring_notification_channel | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 699 | CKV2_GCP_3 | resource | google_app_engine_flexible_app_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 700 | CKV2_GCP_3 | resource | google_compute_http_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 701 | CKV2_GCP_3 | resource | google_app_engine_service_split_traffic | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 702 | CKV2_GCP_3 | resource | google_bigquery_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 703 | CKV2_GCP_3 | resource | google_dataproc_job_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 704 | CKV2_GCP_3 | resource | google_firestore_index | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 705 | CKV2_GCP_3 | resource | google_endpoints_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 706 | CKV2_GCP_3 | resource | google_cloudiot_device | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 707 | CKV2_GCP_3 | resource | google_iap_client | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 708 | CKV2_GCP_3 | resource | google_resource_manager_lien | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 709 | CKV2_GCP_3 | resource | google_monitoring_slo | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 710 | CKV2_GCP_3 | resource | google_compute_snapshot | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 711 | CKV2_GCP_3 | resource | google_bigtable_table | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 712 | CKV2_GCP_3 | resource | google_runtimeconfig_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 713 | CKV2_GCP_3 | resource | google_spanner_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 714 | CKV2_GCP_3 | resource | google_redis_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 715 | CKV2_GCP_3 | resource | google_sourcerepo_repository | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 716 | CKV2_GCP_3 | resource | google_compute_node_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 717 | CKV2_GCP_3 | resource | google_compute_reservation | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 718 | CKV2_GCP_3 | resource | google_project_iam_custom_role | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 719 | CKV2_GCP_3 | resource | google_dataflow_flex_template_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 720 | CKV2_GCP_3 | resource | google_data_catalog_entry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 721 | CKV2_GCP_3 | resource | google_cloud_asset_organization_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 722 | CKV2_GCP_3 | resource | google_data_catalog_tag | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 723 | CKV2_GCP_3 | resource | google_secret_manager_secret_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 724 | CKV2_GCP_3 | resource | google_folder | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 725 | CKV2_GCP_3 | resource | google_spanner_database | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 726 | CKV2_GCP_3 | resource | google_storage_object_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 727 | CKV2_GCP_3 | resource | google_compute_backend_service_signed_url_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 728 | CKV2_GCP_3 | resource | google_compute_image | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 729 | CKV2_GCP_3 | resource | google_organization_iam_custom_role | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 730 | CKV2_GCP_3 | resource | google_sql_ssl_cert | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 731 | CKV2_GCP_3 | resource | google_pubsub_subscription | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 732 | CKV2_GCP_3 | resource | google_project_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 733 | CKV2_GCP_3 | resource | google_os_login_ssh_public_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 734 | CKV2_GCP_3 | resource | google_cloudbuild_trigger | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 735 | CKV2_GCP_3 | resource | google_compute_subnetwork_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 736 | CKV2_GCP_3 | resource | google_pubsub_topic_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 737 | CKV2_GCP_3 | resource | google_kms_secret_ciphertext | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 738 | CKV2_GCP_3 | resource | google_compute_network_peering | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 739 | CKV2_GCP_3 | resource | google_compute_target_pool | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 740 | CKV2_GCP_3 | resource | google_iap_web_type_app_engine_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 741 | CKV2_GCP_3 | resource | google_scc_source | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 742 | CKV2_GCP_3 | resource | google_healthcare_hl7_v2_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 743 | CKV2_GCP_3 | resource | google_storage_bucket_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 744 | CKV2_GCP_3 | resource | google_storage_transfer_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 745 | CKV2_GCP_3 | resource | google_compute_region_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 746 | CKV2_GCP_3 | resource | google_sql_source_representation_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 747 | CKV2_GCP_3 | resource | google_compute_project_metadata_item | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 748 | CKV2_GCP_3 | resource | google_cloud_scheduler_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 749 | CKV2_GCP_3 | resource | google_storage_bucket_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 750 | CKV2_GCP_3 | resource | google_dns_managed_zone | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 751 | CKV2_GCP_3 | resource | google_storage_notification | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 752 | CKV2_GCP_3 | resource | google_compute_network | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 753 | CKV2_GCP_3 | resource | google_bigtable_app_profile | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 754 | CKV2_GCP_3 | resource | google_healthcare_dicom_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 755 | CKV2_GCP_3 | resource | google_network_management_connectivity_test_resource | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 756 | CKV2_GCP_3 | resource | google_service_account_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 757 | CKV2_GCP_3 | resource | google_organization_iam_audit_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 758 | CKV2_GCP_3 | resource | google_billing_account_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 759 | CKV2_GCP_3 | resource | google_compute_interconnect_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 760 | CKV2_GCP_3 | resource | google_compute_address | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 761 | CKV2_GCP_3 | resource | google_compute_router_bgp_peer | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 762 | CKV2_GCP_3 | resource | google_project_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 763 | CKV2_GCP_3 | resource | google_endpoints_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 764 | CKV2_GCP_3 | resource | google_bigquery_table | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 765 | CKV2_GCP_3 | resource | google_folder_iam_audit_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 766 | CKV2_GCP_3 | resource | google_compute_autoscaler | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 767 | CKV2_GCP_3 | resource | google_identity_platform_oauth_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 768 | CKV2_GCP_3 | resource | google_os_config_patch_deployment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 769 | CKV2_GCP_3 | resource | google_cloud_run_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 770 | CKV2_GCP_3 | resource | google_folder_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 771 | CKV2_GCP_3 | resource | google_secret_manager_secret | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 772 | CKV2_GCP_3 | resource | google_cloud_asset_folder_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 773 | CKV2_GCP_3 | resource | google_monitoring_uptime_check_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 774 | CKV2_GCP_3 | resource | google_compute_forwarding_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 775 | CKV2_GCP_3 | resource | google_compute_instance_from_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 776 | CKV2_GCP_3 | resource | google_logging_billing_account_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 777 | CKV2_GCP_3 | resource | google_bigtable_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 778 | CKV2_GCP_3 | resource | google_logging_folder_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 779 | CKV2_GCP_3 | resource | google_dataproc_cluster | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 780 | CKV2_GCP_3 | resource | google_compute_region_target_https_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 781 | CKV2_GCP_3 | resource | google_storage_bucket_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 782 | CKV2_GCP_3 | resource | google_spanner_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 783 | CKV2_GCP_3 | resource | google_app_engine_application_url_dispatch_rules | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 784 | CKV2_GCP_3 | resource | google_storage_object_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 785 | CKV2_GCP_3 | resource | google_billing_account_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 786 | CKV2_GCP_3 | resource | google_usage_export_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 787 | CKV2_GCP_3 | resource | google_service_account_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 788 | CKV2_GCP_3 | resource | google_spanner_database_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 789 | CKV2_GCP_3 | resource | google_service_networking_connection | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 790 | CKV2_GCP_3 | resource | google_compute_region_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 791 | CKV2_GCP_3 | resource | google_compute_vpn_gateway | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 792 | CKV2_GCP_3 | resource | google_bigquery_dataset | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 793 | CKV2_GCP_3 | resource | google_compute_target_tcp_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 794 | CKV2_GCP_3 | resource | google_bigtable_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 795 | CKV2_GCP_3 | resource | google_access_context_manager_service_perimeter_resource | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 796 | CKV2_GCP_3 | resource | google_identity_platform_tenant_inbound_saml_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 797 | CKV2_GCP_3 | resource | google_project_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 798 | CKV2_GCP_3 | resource | google_project_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 799 | CKV2_GCP_3 | resource | google_compute_attached_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 800 | CKV2_GCP_3 | resource | google_composer_environment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 801 | CKV2_GCP_3 | resource | google_compute_region_autoscaler | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 802 | CKV2_GCP_3 | resource | google_datastore_index | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 803 | CKV2_GCP_3 | resource | google_dialogflow_intent | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 804 | CKV2_GCP_3 | resource | google_runtimeconfig_config_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 805 | CKV2_GCP_3 | resource | google_storage_hmac_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 806 | CKV2_GCP_3 | resource | google_bigtable_gc_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 807 | CKV2_GCP_3 | resource | google_logging_organization_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 808 | CKV2_GCP_3 | resource | google_compute_region_backend_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 809 | CKV2_GCP_3 | resource | google_iap_tunnel_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 810 | CKV2_GCP_3 | resource | google_compute_backend_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 811 | CKV2_GCP_3 | resource | google_dataflow_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 812 | CKV2_GCP_3 | resource | google_iap_web_backend_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 813 | CKV2_GCP_3 | resource | google_logging_project_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 814 | CKV2_GCP_3 | resource | google_iap_web_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 815 | CKV2_GCP_3 | resource | google_project_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 816 | CKV2_GCP_3 | resource | google_compute_target_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 817 | CKV2_GCP_3 | resource | google_kms_key_ring | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 818 | CKV2_GCP_3 | resource | google_compute_target_ssl_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 819 | CKV2_GCP_3 | resource | google_compute_security_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 820 | CKV2_GCP_3 | resource | google_pubsub_topic | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 821 | CKV2_GCP_3 | resource | google_dialogflow_entity_type | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 822 | CKV2_GCP_3 | resource | google_container_registry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 823 | CKV2_GCP_3 | resource | google_binary_authorization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 824 | CKV2_GCP_3 | resource | google_compute_network_peering_routes_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 825 | CKV2_GCP_3 | resource | google_compute_ssl_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 826 | CKV2_GCP_3 | resource | google_compute_https_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 827 | CKV2_GCP_3 | resource | google_logging_folder_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 828 | CKV2_GCP_3 | resource | google_compute_network_endpoint | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 829 | CKV2_GCP_3 | resource | google_compute_instance_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 830 | CKV2_GCP_3 | resource | google_logging_billing_account_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 831 | CKV2_GCP_3 | resource | google_compute_vpn_tunnel | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 832 | CKV2_GCP_3 | resource | google_secret_manager_secret_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 833 | CKV2_GCP_3 | resource | google_compute_region_target_http_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 834 | CKV2_GCP_3 | resource | google_healthcare_dataset | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 835 | CKV2_GCP_3 | resource | google_tpu_node | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 836 | CKV2_GCP_3 | resource | google_compute_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 837 | CKV2_GCP_3 | resource | google_bigquery_data_transfer_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 838 | CKV2_GCP_3 | resource | google_organization_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 839 | CKV2_GCP_3 | resource | google_billing_account_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 840 | CKV2_GCP_3 | resource | google_access_context_manager_service_perimeter | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 841 | CKV2_GCP_3 | resource | google_compute_firewall | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 842 | CKV2_GCP_3 | resource | google_compute_global_network_endpoint_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 843 | CKV2_GCP_3 | resource | google_storage_default_object_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 844 | CKV2_GCP_3 | resource | google_storage_bucket_object | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 845 | CKV2_GCP_3 | resource | google_iap_brand | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 846 | CKV2_GCP_3 | resource | google_compute_node_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 847 | CKV2_GCP_3 | resource | google_compute_router | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 848 | CKV2_GCP_3 | resource | google_compute_ssl_certificate | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 849 | CKV2_GCP_3 | resource | google_kms_key_ring_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 850 | CKV2_GCP_3 | resource | google_storage_bucket_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 851 | CKV2_GCP_3 | resource | google_container_analysis_note | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 852 | CKV2_GCP_3 | resource | google_iap_app_engine_version_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 853 | CKV2_GCP_3 | resource | google_app_engine_application | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 854 | CKV2_GCP_3 | resource | google_compute_route | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 855 | CKV2_GCP_3 | resource | google_compute_resource_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 856 | CKV2_GCP_3 | resource | google_logging_folder_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 857 | CKV2_GCP_3 | resource | google_app_engine_domain_mapping | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 858 | CKV2_GCP_3 | resource | google_vpc_access_connector | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 859 | CKV2_GCP_3 | resource | google_healthcare_fhir_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 860 | CKV2_GCP_3 | resource | google_compute_subnetwork | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 861 | CKV2_GCP_3 | resource | google_organization_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 862 | CKV2_GCP_3 | resource | google_compute_project_default_network_tier | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 863 | CKV2_GCP_3 | resource | google_healthcare_fhir_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 864 | CKV2_GCP_3 | resource | google_compute_region_ssl_certificate | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 865 | CKV2_GCP_3 | resource | google_deployment_manager_deployment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 866 | CKV2_GCP_3 | resource | google_bigquery_dataset_access | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 867 | CKV2_GCP_3 | resource | google_organization_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 868 | CKV2_GCP_3 | resource | google_bigquery_dataset_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 869 | CKV2_GCP_3 | resource | google_cloud_asset_project_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 870 | CKV2_GCP_3 | resource | google_container_cluster | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 871 | CKV2_GCP_3 | resource | google_folder_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 872 | CKV2_GCP_3 | resource | google_cloud_run_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 873 | CKV2_GCP_3 | resource | google_compute_project_metadata | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 874 | CKV2_GCP_3 | resource | google_compute_network_endpoint_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 875 | CKV2_GCP_3 | resource | google_folder_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 876 | CKV2_GCP_3 | resource | google_data_catalog_entry_group_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 877 | CKV2_GCP_3 | resource | google_dataproc_cluster_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 878 | CKV2_GCP_3 | resource | google_compute_global_network_endpoint | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 879 | CKV2_GCP_3 | resource | google_identity_platform_tenant | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 880 | CKV2_GCP_3 | resource | google_kms_crypto_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 881 | CKV2_GCP_3 | resource | google_logging_organization_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 882 | CKV2_GCP_3 | resource | google_cloud_run_domain_mapping | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 883 | CKV2_GCP_3 | resource | google_compute_router_interface | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 884 | CKV2_GCP_3 | resource | google_compute_region_disk_resource_policy_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 885 | CKV2_GCP_3 | resource | google_container_node_pool | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 886 | CKV2_GCP_3 | resource | google_kms_crypto_key_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 887 | CKV2_GCP_3 | resource | google_app_engine_firewall_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 888 | CKV2_GCP_3 | resource | google_logging_billing_account_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 889 | CKV2_GCP_3 | resource | google_dns_record_set | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 890 | CKV2_GCP_3 | resource | google_iap_app_engine_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 891 | CKV2_GCP_3 | resource | google_storage_default_object_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 892 | CKV2_GCP_3 | resource | google_healthcare_hl7_v2_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 893 | CKV2_GCP_3 | resource | google_healthcare_dicom_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 894 | CKV2_GCP_3 | resource | google_sql_database | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 895 | CKV2_GCP_3 | resource | google_monitoring_alert_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 896 | CKV2_GCP_3 | resource | google_compute_region_url_map | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 897 | CKV2_GCP_3 | resource | google_compute_instance_group_manager | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 898 | CKV2_GCP_3 | resource | google_compute_router_nat | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 899 | CKV2_GCP_3 | resource | google_compute_target_http_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 900 | CKV2_GCP_3 | resource | google_sql_user | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 901 | CKV2_GCP_3 | resource | google_compute_instance_group_named_port | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 902 | CKV2_GCP_3 | resource | google_sourcerepo_repository_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 903 | CKV2_GCP_3 | resource | google_logging_project_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 904 | CKV2_GCP_3 | resource | google_dataproc_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 905 | CKV2_GCP_3 | resource | google_storage_bucket_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 906 | CKV2_GCP_3 | resource | google_app_engine_standard_app_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 907 | CKV2_GCP_3 | resource | google_access_context_manager_access_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 908 | CKV2_GCP_3 | resource | google_identity_platform_tenant_oauth_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 909 | CKV2_GCP_3 | resource | google_monitoring_metric_descriptor | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 910 | CKV2_GCP_3 | resource | google_compute_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 911 | CKV2_GCP_3 | resource | google_data_catalog_entry_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 912 | CKV2_GCP_3 | resource | google_filestore_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 913 | CKV2_GCP_3 | resource | google_cloudfunctions_cloud_function_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 914 | CKV2_GCP_3 | resource | google_compute_backend_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 915 | CKV2_GCP_3 | resource | google_identity_platform_tenant_default_supported_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 916 | CKV2_GCP_3 | resource | google_compute_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 917 | CKV2_GCP_3 | resource | google_runtimeconfig_variable | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 918 | CKV2_GCP_3 | resource | google_storage_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 919 | CKV2_GCP_3 | resource | google_active_directory_domain | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 920 | CKV2_GCP_3 | resource | google_logging_project_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 921 | CKV2_GCP_3 | resource | google_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 922 | CKV2_GCP_3 | resource | google_compute_target_https_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 923 | CKV2_GCP_3 | resource | google_cloudfunctions_function | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 924 | CKV2_GCP_3 | resource | google_identity_platform_default_supported_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 925 | CKV2_GCP_3 | resource | google_binary_authorization_attestor_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 926 | CKV2_GCP_3 | resource | google_dialogflow_agent | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 927 | CKV2_GCP_3 | resource | google_logging_organization_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 928 | CKV2_GCP_3 | resource | google_compute_global_forwarding_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 929 | CKV2_GCP_3 | resource | google_cloudiot_device_registry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 930 | CKV2_GCP_3 | resource | google_data_catalog_tag_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 931 | CKV2_GCP_3 | resource | google_dns_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 932 | CKV2_GCP_3 | resource | google_cloud_tasks_queue | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 933 | CKV2_GCP_3 | resource | google_binary_authorization_attestor | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 934 | CKV2_GCP_3 | resource | google_compute_url_map | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 935 | CKV2_GCP_3 | resource | google_compute_disk_resource_policy_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 936 | CKV2_GCP_3 | resource | google_ml_engine_model | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 937 | CKV2_GCP_3 | resource | google_compute_shared_vpc_service_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 938 | CKV2_GCP_3 | resource | google_pubsub_subscription_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 939 | CKV2_GCP_3 | resource | google_compute_shared_vpc_host_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 940 | CKV2_GCP_3 | resource | google_kms_key_ring_import_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 941 | CKV2_GCP_3 | resource | google_sql_database_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 942 | CKV2_GCP_3 | resource | google_identity_platform_inbound_saml_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 943 | CKV2_GCP_3 | resource | google_iap_web_type_compute_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 944 | CKV2_GCP_3 | resource | google_monitoring_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 945 | CKV2_GCP_3 | resource | google_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 946 | CKV2_GCP_3 | resource | google_compute_backend_bucket_signed_url_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 947 | CKV2_GCP_3 | resource | google_compute_instance_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 948 | CKV2_GCP_3 | resource | google_dataproc_autoscaling_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 949 | CKV2_GCP_3 | resource | google_service_account | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 950 | CKV2_GCP_3 | resource | google_logging_metric | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 951 | CKV2_GCP_3 | resource | google_compute_global_address | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 952 | CKV2_GCP_4 | resource | google_logging_folder_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 953 | CKV2_GCP_4 | resource | google_logging_organization_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 954 | CKV2_GCP_4 | resource | google_logging_project_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 955 | CKV2_GCP_4 | resource | google_storage_bucket | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 956 | CKV2_GCP_5 | resource | google_project_iam_audit_config | Ensure that Cloud Audit Logging is configured properly across all services and all users from a project | Terraform |
+| 957 | CKV2_GCP_5 | resource | google_project | Ensure that Cloud Audit Logging is configured properly across all services and all users from a project | Terraform |
+| 958 | CKV2_GCP_6 | resource | google_kms_crypto_key_iam_member | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 959 | CKV2_GCP_6 | resource | google_kms_crypto_key | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 960 | CKV2_GCP_6 | resource | google_kms_crypto_key_iam_binding | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 961 | CKV2_GCP_7 | resource | google_sql_user | Ensure that a MySQL database instance does not allow anyone to connect with administrative privileges | Terraform |
+| 962 | CKV2_GCP_7 | resource | google_sql_database_instance | Ensure that a MySQL database instance does not allow anyone to connect with administrative privileges | Terraform |
+| 963 | CKV_GIT_1 | resource | github_repository | Ensure Repository is Private | Terraform |
+| 964 | CKV_K8S_1 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host process ID namespace | Kubernetes |
+| 965 | CKV_K8S_2 | resource | PodSecurityPolicy | Do not admit privileged containers | Kubernetes |
+| 966 | CKV_K8S_3 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host IPC namespace | Kubernetes |
+| 967 | CKV_K8S_4 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host network namespace | Kubernetes |
+| 968 | CKV_K8S_5 | resource | PodSecurityPolicy | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 969 | CKV_K8S_6 | resource | PodSecurityPolicy | Do not admit root containers | Kubernetes |
+| 970 | CKV_K8S_7 | resource | PodSecurityPolicy | Do not admit containers with the NET_RAW capability | Kubernetes |
+| 971 | CKV_K8S_8 | resource | containers | Liveness Probe Should be Configured | Kubernetes |
+| 972 | CKV_K8S_9 | resource | containers | Readiness Probe Should be Configured | Kubernetes |
+| 973 | CKV_K8S_10 | resource | containers | CPU requests should be set | Kubernetes |
+| 974 | CKV_K8S_10 | resource | initContainers | CPU requests should be set | Kubernetes |
+| 975 | CKV_K8S_11 | resource | containers | CPU limits should be set | Kubernetes |
+| 976 | CKV_K8S_11 | resource | initContainers | CPU limits should be set | Kubernetes |
+| 977 | CKV_K8S_12 | resource | containers | Memory requests should be set | Kubernetes |
+| 978 | CKV_K8S_12 | resource | initContainers | Memory requests should be set | Kubernetes |
+| 979 | CKV_K8S_13 | resource | containers | Memory limits should be set | Kubernetes |
+| 980 | CKV_K8S_13 | resource | initContainers | Memory limits should be set | Kubernetes |
+| 981 | CKV_K8S_14 | resource | containers | Image Tag should be fixed - not latest or blank | Kubernetes |
+| 982 | CKV_K8S_14 | resource | initContainers | Image Tag should be fixed - not latest or blank | Kubernetes |
+| 983 | CKV_K8S_15 | resource | containers | Image Pull Policy should be Always | Kubernetes |
+| 984 | CKV_K8S_15 | resource | initContainers | Image Pull Policy should be Always | Kubernetes |
+| 985 | CKV_K8S_16 | resource | containers | Container should not be privileged | Kubernetes |
+| 986 | CKV_K8S_16 | resource | initContainers | Container should not be privileged | Kubernetes |
+| 987 | CKV_K8S_17 | resource | Pod | Containers should not share the host process ID namespace | Kubernetes |
+| 988 | CKV_K8S_17 | resource | Deployment | Containers should not share the host process ID namespace | Kubernetes |
+| 989 | CKV_K8S_17 | resource | DaemonSet | Containers should not share the host process ID namespace | Kubernetes |
+| 990 | CKV_K8S_17 | resource | StatefulSet | Containers should not share the host process ID namespace | Kubernetes |
+| 991 | CKV_K8S_17 | resource | ReplicaSet | Containers should not share the host process ID namespace | Kubernetes |
+| 992 | CKV_K8S_17 | resource | ReplicationController | Containers should not share the host process ID namespace | Kubernetes |
+| 993 | CKV_K8S_17 | resource | Job | Containers should not share the host process ID namespace | Kubernetes |
+| 994 | CKV_K8S_17 | resource | CronJob | Containers should not share the host process ID namespace | Kubernetes |
+| 995 | CKV_K8S_18 | resource | Pod | Containers should not share the host IPC namespace | Kubernetes |
+| 996 | CKV_K8S_18 | resource | Deployment | Containers should not share the host IPC namespace | Kubernetes |
+| 997 | CKV_K8S_18 | resource | DaemonSet | Containers should not share the host IPC namespace | Kubernetes |
+| 998 | CKV_K8S_18 | resource | StatefulSet | Containers should not share the host IPC namespace | Kubernetes |
+| 999 | CKV_K8S_18 | resource | ReplicaSet | Containers should not share the host IPC namespace | Kubernetes |
+| 1000 | CKV_K8S_18 | resource | ReplicationController | Containers should not share the host IPC namespace | Kubernetes |
+| 1001 | CKV_K8S_18 | resource | Job | Containers should not share the host IPC namespace | Kubernetes |
+| 1002 | CKV_K8S_18 | resource | CronJob | Containers should not share the host IPC namespace | Kubernetes |
+| 1003 | CKV_K8S_19 | resource | Pod | Containers should not share the host network namespace | Kubernetes |
+| 1004 | CKV_K8S_19 | resource | Deployment | Containers should not share the host network namespace | Kubernetes |
+| 1005 | CKV_K8S_19 | resource | DaemonSet | Containers should not share the host network namespace | Kubernetes |
+| 1006 | CKV_K8S_19 | resource | StatefulSet | Containers should not share the host network namespace | Kubernetes |
+| 1007 | CKV_K8S_19 | resource | ReplicaSet | Containers should not share the host network namespace | Kubernetes |
+| 1008 | CKV_K8S_19 | resource | ReplicationController | Containers should not share the host network namespace | Kubernetes |
+| 1009 | CKV_K8S_19 | resource | Job | Containers should not share the host network namespace | Kubernetes |
+| 1010 | CKV_K8S_19 | resource | CronJob | Containers should not share the host network namespace | Kubernetes |
+| 1011 | CKV_K8S_20 | resource | containers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 1012 | CKV_K8S_20 | resource | initContainers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 1013 | CKV_K8S_21 | resource | Service | The default namespace should not be used | Kubernetes |
+| 1014 | CKV_K8S_21 | resource | Pod | The default namespace should not be used | Kubernetes |
+| 1015 | CKV_K8S_21 | resource | Deployment | The default namespace should not be used | Kubernetes |
+| 1016 | CKV_K8S_21 | resource | DaemonSet | The default namespace should not be used | Kubernetes |
+| 1017 | CKV_K8S_21 | resource | StatefulSet | The default namespace should not be used | Kubernetes |
+| 1018 | CKV_K8S_21 | resource | ReplicaSet | The default namespace should not be used | Kubernetes |
+| 1019 | CKV_K8S_21 | resource | ReplicationController | The default namespace should not be used | Kubernetes |
+| 1020 | CKV_K8S_21 | resource | Job | The default namespace should not be used | Kubernetes |
+| 1021 | CKV_K8S_21 | resource | CronJob | The default namespace should not be used | Kubernetes |
+| 1022 | CKV_K8S_21 | resource | Secret | The default namespace should not be used | Kubernetes |
+| 1023 | CKV_K8S_21 | resource | ServiceAccount | The default namespace should not be used | Kubernetes |
+| 1024 | CKV_K8S_21 | resource | Role | The default namespace should not be used | Kubernetes |
+| 1025 | CKV_K8S_21 | resource | RoleBinding | The default namespace should not be used | Kubernetes |
+| 1026 | CKV_K8S_21 | resource | ConfigMap | The default namespace should not be used | Kubernetes |
+| 1027 | CKV_K8S_21 | resource | Ingress | The default namespace should not be used | Kubernetes |
+| 1028 | CKV_K8S_22 | resource | containers | Use read-only filesystem for containers where possible | Kubernetes |
+| 1029 | CKV_K8S_22 | resource | initContainers | Use read-only filesystem for containers where possible | Kubernetes |
+| 1030 | CKV_K8S_23 | resource | Pod | Minimize the admission of root containers | Kubernetes |
+| 1031 | CKV_K8S_23 | resource | Deployment | Minimize the admission of root containers | Kubernetes |
+| 1032 | CKV_K8S_23 | resource | DaemonSet | Minimize the admission of root containers | Kubernetes |
+| 1033 | CKV_K8S_23 | resource | StatefulSet | Minimize the admission of root containers | Kubernetes |
+| 1034 | CKV_K8S_23 | resource | ReplicaSet | Minimize the admission of root containers | Kubernetes |
+| 1035 | CKV_K8S_23 | resource | ReplicationController | Minimize the admission of root containers | Kubernetes |
+| 1036 | CKV_K8S_23 | resource | Job | Minimize the admission of root containers | Kubernetes |
+| 1037 | CKV_K8S_23 | resource | CronJob | Minimize the admission of root containers | Kubernetes |
+| 1038 | CKV_K8S_24 | resource | PodSecurityPolicy | Do not allow containers with added capability | Kubernetes |
+| 1039 | CKV_K8S_25 | resource | containers | Minimize the admission of containers with added capability | Kubernetes |
+| 1040 | CKV_K8S_25 | resource | initContainers | Minimize the admission of containers with added capability | Kubernetes |
+| 1041 | CKV_K8S_26 | resource | containers | Do not specify hostPort unless absolutely necessary | Kubernetes |
+| 1042 | CKV_K8S_26 | resource | initContainers | Do not specify hostPort unless absolutely necessary | Kubernetes |
+| 1043 | CKV_K8S_27 | resource | Pod | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1044 | CKV_K8S_27 | resource | Deployment | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1045 | CKV_K8S_27 | resource | DaemonSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1046 | CKV_K8S_27 | resource | StatefulSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1047 | CKV_K8S_27 | resource | ReplicaSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1048 | CKV_K8S_27 | resource | ReplicationController | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1049 | CKV_K8S_27 | resource | Job | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1050 | CKV_K8S_27 | resource | CronJob | Do not expose the docker daemon socket to containers | Kubernetes |
+| 1051 | CKV_K8S_28 | resource | containers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
+| 1052 | CKV_K8S_28 | resource | initContainers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
+| 1053 | CKV_K8S_29 | resource | Pod | Apply security context to your pods and containers | Kubernetes |
+| 1054 | CKV_K8S_29 | resource | Deployment | Apply security context to your pods and containers | Kubernetes |
+| 1055 | CKV_K8S_29 | resource | DaemonSet | Apply security context to your pods and containers | Kubernetes |
+| 1056 | CKV_K8S_29 | resource | StatefulSet | Apply security context to your pods and containers | Kubernetes |
+| 1057 | CKV_K8S_29 | resource | ReplicaSet | Apply security context to your pods and containers | Kubernetes |
+| 1058 | CKV_K8S_29 | resource | ReplicationController | Apply security context to your pods and containers | Kubernetes |
+| 1059 | CKV_K8S_29 | resource | Job | Apply security context to your pods and containers | Kubernetes |
+| 1060 | CKV_K8S_29 | resource | CronJob | Apply security context to your pods and containers | Kubernetes |
+| 1061 | CKV_K8S_30 | resource | containers | Apply security context to your pods and containers | Kubernetes |
+| 1062 | CKV_K8S_30 | resource | initContainers | Apply security context to your pods and containers | Kubernetes |
+| 1063 | CKV_K8S_31 | resource | Pod | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1064 | CKV_K8S_31 | resource | Deployment | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1065 | CKV_K8S_31 | resource | DaemonSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1066 | CKV_K8S_31 | resource | StatefulSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1067 | CKV_K8S_31 | resource | ReplicaSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1068 | CKV_K8S_31 | resource | ReplicationController | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1069 | CKV_K8S_31 | resource | Job | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1070 | CKV_K8S_31 | resource | CronJob | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 1071 | CKV_K8S_32 | resource | PodSecurityPolicy | Ensure default seccomp profile set to docker/default or runtime/default | Kubernetes |
+| 1072 | CKV_K8S_33 | resource | containers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
+| 1073 | CKV_K8S_33 | resource | initContainers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
+| 1074 | CKV_K8S_34 | resource | containers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
+| 1075 | CKV_K8S_34 | resource | initContainers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
+| 1076 | CKV_K8S_35 | resource | containers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
+| 1077 | CKV_K8S_35 | resource | initContainers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
+| 1078 | CKV_K8S_36 | resource | PodSecurityPolicy | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 1079 | CKV_K8S_37 | resource | containers | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 1080 | CKV_K8S_37 | resource | initContainers | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 1081 | CKV_K8S_38 | resource | Pod | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1082 | CKV_K8S_38 | resource | Deployment | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1083 | CKV_K8S_38 | resource | DaemonSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1084 | CKV_K8S_38 | resource | StatefulSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1085 | CKV_K8S_38 | resource | ReplicaSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1086 | CKV_K8S_38 | resource | ReplicationController | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1087 | CKV_K8S_38 | resource | Job | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1088 | CKV_K8S_38 | resource | CronJob | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 1089 | CKV_K8S_39 | resource | containers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
+| 1090 | CKV_K8S_39 | resource | initContainers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
+| 1091 | CKV_K8S_40 | resource | Pod | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1092 | CKV_K8S_40 | resource | Deployment | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1093 | CKV_K8S_40 | resource | DaemonSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1094 | CKV_K8S_40 | resource | StatefulSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1095 | CKV_K8S_40 | resource | ReplicaSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1096 | CKV_K8S_40 | resource | ReplicationController | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1097 | CKV_K8S_40 | resource | Job | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1098 | CKV_K8S_40 | resource | CronJob | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 1099 | CKV_K8S_41 | resource | ServiceAccount | Ensure that default service accounts are not actively used | Kubernetes |
+| 1100 | CKV_K8S_42 | resource | RoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
+| 1101 | CKV_K8S_42 | resource | ClusterRoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
+| 1102 | CKV_K8S_43 | resource | containers | Image should use digest | Kubernetes |
+| 1103 | CKV_K8S_43 | resource | initContainers | Image should use digest | Kubernetes |
+| 1104 | CKV_K8S_44 | resource | Service | Ensure that the Tiller Service (Helm v2) is deleted | Kubernetes |
+| 1105 | CKV_K8S_45 | resource | containers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
+| 1106 | CKV_K8S_45 | resource | initContainers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
+| 1107 | CKV_K8S_49 | resource | Role | Minimize wildcard use in Roles and ClusterRoles | Kubernetes |
+| 1108 | CKV_K8S_49 | resource | ClusterRole | Minimize wildcard use in Roles and ClusterRoles | Kubernetes |
+| 1109 | CKV_K8S_68 | resource | containers | Ensure that the --anonymous-auth argument is set to false | Kubernetes |
+| 1110 | CKV_K8S_69 | resource | containers | Ensure that the --basic-auth-file argument is not set | Kubernetes |
+| 1111 | CKV_K8S_70 | resource | containers | Ensure that the --token-auth-file argument is not set | Kubernetes |
+| 1112 | CKV_K8S_71 | resource | containers | Ensure that the --kubelet-https argument is set to true | Kubernetes |
+| 1113 | CKV_K8S_72 | resource | containers | Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate | Kubernetes |
+| 1114 | CKV_K8S_73 | resource | containers | Ensure that the --kubelet-certificate-authority argument is set as appropriate | Kubernetes |
+| 1115 | CKV_K8S_74 | resource | containers | Ensure that the --authorization-mode argument is not set to AlwaysAllow | Kubernetes |
+| 1116 | CKV_K8S_75 | resource | containers | Ensure that the --authorization-mode argument includes Node | Kubernetes |
+| 1117 | CKV_K8S_77 | resource | containers | Ensure that the --authorization-mode argument includes RBAC | Kubernetes |
+| 1118 | CKV_K8S_78 | resource | AdmissionConfiguration | Ensure that the admission control plugin EventRateLimit is set | Kubernetes |
+| 1119 | CKV_K8S_79 | resource | containers | Ensure that the admission control plugin AlwaysAdmit is not set | Kubernetes |
+| 1120 | CKV_K8S_80 | resource | containers | Ensure that the admission control plugin AlwaysPullImages is set | Kubernetes |
+| 1121 | CKV_K8S_81 | resource | containers | Ensure that the admission control plugin SecurityContextDeny is set if PodSecurityPolicy is not used | Kubernetes |
+| 1122 | CKV_K8S_82 | resource | containers | Ensure that the admission control plugin ServiceAccount is set | Kubernetes |
+| 1123 | CKV_K8S_83 | resource | containers | Ensure that the admission control plugin NamespaceLifecycle is set | Kubernetes |
+| 1124 | CKV_K8S_84 | resource | containers | Ensure that the admission control plugin PodSecurityPolicy is set | Kubernetes |
+| 1125 | CKV_K8S_85 | resource | containers | Ensure that the admission control plugin NodeRestriction is set | Kubernetes |
+| 1126 | CKV_K8S_86 | resource | containers | Ensure that the --insecure-bind-address argument is not set | Kubernetes |
+| 1127 | CKV_K8S_88 | resource | containers | Ensure that the --insecure-port argument is set to 0 | Kubernetes |
+| 1128 | CKV_K8S_89 | resource | containers | Ensure that the --secure-port argument is not set to 0 | Kubernetes |
+| 1129 | CKV_K8S_90 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 1130 | CKV_K8S_91 | resource | containers | Ensure that the --audit-log-path argument is set | Kubernetes |
+| 1131 | CKV_K8S_92 | resource | containers | Ensure that the --audit-log-maxage argument is set to 30 or as appropriate | Kubernetes |
+| 1132 | CKV_K8S_93 | resource | containers | Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate | Kubernetes |
+| 1133 | CKV_K8S_94 | resource | containers | Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate | Kubernetes |
+| 1134 | CKV_K8S_95 | resource | containers | Ensure that the --request-timeout argument is set as appropriate | Kubernetes |
+| 1135 | CKV_K8S_96 | resource | containers | Ensure that the --service-account-lookup argument is set to true | Kubernetes |
+| 1136 | CKV_K8S_97 | resource | containers | Ensure that the --service-account-key-file argument is set as appropriate | Kubernetes |
+| 1137 | CKV_K8S_99 | resource | containers | Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate | Kubernetes |
+| 1138 | CKV_K8S_100 | resource | containers | Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate | Kubernetes |
+| 1139 | CKV_K8S_102 | resource | containers | Ensure that the --etcd-ca-file argument is set as appropriate | Kubernetes |
+| 1140 | CKV_K8S_104 | resource | containers | Ensure that encryption providers are appropriately configured | Kubernetes |
+| 1141 | CKV_K8S_105 | resource | containers | Ensure that the API Server only makes use of Strong Cryptographic Ciphers | Kubernetes |
+| 1142 | CKV_K8S_106 | resource | containers | Ensure that the --terminated-pod-gc-threshold argument is set as appropriate | Kubernetes |
+| 1143 | CKV_K8S_107 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 1144 | CKV_K8S_108 | resource | containers | Ensure that the --use-service-account-credentials argument is set to true | Kubernetes |
+| 1145 | CKV_K8S_110 | resource | containers | Ensure that the --service-account-private-key-file argument is set as appropriate | Kubernetes |
+| 1146 | CKV_K8S_111 | resource | containers | Ensure that the --root-ca-file argument is set as appropriate | Kubernetes |
+| 1147 | CKV_K8S_112 | resource | containers | Ensure that the RotateKubeletServerCertificate argument is set to true | Kubernetes |
+| 1148 | CKV_K8S_113 | resource | containers | Ensure that the --bind-address argument is set to 127.0.0.1 | Kubernetes |
+| 1149 | CKV_K8S_114 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 1150 | CKV_K8S_115 | resource | containers | Ensure that the --bind-address argument is set to 127.0.0.1 | Kubernetes |
+| 1151 | CKV_K8S_116 | resource | containers | Ensure that the --cert-file and --key-file arguments are set as appropriate | Kubernetes |
+| 1152 | CKV_K8S_117 | resource | containers | Ensure that the --client-cert-auth argument is set to true | Kubernetes |
+| 1153 | CKV_K8S_118 | resource | containers | Ensure that the --auto-tls argument is not set to true | Kubernetes |
+| 1154 | CKV_K8S_119 | resource | containers | Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate | Kubernetes |
+| 1155 | CKV_K8S_121 | resource | Pod | Ensure that the --peer-client-cert-auth argument is set to true | Kubernetes |
+| 1156 | CKV_K8S_138 | resource | containers | Ensure that the --anonymous-auth argument is set to false | Kubernetes |
+| 1157 | CKV_K8S_139 | resource | containers | Ensure that the --authorization-mode argument is not set to AlwaysAllow | Kubernetes |
+| 1158 | CKV_K8S_140 | resource | containers | Ensure that the --client-ca-file argument is set as appropriate | Kubernetes |
+| 1159 | CKV_K8S_141 | resource | containers | Ensure that the --read-only-port argument is set to 0 | Kubernetes |
+| 1160 | CKV_K8S_143 | resource | containers | Ensure that the --streaming-connection-idle-timeout argument is not set to 0 | Kubernetes |
+| 1161 | CKV_K8S_144 | resource | containers | Ensure that the --protect-kernel-defaults argument is set to true | Kubernetes |
+| 1162 | CKV_K8S_145 | resource | containers | Ensure that the --make-iptables-util-chains argument is set to true | Kubernetes |
+| 1163 | CKV_K8S_146 | resource | containers | Ensure that the --hostname-override argument is not set | Kubernetes |
+| 1164 | CKV_K8S_147 | resource | containers | Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture | Kubernetes |
+| 1165 | CKV_K8S_148 | resource | containers | Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate | Kubernetes |
+| 1166 | CKV_K8S_149 | resource | containers | Ensure that the --rotate-certificates argument is not set to false | Kubernetes |
+| 1167 | CKV_K8S_150 | resource | containers | Ensure that the RotateKubeletServerCertificate argument is set to true | Kubernetes |
+| 1168 | CKV_K8S_151 | resource | containers | Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers | Kubernetes |
+| 1169 | CKV_LIN_1 | provider | linode | Ensure no hard coded Linode tokens exist in provider | Terraform |
+| 1170 | CKV_LIN_2 | resource | linode_instance | Ensure SSH key set in authorized_keys | Terraform |
+| 1171 | CKV_SECRET_1 | Artifactory Credentials | secrets | Artifactory Credentials | Artifactory Credentials |
+| 1172 | CKV_SECRET_2 | AWS Access Key | secrets | AWS Access Key | AWS Access Key |
+| 1173 | CKV_SECRET_3 | Azure Storage Account access key | secrets | Azure Storage Account access key | Azure Storage Account access key |
+| 1174 | CKV_SECRET_4 | Basic Auth Credentials | secrets | Basic Auth Credentials | Basic Auth Credentials |
+| 1175 | CKV_SECRET_5 | Cloudant Credentials | secrets | Cloudant Credentials | Cloudant Credentials |
+| 1176 | CKV_SECRET_6 | Base64 High Entropy String | secrets | Base64 High Entropy String | Base64 High Entropy String |
+| 1177 | CKV_SECRET_7 | IBM Cloud IAM Key | secrets | IBM Cloud IAM Key | IBM Cloud IAM Key |
+| 1178 | CKV_SECRET_8 | IBM COS HMAC Credentials | secrets | IBM COS HMAC Credentials | IBM COS HMAC Credentials |
+| 1179 | CKV_SECRET_9 | JSON Web Token | secrets | JSON Web Token | JSON Web Token |
+| 1180 | CKV_SECRET_11 | Mailchimp Access Key | secrets | Mailchimp Access Key | Mailchimp Access Key |
+| 1181 | CKV_SECRET_12 | NPM tokens | secrets | NPM tokens | NPM tokens |
+| 1182 | CKV_SECRET_13 | Private Key | secrets | Private Key | Private Key |
+| 1183 | CKV_SECRET_14 | Slack Token | secrets | Slack Token | Slack Token |
+| 1184 | CKV_SECRET_15 | SoftLayer Credentials | secrets | SoftLayer Credentials | SoftLayer Credentials |
+| 1185 | CKV_SECRET_16 | Square OAuth Secret | secrets | Square OAuth Secret | Square OAuth Secret |
+| 1186 | CKV_SECRET_17 | Stripe Access Key | secrets | Stripe Access Key | Stripe Access Key |
+| 1187 | CKV_SECRET_18 | Twilio API Key | secrets | Twilio API Key | Twilio API Key |
+| 1188 | CKV_SECRET_19 | Hex High Entropy String | secrets | Hex High Entropy String | Hex High Entropy String |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/arm.md b/docs/5.Policy Index/arm.md
new file mode 100644
index 0000000000..a8a8db5ad7
--- /dev/null
+++ b/docs/5.Policy Index/arm.md
@@ -0,0 +1,68 @@
+---
+layout: default
+title: arm resource scans
+nav_order: 1
+---
+
+# arm resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|----|---------------|-----------|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|-------|
+| 0 | CKV_AZURE_1 | resource | Microsoft.Compute/virtualMachines | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | arm |
+| 1 | CKV_AZURE_2 | resource | Microsoft.Compute/disks | Ensure Azure managed disk have encryption enabled | arm |
+| 2 | CKV_AZURE_3 | resource | Microsoft.Storage/storageAccounts | Ensure that 'supportsHttpsTrafficOnly' is set to 'true' | arm |
+| 3 | CKV_AZURE_4 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS logging to Azure Monitoring is Configured | arm |
+| 4 | CKV_AZURE_5 | resource | Microsoft.ContainerService/managedClusters | Ensure RBAC is enabled on AKS clusters | arm |
+| 5 | CKV_AZURE_6 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS has an API Server Authorized IP Ranges enabled | arm |
+| 6 | CKV_AZURE_7 | resource | Microsoft.ContainerService/managedClusters | Ensure AKS cluster has Network Policy configured | arm |
+| 7 | CKV_AZURE_8 | resource | Microsoft.ContainerService/managedClusters | Ensure Kubernetes Dashboard is disabled | arm |
+| 8 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups | Ensure that RDP access is restricted from the internet | arm |
+| 9 | CKV_AZURE_9 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that RDP access is restricted from the internet | arm |
+| 10 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups | Ensure that SSH access is restricted from the internet | arm |
+| 11 | CKV_AZURE_10 | resource | Microsoft.Network/networkSecurityGroups/securityRules | Ensure that SSH access is restricted from the internet | arm |
+| 12 | CKV_AZURE_11 | resource | Microsoft.Sql/servers | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | arm |
+| 13 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 14 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 15 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/flowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 16 | CKV_AZURE_12 | resource | Microsoft.Network/networkWatchers/FlowLogs/ | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | arm |
+| 17 | CKV_AZURE_13 | resource | Microsoft.Web/sites/config | Ensure App Service Authentication is set on Azure App Service | arm |
+| 18 | CKV_AZURE_13 | resource | config | Ensure App Service Authentication is set on Azure App Service | arm |
+| 19 | CKV_AZURE_14 | resource | Microsoft.Web/sites | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | arm |
+| 20 | CKV_AZURE_15 | resource | Microsoft.Web/sites | Ensure web app is using the latest version of TLS encryption | arm |
+| 21 | CKV_AZURE_16 | resource | Microsoft.Web/sites | Ensure that Register with Azure Active Directory is enabled on App Service | arm |
+| 22 | CKV_AZURE_17 | resource | Microsoft.Web/sites | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | arm |
+| 23 | CKV_AZURE_18 | resource | Microsoft.Web/sites | Ensure that 'HTTP Version' is the latest if used to run the web app | arm |
+| 24 | CKV_AZURE_19 | resource | Microsoft.Security/pricings | Ensure that standard pricing tier is selected | arm |
+| 25 | CKV_AZURE_20 | resource | Microsoft.Security/securityContacts | Ensure that security contact 'Phone number' is set | arm |
+| 26 | CKV_AZURE_21 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
+| 27 | CKV_AZURE_22 | resource | Microsoft.Security/securityContacts | Ensure that 'Send email notification for high severity alerts' is set to 'On' | arm |
+| 28 | CKV_AZURE_23 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' is set to 'Enabled' for SQL servers | arm |
+| 29 | CKV_AZURE_24 | resource | Microsoft.Sql/servers | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | arm |
+| 30 | CKV_AZURE_25 | resource | Microsoft.Sql/servers/databases | Ensure that 'Threat Detection types' is set to 'All' | arm |
+| 31 | CKV_AZURE_26 | resource | Microsoft.Sql/servers/databases | Ensure that 'Send Alerts To' is enabled for MSSQL servers | arm |
+| 32 | CKV_AZURE_27 | resource | Microsoft.Sql/servers/databases | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | arm |
+| 33 | CKV_AZURE_28 | resource | Microsoft.DBforMySQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | arm |
+| 34 | CKV_AZURE_29 | resource | Microsoft.DBforPostgreSQL/servers | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | arm |
+| 35 | CKV_AZURE_30 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
+| 36 | CKV_AZURE_30 | resource | configurations | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | arm |
+| 37 | CKV_AZURE_31 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
+| 38 | CKV_AZURE_31 | resource | configurations | Ensure configuration 'log_connections' is set to 'ON' for PostgreSQL Database Server | arm |
+| 39 | CKV_AZURE_32 | resource | Microsoft.DBforPostgreSQL/servers/configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
+| 40 | CKV_AZURE_32 | resource | configurations | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | arm |
+| 41 | CKV_AZURE_33 | resource | Microsoft.Storage/storageAccounts/queueServices/providers/diagnosticsettings | Ensure Storage logging is enabled for Queue service for read, write and delete requests | arm |
+| 42 | CKV_AZURE_34 | resource | Microsoft.Storage/storageAccounts/blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 43 | CKV_AZURE_34 | resource | containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 44 | CKV_AZURE_34 | resource | blobServices/containers | Ensure that 'Public access level' is set to Private for blob containers | arm |
+| 45 | CKV_AZURE_35 | resource | Microsoft.Storage/storageAccounts | Ensure default network access rule for Storage Accounts is set to deny | arm |
+| 46 | CKV_AZURE_36 | resource | Microsoft.Storage/storageAccounts | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | arm |
+| 47 | CKV_AZURE_37 | resource | microsoft.insights/logprofiles | Ensure that Activity Log Retention is set 365 days or greater | arm |
+| 48 | CKV_AZURE_38 | resource | microsoft.insights/logprofiles | Ensure audit profile captures all the activities | arm |
+| 49 | CKV_AZURE_39 | resource | Microsoft.Authorization/roleDefinitions | Ensure that no custom subscription owner roles are created | arm |
+| 50 | CKV_AZURE_41 | resource | Microsoft.KeyVault/vaults/secrets | Ensure that the expiration date is set on all secrets | arm |
+| 51 | CKV_AZURE_42 | resource | Microsoft.KeyVault/vaults | Ensure the key vault is recoverable | arm |
+| 52 | CKV_AZURE_131 | parameter | secureString | SecureString parameter should not have hardcoded default values | arm |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/cloudformation.md b/docs/5.Policy Index/cloudformation.md
new file mode 100644
index 0000000000..f3e710f8cc
--- /dev/null
+++ b/docs/5.Policy Index/cloudformation.md
@@ -0,0 +1,138 @@
+---
+layout: default
+title: cloudformation resource scans
+nav_order: 1
+---
+
+# cloudformation resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|-----|-------------|----------|-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
+| 0 | CKV_AWS_2 | resource | AWS::ElasticLoadBalancingV2::Listener | Ensure ALB protocol is HTTPS | Cloudformation |
+| 1 | CKV_AWS_3 | resource | AWS::EC2::Volume | Ensure all data stored in the EBS is securely encrypted | Cloudformation |
+| 2 | CKV_AWS_5 | resource | AWS::Elasticsearch::Domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Cloudformation |
+| 3 | CKV_AWS_6 | resource | AWS::Elasticsearch::Domain | Ensure all Elasticsearch has node-to-node encryption enabled | Cloudformation |
+| 4 | CKV_AWS_7 | resource | AWS::KMS::Key | Ensure rotation for customer created CMKs is enabled | Cloudformation |
+| 5 | CKV_AWS_8 | resource | AWS::AutoScaling::LaunchConfiguration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Cloudformation |
+| 6 | CKV_AWS_16 | resource | AWS::RDS::DBInstance | Ensure all data stored in the RDS is securely encrypted at rest | Cloudformation |
+| 7 | CKV_AWS_17 | resource | AWS::RDS::DBInstance | Ensure all data stored in RDS is not publicly accessible | Cloudformation |
+| 8 | CKV_AWS_18 | resource | AWS::S3::Bucket | Ensure the S3 bucket has access logging enabled | Cloudformation |
+| 9 | CKV_AWS_19 | resource | AWS::S3::Bucket | Ensure the S3 bucket has server-side-encryption enabled | Cloudformation |
+| 10 | CKV_AWS_20 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow READ permissions to everyone | Cloudformation |
+| 11 | CKV_AWS_21 | resource | AWS::S3::Bucket | Ensure the S3 bucket has versioning enabled | Cloudformation |
+| 12 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroup | Ensure every security groups rule has a description | Cloudformation |
+| 13 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupIngress | Ensure every security groups rule has a description | Cloudformation |
+| 14 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupEgress | Ensure every security groups rule has a description | Cloudformation |
+| 15 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 16 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 17 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 18 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 19 | CKV_AWS_26 | resource | AWS::SNS::Topic | Ensure all data stored in the SNS topic is encrypted | Cloudformation |
+| 20 | CKV_AWS_27 | resource | AWS::SQS::Queue | Ensure all data stored in the SQS queue is encrypted | Cloudformation |
+| 21 | CKV_AWS_28 | resource | AWS::DynamoDB::Table | Ensure Dynamodb point in time recovery (backup) is enabled | Cloudformation |
+| 22 | CKV_AWS_29 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Cloudformation |
+| 23 | CKV_AWS_30 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Cloudformation |
+| 24 | CKV_AWS_31 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Cloudformation |
+| 25 | CKV_AWS_32 | resource | AWS::ECR::Repository | Ensure ECR policy is not set to public | Cloudformation |
+| 26 | CKV_AWS_33 | resource | AWS::KMS::Key | Ensure KMS key policy does not contain wildcard (*) principal | Cloudformation |
+| 27 | CKV_AWS_34 | resource | AWS::CloudFront::Distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Cloudformation |
+| 28 | CKV_AWS_35 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Cloudformation |
+| 29 | CKV_AWS_36 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail log file validation is enabled | Cloudformation |
+| 30 | CKV_AWS_40 | resource | AWS::IAM::Policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Cloudformation |
+| 31 | CKV_AWS_42 | resource | AWS::EFS::FileSystem | Ensure EFS is securely encrypted | Cloudformation |
+| 32 | CKV_AWS_43 | resource | AWS::Kinesis::Stream | Ensure Kinesis Stream is securely encrypted | Cloudformation |
+| 33 | CKV_AWS_44 | resource | AWS::Neptune::DBCluster | Ensure Neptune storage is securely encrypted | Cloudformation |
+| 34 | CKV_AWS_45 | resource | AWS::Lambda::Function | Ensure no hard-coded secrets exist in lambda environment | Cloudformation |
+| 35 | CKV_AWS_46 | resource | AWS::EC2::Instance | Ensure no hard-coded secrets exist in EC2 user data | Cloudformation |
+| 36 | CKV_AWS_47 | resource | AWS::DAX::Cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Cloudformation |
+| 37 | CKV_AWS_51 | resource | AWS::ECR::Repository | Ensure ECR Image Tags are immutable | Cloudformation |
+| 38 | CKV_AWS_53 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public ACLS enabled | Cloudformation |
+| 39 | CKV_AWS_54 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public policy enabled | Cloudformation |
+| 40 | CKV_AWS_55 | resource | AWS::S3::Bucket | Ensure S3 bucket has ignore public ACLs enabled | Cloudformation |
+| 41 | CKV_AWS_56 | resource | AWS::S3::Bucket | Ensure S3 bucket has 'restrict_public_bucket' enabled | Cloudformation |
+| 42 | CKV_AWS_57 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow WRITE permissions to everyone | Cloudformation |
+| 43 | CKV_AWS_58 | resource | AWS::EKS::Cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Cloudformation |
+| 44 | CKV_AWS_59 | resource | AWS::ApiGateway::Method | Ensure there is no open access to back-end resources through API | Cloudformation |
+| 45 | CKV_AWS_60 | resource | AWS::IAM::Role | Ensure IAM role allows only specific services or principals to assume it | Cloudformation |
+| 46 | CKV_AWS_61 | resource | AWS::IAM::Role | Ensure IAM role allows only specific principals in account to assume it | Cloudformation |
+| 47 | CKV_AWS_64 | resource | AWS::Redshift::Cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Cloudformation |
+| 48 | CKV_AWS_65 | resource | AWS::ECS::Cluster | Ensure container insights are enabled on ECS cluster | Cloudformation |
+| 49 | CKV_AWS_66 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group specifies retention days | Cloudformation |
+| 50 | CKV_AWS_67 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail is enabled in all Regions | Cloudformation |
+| 51 | CKV_AWS_68 | resource | AWS::CloudFront::Distribution | CloudFront Distribution should have WAF enabled | Cloudformation |
+| 52 | CKV_AWS_69 | resource | AWS::AmazonMQ::Broker | Ensure Amazon MQ Broker should not have public access | Cloudformation |
+| 53 | CKV_AWS_71 | resource | AWS::Redshift::Cluster | Ensure Redshift Cluster logging is enabled | Cloudformation |
+| 54 | CKV_AWS_73 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has X-Ray Tracing enabled | Cloudformation |
+| 55 | CKV_AWS_74 | resource | AWS::DocDB::DBCluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Cloudformation |
+| 56 | CKV_AWS_76 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has Access Logging enabled | Cloudformation |
+| 57 | CKV_AWS_78 | resource | AWS::CodeBuild::Project | Ensure that CodeBuild Project encryption is not disabled | Cloudformation |
+| 58 | CKV_AWS_79 | resource | AWS::EC2::LaunchTemplate | Ensure Instance Metadata Service Version 1 is not enabled | Cloudformation |
+| 59 | CKV_AWS_82 | resource | AWS::Athena::WorkGroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Cloudformation |
+| 60 | CKV_AWS_83 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain enforces HTTPS | Cloudformation |
+| 61 | CKV_AWS_84 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain Logging is enabled | Cloudformation |
+| 62 | CKV_AWS_85 | resource | AWS::DocDB::DBCluster | Ensure DocDB Logging is enabled | Cloudformation |
+| 63 | CKV_AWS_86 | resource | AWS::CloudFront::Distribution | Ensure Cloudfront distribution has Access Logging enabled | Cloudformation |
+| 64 | CKV_AWS_87 | resource | AWS::Redshift::Cluster | Redshift cluster should not be publicly accessible | Cloudformation |
+| 65 | CKV_AWS_88 | resource | AWS::EC2::LaunchTemplate | EC2 instance should not have public IP. | Cloudformation |
+| 66 | CKV_AWS_88 | resource | AWS::EC2::Instance | EC2 instance should not have public IP. | Cloudformation |
+| 67 | CKV_AWS_89 | resource | AWS::DMS::ReplicationInstance | DMS replication instance should not be publicly accessible | Cloudformation |
+| 68 | CKV_AWS_90 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB TLS is not disabled | Cloudformation |
+| 69 | CKV_AWS_91 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure the ELBv2 (Application/Network) has access logging enabled | Cloudformation |
+| 70 | CKV_AWS_92 | resource | AWS::ElasticLoadBalancing::LoadBalancer | Ensure the ELB has access logging enabled | Cloudformation |
+| 71 | CKV_AWS_94 | resource | AWS::Glue::DataCatalogEncryptionSettings | Ensure Glue Data Catalog Encryption is enabled | Cloudformation |
+| 72 | CKV_AWS_95 | resource | AWS::ApiGatewayV2::Stage | Ensure API Gateway V2 has Access Logging enabled | Cloudformation |
+| 73 | CKV_AWS_96 | resource | AWS::RDS::DBCluster | Ensure all data stored in Aurrora is securely encrypted at rest | Cloudformation |
+| 74 | CKV_AWS_97 | resource | AWS::ECS::TaskDefinition | Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions | Cloudformation |
+| 75 | CKV_AWS_99 | resource | AWS::Glue::SecurityConfiguration | Ensure Glue Security Configuration Encryption is enabled | Cloudformation |
+| 76 | CKV_AWS_100 | resource | AWS::EKS::Nodegroup | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Cloudformation |
+| 77 | CKV_AWS_101 | resource | AWS::Neptune::DBCluster | Ensure Neptune logging is enabled | Cloudformation |
+| 78 | CKV_AWS_104 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB has audit logs enabled | Cloudformation |
+| 79 | CKV_AWS_105 | resource | AWS::Redshift::ClusterParameterGroup | Ensure Redshift uses SSL | Cloudformation |
+| 80 | CKV_AWS_107 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 81 | CKV_AWS_107 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 82 | CKV_AWS_107 | resource | AWS::IAM::Group | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 83 | CKV_AWS_107 | resource | AWS::IAM::Role | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 84 | CKV_AWS_107 | resource | AWS::IAM::User | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 85 | CKV_AWS_108 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 86 | CKV_AWS_108 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 87 | CKV_AWS_108 | resource | AWS::IAM::Group | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 88 | CKV_AWS_108 | resource | AWS::IAM::Role | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 89 | CKV_AWS_108 | resource | AWS::IAM::User | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 90 | CKV_AWS_109 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 91 | CKV_AWS_109 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 92 | CKV_AWS_109 | resource | AWS::IAM::Group | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 93 | CKV_AWS_109 | resource | AWS::IAM::Role | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 94 | CKV_AWS_109 | resource | AWS::IAM::User | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 95 | CKV_AWS_110 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 96 | CKV_AWS_110 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 97 | CKV_AWS_110 | resource | AWS::IAM::Group | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 98 | CKV_AWS_110 | resource | AWS::IAM::Role | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 99 | CKV_AWS_110 | resource | AWS::IAM::User | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 100 | CKV_AWS_111 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 101 | CKV_AWS_111 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 102 | CKV_AWS_111 | resource | AWS::IAM::Group | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 103 | CKV_AWS_111 | resource | AWS::IAM::Role | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 104 | CKV_AWS_111 | resource | AWS::IAM::User | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 105 | CKV_AWS_120 | resource | AWS::ApiGateway::Stage | Ensure API Gateway caching is enabled | Cloudformation |
+| 106 | CKV_AWS_123 | resource | AWS::EC2::VPCEndpointService | Ensure that VPC Endpoint Service is configured for Manual Acceptance | Cloudformation |
+| 107 | CKV_AWS_131 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure that ALB drops HTTP headers | Cloudformation |
+| 108 | CKV_AWS_136 | resource | AWS::ECR::Repository | Ensure that ECR repositories are encrypted using KMS | Cloudformation |
+| 109 | CKV_AWS_154 | resource | AWS::Redshift::Cluster | Ensure Redshift is not deployed outside of a VPC | Cloudformation |
+| 110 | CKV_AWS_155 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace user volumes are encrypted | Cloudformation |
+| 111 | CKV_AWS_156 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace root volumes are encrypted | Cloudformation |
+| 112 | CKV_AWS_157 | resource | AWS::RDS::DBInstance | Ensure that RDS instances have Multi-AZ enabled | Cloudformation |
+| 113 | CKV_AWS_158 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group is encrypted by KMS | Cloudformation |
+| 114 | CKV_AWS_160 | resource | AWS::Timestream::Database | Ensure that Timestream database is encrypted with KMS CMK | Cloudformation |
+| 115 | CKV_AWS_161 | resource | AWS::RDS::DBInstance | Ensure RDS database has IAM authentication enabled | Cloudformation |
+| 116 | CKV_AWS_162 | resource | AWS::RDS::DBCluster | Ensure RDS cluster has IAM authentication enabled | Cloudformation |
+| 117 | CKV_AWS_163 | resource | AWS::ECR::Repository | Ensure ECR image scanning on push is enabled | Cloudformation |
+| 118 | CKV_AWS_164 | resource | AWS::Transfer::Server | Ensure Transfer Server is not exposed publicly. | Cloudformation |
+| 119 | CKV_AWS_165 | resource | AWS::DynamoDB::GlobalTable | Ensure Dynamodb global table point in time recovery (backup) is enabled | Cloudformation |
+| 120 | CKV_AWS_166 | resource | AWS::Backup::BackupVault | Ensure Backup Vault is encrypted at rest using KMS CMK | Cloudformation |
+| 121 | CKV_AWS_170 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger permissions mode is set to STANDARD | Cloudformation |
+| 122 | CKV_AWS_172 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger has deletion protection enabled | Cloudformation |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/dockerfile.md b/docs/5.Policy Index/dockerfile.md
new file mode 100644
index 0000000000..9ee8b73ed8
--- /dev/null
+++ b/docs/5.Policy Index/dockerfile.md
@@ -0,0 +1,23 @@
+---
+layout: default
+title: dockerfile resource scans
+nav_order: 1
+---
+
+# dockerfile resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|----|--------------|------------|------------|--------------------------------------------------------------------------|------------|
+| 0 | CKV_DOCKER_1 | dockerfile | EXPOSE | Ensure port 22 is not exposed | dockerfile |
+| 1 | CKV_DOCKER_2 | dockerfile | * | Ensure that HEALTHCHECK instructions have been added to container images | dockerfile |
+| 2 | CKV_DOCKER_3 | dockerfile | * | Ensure that a user for the container has been created | dockerfile |
+| 3 | CKV_DOCKER_4 | dockerfile | ADD | Ensure that COPY is used instead of ADD in Dockerfiles | dockerfile |
+| 4 | CKV_DOCKER_5 | dockerfile | RUN | Ensure update instructions are not use alone in the Dockerfile | dockerfile |
+| 5 | CKV_DOCKER_6 | dockerfile | MAINTAINER | Ensure that LABEL maintainer is used instead of MAINTAINER (deprecated) | dockerfile |
+| 6 | CKV_DOCKER_7 | dockerfile | FROM | Ensure the base image uses a non latest version tag | dockerfile |
+| 7 | CKV_DOCKER_8 | dockerfile | USER | Ensure the last USER is not root | dockerfile |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/kubernetes.md b/docs/5.Policy Index/kubernetes.md
new file mode 100644
index 0000000000..2c0beaec34
--- /dev/null
+++ b/docs/5.Policy Index/kubernetes.md
@@ -0,0 +1,220 @@
+---
+layout: default
+title: kubernetes resource scans
+nav_order: 1
+---
+
+# kubernetes resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|-----|-------------|----------|------------------------|--------------------------------------------------------------------------------------------------------|------------|
+| 0 | CKV_K8S_1 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host process ID namespace | Kubernetes |
+| 1 | CKV_K8S_2 | resource | PodSecurityPolicy | Do not admit privileged containers | Kubernetes |
+| 2 | CKV_K8S_3 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host IPC namespace | Kubernetes |
+| 3 | CKV_K8S_4 | resource | PodSecurityPolicy | Do not admit containers wishing to share the host network namespace | Kubernetes |
+| 4 | CKV_K8S_5 | resource | PodSecurityPolicy | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 5 | CKV_K8S_6 | resource | PodSecurityPolicy | Do not admit root containers | Kubernetes |
+| 6 | CKV_K8S_7 | resource | PodSecurityPolicy | Do not admit containers with the NET_RAW capability | Kubernetes |
+| 7 | CKV_K8S_8 | resource | containers | Liveness Probe Should be Configured | Kubernetes |
+| 8 | CKV_K8S_9 | resource | containers | Readiness Probe Should be Configured | Kubernetes |
+| 9 | CKV_K8S_10 | resource | containers | CPU requests should be set | Kubernetes |
+| 10 | CKV_K8S_10 | resource | initContainers | CPU requests should be set | Kubernetes |
+| 11 | CKV_K8S_11 | resource | containers | CPU limits should be set | Kubernetes |
+| 12 | CKV_K8S_11 | resource | initContainers | CPU limits should be set | Kubernetes |
+| 13 | CKV_K8S_12 | resource | containers | Memory requests should be set | Kubernetes |
+| 14 | CKV_K8S_12 | resource | initContainers | Memory requests should be set | Kubernetes |
+| 15 | CKV_K8S_13 | resource | containers | Memory limits should be set | Kubernetes |
+| 16 | CKV_K8S_13 | resource | initContainers | Memory limits should be set | Kubernetes |
+| 17 | CKV_K8S_14 | resource | containers | Image Tag should be fixed - not latest or blank | Kubernetes |
+| 18 | CKV_K8S_14 | resource | initContainers | Image Tag should be fixed - not latest or blank | Kubernetes |
+| 19 | CKV_K8S_15 | resource | containers | Image Pull Policy should be Always | Kubernetes |
+| 20 | CKV_K8S_15 | resource | initContainers | Image Pull Policy should be Always | Kubernetes |
+| 21 | CKV_K8S_16 | resource | containers | Container should not be privileged | Kubernetes |
+| 22 | CKV_K8S_16 | resource | initContainers | Container should not be privileged | Kubernetes |
+| 23 | CKV_K8S_17 | resource | Pod | Containers should not share the host process ID namespace | Kubernetes |
+| 24 | CKV_K8S_17 | resource | Deployment | Containers should not share the host process ID namespace | Kubernetes |
+| 25 | CKV_K8S_17 | resource | DaemonSet | Containers should not share the host process ID namespace | Kubernetes |
+| 26 | CKV_K8S_17 | resource | StatefulSet | Containers should not share the host process ID namespace | Kubernetes |
+| 27 | CKV_K8S_17 | resource | ReplicaSet | Containers should not share the host process ID namespace | Kubernetes |
+| 28 | CKV_K8S_17 | resource | ReplicationController | Containers should not share the host process ID namespace | Kubernetes |
+| 29 | CKV_K8S_17 | resource | Job | Containers should not share the host process ID namespace | Kubernetes |
+| 30 | CKV_K8S_17 | resource | CronJob | Containers should not share the host process ID namespace | Kubernetes |
+| 31 | CKV_K8S_18 | resource | Pod | Containers should not share the host IPC namespace | Kubernetes |
+| 32 | CKV_K8S_18 | resource | Deployment | Containers should not share the host IPC namespace | Kubernetes |
+| 33 | CKV_K8S_18 | resource | DaemonSet | Containers should not share the host IPC namespace | Kubernetes |
+| 34 | CKV_K8S_18 | resource | StatefulSet | Containers should not share the host IPC namespace | Kubernetes |
+| 35 | CKV_K8S_18 | resource | ReplicaSet | Containers should not share the host IPC namespace | Kubernetes |
+| 36 | CKV_K8S_18 | resource | ReplicationController | Containers should not share the host IPC namespace | Kubernetes |
+| 37 | CKV_K8S_18 | resource | Job | Containers should not share the host IPC namespace | Kubernetes |
+| 38 | CKV_K8S_18 | resource | CronJob | Containers should not share the host IPC namespace | Kubernetes |
+| 39 | CKV_K8S_19 | resource | Pod | Containers should not share the host network namespace | Kubernetes |
+| 40 | CKV_K8S_19 | resource | Deployment | Containers should not share the host network namespace | Kubernetes |
+| 41 | CKV_K8S_19 | resource | DaemonSet | Containers should not share the host network namespace | Kubernetes |
+| 42 | CKV_K8S_19 | resource | StatefulSet | Containers should not share the host network namespace | Kubernetes |
+| 43 | CKV_K8S_19 | resource | ReplicaSet | Containers should not share the host network namespace | Kubernetes |
+| 44 | CKV_K8S_19 | resource | ReplicationController | Containers should not share the host network namespace | Kubernetes |
+| 45 | CKV_K8S_19 | resource | Job | Containers should not share the host network namespace | Kubernetes |
+| 46 | CKV_K8S_19 | resource | CronJob | Containers should not share the host network namespace | Kubernetes |
+| 47 | CKV_K8S_20 | resource | containers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 48 | CKV_K8S_20 | resource | initContainers | Containers should not run with allowPrivilegeEscalation | Kubernetes |
+| 49 | CKV_K8S_21 | resource | Service | The default namespace should not be used | Kubernetes |
+| 50 | CKV_K8S_21 | resource | Pod | The default namespace should not be used | Kubernetes |
+| 51 | CKV_K8S_21 | resource | Deployment | The default namespace should not be used | Kubernetes |
+| 52 | CKV_K8S_21 | resource | DaemonSet | The default namespace should not be used | Kubernetes |
+| 53 | CKV_K8S_21 | resource | StatefulSet | The default namespace should not be used | Kubernetes |
+| 54 | CKV_K8S_21 | resource | ReplicaSet | The default namespace should not be used | Kubernetes |
+| 55 | CKV_K8S_21 | resource | ReplicationController | The default namespace should not be used | Kubernetes |
+| 56 | CKV_K8S_21 | resource | Job | The default namespace should not be used | Kubernetes |
+| 57 | CKV_K8S_21 | resource | CronJob | The default namespace should not be used | Kubernetes |
+| 58 | CKV_K8S_21 | resource | Secret | The default namespace should not be used | Kubernetes |
+| 59 | CKV_K8S_21 | resource | ServiceAccount | The default namespace should not be used | Kubernetes |
+| 60 | CKV_K8S_21 | resource | Role | The default namespace should not be used | Kubernetes |
+| 61 | CKV_K8S_21 | resource | RoleBinding | The default namespace should not be used | Kubernetes |
+| 62 | CKV_K8S_21 | resource | ConfigMap | The default namespace should not be used | Kubernetes |
+| 63 | CKV_K8S_21 | resource | Ingress | The default namespace should not be used | Kubernetes |
+| 64 | CKV_K8S_22 | resource | containers | Use read-only filesystem for containers where possible | Kubernetes |
+| 65 | CKV_K8S_22 | resource | initContainers | Use read-only filesystem for containers where possible | Kubernetes |
+| 66 | CKV_K8S_23 | resource | Pod | Minimize the admission of root containers | Kubernetes |
+| 67 | CKV_K8S_23 | resource | Deployment | Minimize the admission of root containers | Kubernetes |
+| 68 | CKV_K8S_23 | resource | DaemonSet | Minimize the admission of root containers | Kubernetes |
+| 69 | CKV_K8S_23 | resource | StatefulSet | Minimize the admission of root containers | Kubernetes |
+| 70 | CKV_K8S_23 | resource | ReplicaSet | Minimize the admission of root containers | Kubernetes |
+| 71 | CKV_K8S_23 | resource | ReplicationController | Minimize the admission of root containers | Kubernetes |
+| 72 | CKV_K8S_23 | resource | Job | Minimize the admission of root containers | Kubernetes |
+| 73 | CKV_K8S_23 | resource | CronJob | Minimize the admission of root containers | Kubernetes |
+| 74 | CKV_K8S_24 | resource | PodSecurityPolicy | Do not allow containers with added capability | Kubernetes |
+| 75 | CKV_K8S_25 | resource | containers | Minimize the admission of containers with added capability | Kubernetes |
+| 76 | CKV_K8S_25 | resource | initContainers | Minimize the admission of containers with added capability | Kubernetes |
+| 77 | CKV_K8S_26 | resource | containers | Do not specify hostPort unless absolutely necessary | Kubernetes |
+| 78 | CKV_K8S_26 | resource | initContainers | Do not specify hostPort unless absolutely necessary | Kubernetes |
+| 79 | CKV_K8S_27 | resource | Pod | Do not expose the docker daemon socket to containers | Kubernetes |
+| 80 | CKV_K8S_27 | resource | Deployment | Do not expose the docker daemon socket to containers | Kubernetes |
+| 81 | CKV_K8S_27 | resource | DaemonSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 82 | CKV_K8S_27 | resource | StatefulSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 83 | CKV_K8S_27 | resource | ReplicaSet | Do not expose the docker daemon socket to containers | Kubernetes |
+| 84 | CKV_K8S_27 | resource | ReplicationController | Do not expose the docker daemon socket to containers | Kubernetes |
+| 85 | CKV_K8S_27 | resource | Job | Do not expose the docker daemon socket to containers | Kubernetes |
+| 86 | CKV_K8S_27 | resource | CronJob | Do not expose the docker daemon socket to containers | Kubernetes |
+| 87 | CKV_K8S_28 | resource | containers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
+| 88 | CKV_K8S_28 | resource | initContainers | Minimize the admission of containers with the NET_RAW capability | Kubernetes |
+| 89 | CKV_K8S_29 | resource | Pod | Apply security context to your pods and containers | Kubernetes |
+| 90 | CKV_K8S_29 | resource | Deployment | Apply security context to your pods and containers | Kubernetes |
+| 91 | CKV_K8S_29 | resource | DaemonSet | Apply security context to your pods and containers | Kubernetes |
+| 92 | CKV_K8S_29 | resource | StatefulSet | Apply security context to your pods and containers | Kubernetes |
+| 93 | CKV_K8S_29 | resource | ReplicaSet | Apply security context to your pods and containers | Kubernetes |
+| 94 | CKV_K8S_29 | resource | ReplicationController | Apply security context to your pods and containers | Kubernetes |
+| 95 | CKV_K8S_29 | resource | Job | Apply security context to your pods and containers | Kubernetes |
+| 96 | CKV_K8S_29 | resource | CronJob | Apply security context to your pods and containers | Kubernetes |
+| 97 | CKV_K8S_30 | resource | containers | Apply security context to your pods and containers | Kubernetes |
+| 98 | CKV_K8S_30 | resource | initContainers | Apply security context to your pods and containers | Kubernetes |
+| 99 | CKV_K8S_31 | resource | Pod | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 100 | CKV_K8S_31 | resource | Deployment | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 101 | CKV_K8S_31 | resource | DaemonSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 102 | CKV_K8S_31 | resource | StatefulSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 103 | CKV_K8S_31 | resource | ReplicaSet | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 104 | CKV_K8S_31 | resource | ReplicationController | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 105 | CKV_K8S_31 | resource | Job | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 106 | CKV_K8S_31 | resource | CronJob | Ensure that the seccomp profile is set to docker/default or runtime/default | Kubernetes |
+| 107 | CKV_K8S_32 | resource | PodSecurityPolicy | Ensure default seccomp profile set to docker/default or runtime/default | Kubernetes |
+| 108 | CKV_K8S_33 | resource | containers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
+| 109 | CKV_K8S_33 | resource | initContainers | Ensure the Kubernetes dashboard is not deployed | Kubernetes |
+| 110 | CKV_K8S_34 | resource | containers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
+| 111 | CKV_K8S_34 | resource | initContainers | Ensure that Tiller (Helm v2) is not deployed | Kubernetes |
+| 112 | CKV_K8S_35 | resource | containers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
+| 113 | CKV_K8S_35 | resource | initContainers | Prefer using secrets as files over secrets as environment variables | Kubernetes |
+| 114 | CKV_K8S_36 | resource | PodSecurityPolicy | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 115 | CKV_K8S_37 | resource | containers | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 116 | CKV_K8S_37 | resource | initContainers | Minimize the admission of containers with capabilities assigned | Kubernetes |
+| 117 | CKV_K8S_38 | resource | Pod | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 118 | CKV_K8S_38 | resource | Deployment | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 119 | CKV_K8S_38 | resource | DaemonSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 120 | CKV_K8S_38 | resource | StatefulSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 121 | CKV_K8S_38 | resource | ReplicaSet | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 122 | CKV_K8S_38 | resource | ReplicationController | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 123 | CKV_K8S_38 | resource | Job | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 124 | CKV_K8S_38 | resource | CronJob | Ensure that Service Account Tokens are only mounted where necessary | Kubernetes |
+| 125 | CKV_K8S_39 | resource | containers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
+| 126 | CKV_K8S_39 | resource | initContainers | Do not use the CAP_SYS_ADMIN linux capability | Kubernetes |
+| 127 | CKV_K8S_40 | resource | Pod | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 128 | CKV_K8S_40 | resource | Deployment | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 129 | CKV_K8S_40 | resource | DaemonSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 130 | CKV_K8S_40 | resource | StatefulSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 131 | CKV_K8S_40 | resource | ReplicaSet | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 132 | CKV_K8S_40 | resource | ReplicationController | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 133 | CKV_K8S_40 | resource | Job | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 134 | CKV_K8S_40 | resource | CronJob | Containers should run as a high UID to avoid host conflict | Kubernetes |
+| 135 | CKV_K8S_41 | resource | ServiceAccount | Ensure that default service accounts are not actively used | Kubernetes |
+| 136 | CKV_K8S_42 | resource | RoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
+| 137 | CKV_K8S_42 | resource | ClusterRoleBinding | Ensure that default service accounts are not actively used | Kubernetes |
+| 138 | CKV_K8S_43 | resource | containers | Image should use digest | Kubernetes |
+| 139 | CKV_K8S_43 | resource | initContainers | Image should use digest | Kubernetes |
+| 140 | CKV_K8S_44 | resource | Service | Ensure that the Tiller Service (Helm v2) is deleted | Kubernetes |
+| 141 | CKV_K8S_45 | resource | containers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
+| 142 | CKV_K8S_45 | resource | initContainers | Ensure the Tiller Deployment (Helm V2) is not accessible from within the cluster | Kubernetes |
+| 143 | CKV_K8S_49 | resource | Role | Minimize wildcard use in Roles and ClusterRoles | Kubernetes |
+| 144 | CKV_K8S_49 | resource | ClusterRole | Minimize wildcard use in Roles and ClusterRoles | Kubernetes |
+| 145 | CKV_K8S_68 | resource | containers | Ensure that the --anonymous-auth argument is set to false | Kubernetes |
+| 146 | CKV_K8S_69 | resource | containers | Ensure that the --basic-auth-file argument is not set | Kubernetes |
+| 147 | CKV_K8S_70 | resource | containers | Ensure that the --token-auth-file argument is not set | Kubernetes |
+| 148 | CKV_K8S_71 | resource | containers | Ensure that the --kubelet-https argument is set to true | Kubernetes |
+| 149 | CKV_K8S_72 | resource | containers | Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate | Kubernetes |
+| 150 | CKV_K8S_73 | resource | containers | Ensure that the --kubelet-certificate-authority argument is set as appropriate | Kubernetes |
+| 151 | CKV_K8S_74 | resource | containers | Ensure that the --authorization-mode argument is not set to AlwaysAllow | Kubernetes |
+| 152 | CKV_K8S_75 | resource | containers | Ensure that the --authorization-mode argument includes Node | Kubernetes |
+| 153 | CKV_K8S_77 | resource | containers | Ensure that the --authorization-mode argument includes RBAC | Kubernetes |
+| 154 | CKV_K8S_78 | resource | AdmissionConfiguration | Ensure that the admission control plugin EventRateLimit is set | Kubernetes |
+| 155 | CKV_K8S_79 | resource | containers | Ensure that the admission control plugin AlwaysAdmit is not set | Kubernetes |
+| 156 | CKV_K8S_80 | resource | containers | Ensure that the admission control plugin AlwaysPullImages is set | Kubernetes |
+| 157 | CKV_K8S_81 | resource | containers | Ensure that the admission control plugin SecurityContextDeny is set if PodSecurityPolicy is not used | Kubernetes |
+| 158 | CKV_K8S_82 | resource | containers | Ensure that the admission control plugin ServiceAccount is set | Kubernetes |
+| 159 | CKV_K8S_83 | resource | containers | Ensure that the admission control plugin NamespaceLifecycle is set | Kubernetes |
+| 160 | CKV_K8S_84 | resource | containers | Ensure that the admission control plugin PodSecurityPolicy is set | Kubernetes |
+| 161 | CKV_K8S_85 | resource | containers | Ensure that the admission control plugin NodeRestriction is set | Kubernetes |
+| 162 | CKV_K8S_86 | resource | containers | Ensure that the --insecure-bind-address argument is not set | Kubernetes |
+| 163 | CKV_K8S_88 | resource | containers | Ensure that the --insecure-port argument is set to 0 | Kubernetes |
+| 164 | CKV_K8S_89 | resource | containers | Ensure that the --secure-port argument is not set to 0 | Kubernetes |
+| 165 | CKV_K8S_90 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 166 | CKV_K8S_91 | resource | containers | Ensure that the --audit-log-path argument is set | Kubernetes |
+| 167 | CKV_K8S_92 | resource | containers | Ensure that the --audit-log-maxage argument is set to 30 or as appropriate | Kubernetes |
+| 168 | CKV_K8S_93 | resource | containers | Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate | Kubernetes |
+| 169 | CKV_K8S_94 | resource | containers | Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate | Kubernetes |
+| 170 | CKV_K8S_95 | resource | containers | Ensure that the --request-timeout argument is set as appropriate | Kubernetes |
+| 171 | CKV_K8S_96 | resource | containers | Ensure that the --service-account-lookup argument is set to true | Kubernetes |
+| 172 | CKV_K8S_97 | resource | containers | Ensure that the --service-account-key-file argument is set as appropriate | Kubernetes |
+| 173 | CKV_K8S_99 | resource | containers | Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate | Kubernetes |
+| 174 | CKV_K8S_100 | resource | containers | Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate | Kubernetes |
+| 175 | CKV_K8S_102 | resource | containers | Ensure that the --etcd-ca-file argument is set as appropriate | Kubernetes |
+| 176 | CKV_K8S_104 | resource | containers | Ensure that encryption providers are appropriately configured | Kubernetes |
+| 177 | CKV_K8S_105 | resource | containers | Ensure that the API Server only makes use of Strong Cryptographic Ciphers | Kubernetes |
+| 178 | CKV_K8S_106 | resource | containers | Ensure that the --terminated-pod-gc-threshold argument is set as appropriate | Kubernetes |
+| 179 | CKV_K8S_107 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 180 | CKV_K8S_108 | resource | containers | Ensure that the --use-service-account-credentials argument is set to true | Kubernetes |
+| 181 | CKV_K8S_110 | resource | containers | Ensure that the --service-account-private-key-file argument is set as appropriate | Kubernetes |
+| 182 | CKV_K8S_111 | resource | containers | Ensure that the --root-ca-file argument is set as appropriate | Kubernetes |
+| 183 | CKV_K8S_112 | resource | containers | Ensure that the RotateKubeletServerCertificate argument is set to true | Kubernetes |
+| 184 | CKV_K8S_113 | resource | containers | Ensure that the --bind-address argument is set to 127.0.0.1 | Kubernetes |
+| 185 | CKV_K8S_114 | resource | containers | Ensure that the --profiling argument is set to false | Kubernetes |
+| 186 | CKV_K8S_115 | resource | containers | Ensure that the --bind-address argument is set to 127.0.0.1 | Kubernetes |
+| 187 | CKV_K8S_116 | resource | containers | Ensure that the --cert-file and --key-file arguments are set as appropriate | Kubernetes |
+| 188 | CKV_K8S_117 | resource | containers | Ensure that the --client-cert-auth argument is set to true | Kubernetes |
+| 189 | CKV_K8S_118 | resource | containers | Ensure that the --auto-tls argument is not set to true | Kubernetes |
+| 190 | CKV_K8S_119 | resource | containers | Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate | Kubernetes |
+| 191 | CKV_K8S_121 | resource | Pod | Ensure that the --peer-client-cert-auth argument is set to true | Kubernetes |
+| 192 | CKV_K8S_138 | resource | containers | Ensure that the --anonymous-auth argument is set to false | Kubernetes |
+| 193 | CKV_K8S_139 | resource | containers | Ensure that the --authorization-mode argument is not set to AlwaysAllow | Kubernetes |
+| 194 | CKV_K8S_140 | resource | containers | Ensure that the --client-ca-file argument is set as appropriate | Kubernetes |
+| 195 | CKV_K8S_141 | resource | containers | Ensure that the --read-only-port argument is set to 0 | Kubernetes |
+| 196 | CKV_K8S_143 | resource | containers | Ensure that the --streaming-connection-idle-timeout argument is not set to 0 | Kubernetes |
+| 197 | CKV_K8S_144 | resource | containers | Ensure that the --protect-kernel-defaults argument is set to true | Kubernetes |
+| 198 | CKV_K8S_145 | resource | containers | Ensure that the --make-iptables-util-chains argument is set to true | Kubernetes |
+| 199 | CKV_K8S_146 | resource | containers | Ensure that the --hostname-override argument is not set | Kubernetes |
+| 200 | CKV_K8S_147 | resource | containers | Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture | Kubernetes |
+| 201 | CKV_K8S_148 | resource | containers | Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate | Kubernetes |
+| 202 | CKV_K8S_149 | resource | containers | Ensure that the --rotate-certificates argument is not set to false | Kubernetes |
+| 203 | CKV_K8S_150 | resource | containers | Ensure that the RotateKubeletServerCertificate argument is set to true | Kubernetes |
+| 204 | CKV_K8S_151 | resource | containers | Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers | Kubernetes |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/secrets.md b/docs/5.Policy Index/secrets.md
new file mode 100644
index 0000000000..cc3c5d8bd5
--- /dev/null
+++ b/docs/5.Policy Index/secrets.md
@@ -0,0 +1,33 @@
+---
+layout: default
+title: secrets resource scans
+nav_order: 1
+---
+
+# secrets resource scans (auto generated)
+
+| | | Id | Type | Entity | Policy | IaC |
+|----|---------------|----------------------------------|---------|----------------------------------|----------------------------------|---------|
+| 0 | CKV_SECRET_1 | Artifactory Credentials | secrets | Artifactory Credentials | Artifactory Credentials | secrets |
+| 1 | CKV_SECRET_2 | AWS Access Key | secrets | AWS Access Key | AWS Access Key | secrets |
+| 2 | CKV_SECRET_3 | Azure Storage Account access key | secrets | Azure Storage Account access key | Azure Storage Account access key | secrets |
+| 3 | CKV_SECRET_4 | Basic Auth Credentials | secrets | Basic Auth Credentials | Basic Auth Credentials | secrets |
+| 4 | CKV_SECRET_5 | Cloudant Credentials | secrets | Cloudant Credentials | Cloudant Credentials | secrets |
+| 5 | CKV_SECRET_6 | Base64 High Entropy String | secrets | Base64 High Entropy String | Base64 High Entropy String | secrets |
+| 6 | CKV_SECRET_7 | IBM Cloud IAM Key | secrets | IBM Cloud IAM Key | IBM Cloud IAM Key | secrets |
+| 7 | CKV_SECRET_8 | IBM COS HMAC Credentials | secrets | IBM COS HMAC Credentials | IBM COS HMAC Credentials | secrets |
+| 8 | CKV_SECRET_9 | JSON Web Token | secrets | JSON Web Token | JSON Web Token | secrets |
+| 9 | CKV_SECRET_11 | Mailchimp Access Key | secrets | Mailchimp Access Key | Mailchimp Access Key | secrets |
+| 10 | CKV_SECRET_12 | NPM tokens | secrets | NPM tokens | NPM tokens | secrets |
+| 11 | CKV_SECRET_13 | Private Key | secrets | Private Key | Private Key | secrets |
+| 12 | CKV_SECRET_14 | Slack Token | secrets | Slack Token | Slack Token | secrets |
+| 13 | CKV_SECRET_15 | SoftLayer Credentials | secrets | SoftLayer Credentials | SoftLayer Credentials | secrets |
+| 14 | CKV_SECRET_16 | Square OAuth Secret | secrets | Square OAuth Secret | Square OAuth Secret | secrets |
+| 15 | CKV_SECRET_17 | Stripe Access Key | secrets | Stripe Access Key | Stripe Access Key | secrets |
+| 16 | CKV_SECRET_18 | Twilio API Key | secrets | Twilio API Key | Twilio API Key | secrets |
+| 17 | CKV_SECRET_19 | Hex High Entropy String | secrets | Hex High Entropy String | Hex High Entropy String | secrets |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/serverless.md b/docs/5.Policy Index/serverless.md
new file mode 100644
index 0000000000..020ab484d2
--- /dev/null
+++ b/docs/5.Policy Index/serverless.md
@@ -0,0 +1,148 @@
+---
+layout: default
+title: serverless resource scans
+nav_order: 1
+---
+
+# serverless resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|----|------------|----------|----------------|-------------------------------------------------------------------------------------|------------|
+| 0 | CKV_AWS_1 | resource | serverless_aws | Ensure IAM policies that allow full "*-*" administrative privileges are not created | serverless |
+| 1 | CKV_AWS_41 | resource | serverless_aws | Ensure no hard coded AWS access key and secret key exists in provider | serverless |
+| 2 | CKV_AWS_49 | resource | serverless_aws | Ensure no IAM policies documents allow "*" as a statement's actions | serverless |
+
+
+---
+
+
+| | Id | Type | Entity | Policy | IaC |
+|-----|-------------|----------|-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
+| 0 | CKV_AWS_2 | resource | AWS::ElasticLoadBalancingV2::Listener | Ensure ALB protocol is HTTPS | Cloudformation |
+| 1 | CKV_AWS_3 | resource | AWS::EC2::Volume | Ensure all data stored in the EBS is securely encrypted | Cloudformation |
+| 2 | CKV_AWS_5 | resource | AWS::Elasticsearch::Domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Cloudformation |
+| 3 | CKV_AWS_6 | resource | AWS::Elasticsearch::Domain | Ensure all Elasticsearch has node-to-node encryption enabled | Cloudformation |
+| 4 | CKV_AWS_7 | resource | AWS::KMS::Key | Ensure rotation for customer created CMKs is enabled | Cloudformation |
+| 5 | CKV_AWS_8 | resource | AWS::AutoScaling::LaunchConfiguration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Cloudformation |
+| 6 | CKV_AWS_16 | resource | AWS::RDS::DBInstance | Ensure all data stored in the RDS is securely encrypted at rest | Cloudformation |
+| 7 | CKV_AWS_17 | resource | AWS::RDS::DBInstance | Ensure all data stored in RDS is not publicly accessible | Cloudformation |
+| 8 | CKV_AWS_18 | resource | AWS::S3::Bucket | Ensure the S3 bucket has access logging enabled | Cloudformation |
+| 9 | CKV_AWS_19 | resource | AWS::S3::Bucket | Ensure the S3 bucket has server-side-encryption enabled | Cloudformation |
+| 10 | CKV_AWS_20 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow READ permissions to everyone | Cloudformation |
+| 11 | CKV_AWS_21 | resource | AWS::S3::Bucket | Ensure the S3 bucket has versioning enabled | Cloudformation |
+| 12 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroup | Ensure every security groups rule has a description | Cloudformation |
+| 13 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupIngress | Ensure every security groups rule has a description | Cloudformation |
+| 14 | CKV_AWS_23 | resource | AWS::EC2::SecurityGroupEgress | Ensure every security groups rule has a description | Cloudformation |
+| 15 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 16 | CKV_AWS_24 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Cloudformation |
+| 17 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroup | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 18 | CKV_AWS_25 | resource | AWS::EC2::SecurityGroupIngress | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Cloudformation |
+| 19 | CKV_AWS_26 | resource | AWS::SNS::Topic | Ensure all data stored in the SNS topic is encrypted | Cloudformation |
+| 20 | CKV_AWS_27 | resource | AWS::SQS::Queue | Ensure all data stored in the SQS queue is encrypted | Cloudformation |
+| 21 | CKV_AWS_28 | resource | AWS::DynamoDB::Table | Ensure Dynamodb point in time recovery (backup) is enabled | Cloudformation |
+| 22 | CKV_AWS_29 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Cloudformation |
+| 23 | CKV_AWS_30 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Cloudformation |
+| 24 | CKV_AWS_31 | resource | AWS::ElastiCache::ReplicationGroup | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Cloudformation |
+| 25 | CKV_AWS_32 | resource | AWS::ECR::Repository | Ensure ECR policy is not set to public | Cloudformation |
+| 26 | CKV_AWS_33 | resource | AWS::KMS::Key | Ensure KMS key policy does not contain wildcard (*) principal | Cloudformation |
+| 27 | CKV_AWS_34 | resource | AWS::CloudFront::Distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Cloudformation |
+| 28 | CKV_AWS_35 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Cloudformation |
+| 29 | CKV_AWS_36 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail log file validation is enabled | Cloudformation |
+| 30 | CKV_AWS_40 | resource | AWS::IAM::Policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Cloudformation |
+| 31 | CKV_AWS_42 | resource | AWS::EFS::FileSystem | Ensure EFS is securely encrypted | Cloudformation |
+| 32 | CKV_AWS_43 | resource | AWS::Kinesis::Stream | Ensure Kinesis Stream is securely encrypted | Cloudformation |
+| 33 | CKV_AWS_44 | resource | AWS::Neptune::DBCluster | Ensure Neptune storage is securely encrypted | Cloudformation |
+| 34 | CKV_AWS_45 | resource | AWS::Lambda::Function | Ensure no hard-coded secrets exist in lambda environment | Cloudformation |
+| 35 | CKV_AWS_46 | resource | AWS::EC2::Instance | Ensure no hard-coded secrets exist in EC2 user data | Cloudformation |
+| 36 | CKV_AWS_47 | resource | AWS::DAX::Cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Cloudformation |
+| 37 | CKV_AWS_51 | resource | AWS::ECR::Repository | Ensure ECR Image Tags are immutable | Cloudformation |
+| 38 | CKV_AWS_53 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public ACLS enabled | Cloudformation |
+| 39 | CKV_AWS_54 | resource | AWS::S3::Bucket | Ensure S3 bucket has block public policy enabled | Cloudformation |
+| 40 | CKV_AWS_55 | resource | AWS::S3::Bucket | Ensure S3 bucket has ignore public ACLs enabled | Cloudformation |
+| 41 | CKV_AWS_56 | resource | AWS::S3::Bucket | Ensure S3 bucket has 'restrict_public_bucket' enabled | Cloudformation |
+| 42 | CKV_AWS_57 | resource | AWS::S3::Bucket | Ensure the S3 bucket does not allow WRITE permissions to everyone | Cloudformation |
+| 43 | CKV_AWS_58 | resource | AWS::EKS::Cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Cloudformation |
+| 44 | CKV_AWS_59 | resource | AWS::ApiGateway::Method | Ensure there is no open access to back-end resources through API | Cloudformation |
+| 45 | CKV_AWS_60 | resource | AWS::IAM::Role | Ensure IAM role allows only specific services or principals to assume it | Cloudformation |
+| 46 | CKV_AWS_61 | resource | AWS::IAM::Role | Ensure IAM role allows only specific principals in account to assume it | Cloudformation |
+| 47 | CKV_AWS_64 | resource | AWS::Redshift::Cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Cloudformation |
+| 48 | CKV_AWS_65 | resource | AWS::ECS::Cluster | Ensure container insights are enabled on ECS cluster | Cloudformation |
+| 49 | CKV_AWS_66 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group specifies retention days | Cloudformation |
+| 50 | CKV_AWS_67 | resource | AWS::CloudTrail::Trail | Ensure CloudTrail is enabled in all Regions | Cloudformation |
+| 51 | CKV_AWS_68 | resource | AWS::CloudFront::Distribution | CloudFront Distribution should have WAF enabled | Cloudformation |
+| 52 | CKV_AWS_69 | resource | AWS::AmazonMQ::Broker | Ensure Amazon MQ Broker should not have public access | Cloudformation |
+| 53 | CKV_AWS_71 | resource | AWS::Redshift::Cluster | Ensure Redshift Cluster logging is enabled | Cloudformation |
+| 54 | CKV_AWS_73 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has X-Ray Tracing enabled | Cloudformation |
+| 55 | CKV_AWS_74 | resource | AWS::DocDB::DBCluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Cloudformation |
+| 56 | CKV_AWS_76 | resource | AWS::ApiGateway::Stage | Ensure API Gateway has Access Logging enabled | Cloudformation |
+| 57 | CKV_AWS_78 | resource | AWS::CodeBuild::Project | Ensure that CodeBuild Project encryption is not disabled | Cloudformation |
+| 58 | CKV_AWS_79 | resource | AWS::EC2::LaunchTemplate | Ensure Instance Metadata Service Version 1 is not enabled | Cloudformation |
+| 59 | CKV_AWS_82 | resource | AWS::Athena::WorkGroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Cloudformation |
+| 60 | CKV_AWS_83 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain enforces HTTPS | Cloudformation |
+| 61 | CKV_AWS_84 | resource | AWS::Elasticsearch::Domain | Ensure Elasticsearch Domain Logging is enabled | Cloudformation |
+| 62 | CKV_AWS_85 | resource | AWS::DocDB::DBCluster | Ensure DocDB Logging is enabled | Cloudformation |
+| 63 | CKV_AWS_86 | resource | AWS::CloudFront::Distribution | Ensure Cloudfront distribution has Access Logging enabled | Cloudformation |
+| 64 | CKV_AWS_87 | resource | AWS::Redshift::Cluster | Redshift cluster should not be publicly accessible | Cloudformation |
+| 65 | CKV_AWS_88 | resource | AWS::EC2::LaunchTemplate | EC2 instance should not have public IP. | Cloudformation |
+| 66 | CKV_AWS_88 | resource | AWS::EC2::Instance | EC2 instance should not have public IP. | Cloudformation |
+| 67 | CKV_AWS_89 | resource | AWS::DMS::ReplicationInstance | DMS replication instance should not be publicly accessible | Cloudformation |
+| 68 | CKV_AWS_90 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB TLS is not disabled | Cloudformation |
+| 69 | CKV_AWS_91 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure the ELBv2 (Application/Network) has access logging enabled | Cloudformation |
+| 70 | CKV_AWS_92 | resource | AWS::ElasticLoadBalancing::LoadBalancer | Ensure the ELB has access logging enabled | Cloudformation |
+| 71 | CKV_AWS_94 | resource | AWS::Glue::DataCatalogEncryptionSettings | Ensure Glue Data Catalog Encryption is enabled | Cloudformation |
+| 72 | CKV_AWS_95 | resource | AWS::ApiGatewayV2::Stage | Ensure API Gateway V2 has Access Logging enabled | Cloudformation |
+| 73 | CKV_AWS_96 | resource | AWS::RDS::DBCluster | Ensure all data stored in Aurrora is securely encrypted at rest | Cloudformation |
+| 74 | CKV_AWS_97 | resource | AWS::ECS::TaskDefinition | Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions | Cloudformation |
+| 75 | CKV_AWS_99 | resource | AWS::Glue::SecurityConfiguration | Ensure Glue Security Configuration Encryption is enabled | Cloudformation |
+| 76 | CKV_AWS_100 | resource | AWS::EKS::Nodegroup | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Cloudformation |
+| 77 | CKV_AWS_101 | resource | AWS::Neptune::DBCluster | Ensure Neptune logging is enabled | Cloudformation |
+| 78 | CKV_AWS_104 | resource | AWS::DocDB::DBClusterParameterGroup | Ensure DocDB has audit logs enabled | Cloudformation |
+| 79 | CKV_AWS_105 | resource | AWS::Redshift::ClusterParameterGroup | Ensure Redshift uses SSL | Cloudformation |
+| 80 | CKV_AWS_107 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 81 | CKV_AWS_107 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 82 | CKV_AWS_107 | resource | AWS::IAM::Group | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 83 | CKV_AWS_107 | resource | AWS::IAM::Role | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 84 | CKV_AWS_107 | resource | AWS::IAM::User | Ensure IAM policies does not allow credentials exposure | Cloudformation |
+| 85 | CKV_AWS_108 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 86 | CKV_AWS_108 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 87 | CKV_AWS_108 | resource | AWS::IAM::Group | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 88 | CKV_AWS_108 | resource | AWS::IAM::Role | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 89 | CKV_AWS_108 | resource | AWS::IAM::User | Ensure IAM policies does not allow data exfiltration | Cloudformation |
+| 90 | CKV_AWS_109 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 91 | CKV_AWS_109 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 92 | CKV_AWS_109 | resource | AWS::IAM::Group | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 93 | CKV_AWS_109 | resource | AWS::IAM::Role | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 94 | CKV_AWS_109 | resource | AWS::IAM::User | Ensure IAM policies does not allow permissions management without constraints | Cloudformation |
+| 95 | CKV_AWS_110 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 96 | CKV_AWS_110 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 97 | CKV_AWS_110 | resource | AWS::IAM::Group | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 98 | CKV_AWS_110 | resource | AWS::IAM::Role | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 99 | CKV_AWS_110 | resource | AWS::IAM::User | Ensure IAM policies does not allow privilege escalation | Cloudformation |
+| 100 | CKV_AWS_111 | resource | AWS::IAM::Policy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 101 | CKV_AWS_111 | resource | AWS::IAM::ManagedPolicy | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 102 | CKV_AWS_111 | resource | AWS::IAM::Group | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 103 | CKV_AWS_111 | resource | AWS::IAM::Role | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 104 | CKV_AWS_111 | resource | AWS::IAM::User | Ensure IAM policies does not allow write access without constraints | Cloudformation |
+| 105 | CKV_AWS_120 | resource | AWS::ApiGateway::Stage | Ensure API Gateway caching is enabled | Cloudformation |
+| 106 | CKV_AWS_123 | resource | AWS::EC2::VPCEndpointService | Ensure that VPC Endpoint Service is configured for Manual Acceptance | Cloudformation |
+| 107 | CKV_AWS_131 | resource | AWS::ElasticLoadBalancingV2::LoadBalancer | Ensure that ALB drops HTTP headers | Cloudformation |
+| 108 | CKV_AWS_136 | resource | AWS::ECR::Repository | Ensure that ECR repositories are encrypted using KMS | Cloudformation |
+| 109 | CKV_AWS_154 | resource | AWS::Redshift::Cluster | Ensure Redshift is not deployed outside of a VPC | Cloudformation |
+| 110 | CKV_AWS_155 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace user volumes are encrypted | Cloudformation |
+| 111 | CKV_AWS_156 | resource | AWS::WorkSpaces::Workspace | Ensure that Workspace root volumes are encrypted | Cloudformation |
+| 112 | CKV_AWS_157 | resource | AWS::RDS::DBInstance | Ensure that RDS instances have Multi-AZ enabled | Cloudformation |
+| 113 | CKV_AWS_158 | resource | AWS::Logs::LogGroup | Ensure that CloudWatch Log Group is encrypted by KMS | Cloudformation |
+| 114 | CKV_AWS_160 | resource | AWS::Timestream::Database | Ensure that Timestream database is encrypted with KMS CMK | Cloudformation |
+| 115 | CKV_AWS_161 | resource | AWS::RDS::DBInstance | Ensure RDS database has IAM authentication enabled | Cloudformation |
+| 116 | CKV_AWS_162 | resource | AWS::RDS::DBCluster | Ensure RDS cluster has IAM authentication enabled | Cloudformation |
+| 117 | CKV_AWS_163 | resource | AWS::ECR::Repository | Ensure ECR image scanning on push is enabled | Cloudformation |
+| 118 | CKV_AWS_164 | resource | AWS::Transfer::Server | Ensure Transfer Server is not exposed publicly. | Cloudformation |
+| 119 | CKV_AWS_165 | resource | AWS::DynamoDB::GlobalTable | Ensure Dynamodb global table point in time recovery (backup) is enabled | Cloudformation |
+| 120 | CKV_AWS_166 | resource | AWS::Backup::BackupVault | Ensure Backup Vault is encrypted at rest using KMS CMK | Cloudformation |
+| 121 | CKV_AWS_170 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger permissions mode is set to STANDARD | Cloudformation |
+| 122 | CKV_AWS_172 | resource | AWS::QLDB::Ledger | Ensure QLDB ledger has deletion protection enabled | Cloudformation |
+
+
+---
+
+
diff --git a/docs/5.Policy Index/terraform.md b/docs/5.Policy Index/terraform.md
new file mode 100644
index 0000000000..151e8748dd
--- /dev/null
+++ b/docs/5.Policy Index/terraform.md
@@ -0,0 +1,794 @@
+---
+layout: default
+title: terraform resource scans
+nav_order: 1
+---
+
+# terraform resource scans (auto generated)
+
+| | Id | Type | Entity | Policy | IaC |
+|-----|---------------|----------|--------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
+| 0 | CKV_AWS_1 | data | aws_iam_policy_document | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 1 | CKV_AWS_2 | resource | aws_lb_listener | Ensure ALB protocol is HTTPS | Terraform |
+| 2 | CKV_AWS_3 | resource | aws_ebs_volume | Ensure all data stored in the EBS is securely encrypted | Terraform |
+| 3 | CKV_AWS_5 | resource | aws_elasticsearch_domain | Ensure all data stored in the Elasticsearch is securely encrypted at rest | Terraform |
+| 4 | CKV_AWS_6 | resource | aws_elasticsearch_domain | Ensure all Elasticsearch has node-to-node encryption enabled | Terraform |
+| 5 | CKV_AWS_7 | resource | aws_kms_key | Ensure rotation for customer created CMKs is enabled | Terraform |
+| 6 | CKV_AWS_8 | resource | aws_instance | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
+| 7 | CKV_AWS_8 | resource | aws_launch_configuration | Ensure all data stored in the Launch configuration EBS is securely encrypted | Terraform |
+| 8 | CKV_AWS_9 | resource | aws_iam_account_password_policy | Ensure IAM password policy expires passwords within 90 days or less | Terraform |
+| 9 | CKV_AWS_10 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires minimum length of 14 or greater | Terraform |
+| 10 | CKV_AWS_11 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one lowercase letter | Terraform |
+| 11 | CKV_AWS_12 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one number | Terraform |
+| 12 | CKV_AWS_13 | resource | aws_iam_account_password_policy | Ensure IAM password policy prevents password reuse | Terraform |
+| 13 | CKV_AWS_14 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one symbol | Terraform |
+| 14 | CKV_AWS_15 | resource | aws_iam_account_password_policy | Ensure IAM password policy requires at least one uppercase letter | Terraform |
+| 15 | CKV_AWS_16 | resource | aws_db_instance | Ensure all data stored in the RDS is securely encrypted at rest | Terraform |
+| 16 | CKV_AWS_17 | resource | aws_db_instance | Ensure all data stored in RDS is not publicly accessible | Terraform |
+| 17 | CKV_AWS_17 | resource | aws_rds_cluster_instance | Ensure all data stored in RDS is not publicly accessible | Terraform |
+| 18 | CKV_AWS_18 | resource | aws_s3_bucket | Ensure the S3 bucket has access logging enabled | Terraform |
+| 19 | CKV_AWS_19 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket is securely encrypted at rest | Terraform |
+| 20 | CKV_AWS_20 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public READ access. | Terraform |
+| 21 | CKV_AWS_21 | resource | aws_s3_bucket | Ensure all data stored in the S3 bucket have versioning enabled | Terraform |
+| 22 | CKV_AWS_22 | resource | aws_sagemaker_notebook_instance | Ensure SageMaker Notebook is encrypted at rest using KMS CMK | Terraform |
+| 23 | CKV_AWS_23 | resource | aws_security_group | Ensure every security groups rule has a description | Terraform |
+| 24 | CKV_AWS_23 | resource | aws_security_group_rule | Ensure every security groups rule has a description | Terraform |
+| 25 | CKV_AWS_23 | resource | aws_db_security_group | Ensure every security groups rule has a description | Terraform |
+| 26 | CKV_AWS_23 | resource | aws_elasticache_security_group | Ensure every security groups rule has a description | Terraform |
+| 27 | CKV_AWS_23 | resource | aws_redshift_security_group | Ensure every security groups rule has a description | Terraform |
+| 28 | CKV_AWS_24 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
+| 29 | CKV_AWS_24 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 22 | Terraform |
+| 30 | CKV_AWS_25 | resource | aws_security_group | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
+| 31 | CKV_AWS_25 | resource | aws_security_group_rule | Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389 | Terraform |
+| 32 | CKV_AWS_26 | resource | aws_sns_topic | Ensure all data stored in the SNS topic is encrypted | Terraform |
+| 33 | CKV_AWS_27 | resource | aws_sqs_queue | Ensure all data stored in the SQS queue is encrypted | Terraform |
+| 34 | CKV_AWS_28 | resource | aws_dynamodb_table | Ensure Dynamodb point in time recovery (backup) is enabled | Terraform |
+| 35 | CKV_AWS_29 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at rest | Terraform |
+| 36 | CKV_AWS_30 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit | Terraform |
+| 37 | CKV_AWS_31 | resource | aws_elasticache_replication_group | Ensure all data stored in the Elasticache Replication Group is securely encrypted at transit and has auth token | Terraform |
+| 38 | CKV_AWS_32 | resource | aws_ecr_repository_policy | Ensure ECR policy is not set to public | Terraform |
+| 39 | CKV_AWS_33 | resource | aws_kms_key | Ensure KMS key policy does not contain wildcard (*) principal | Terraform |
+| 40 | CKV_AWS_34 | resource | aws_cloudfront_distribution | Ensure cloudfront distribution ViewerProtocolPolicy is set to HTTPS | Terraform |
+| 41 | CKV_AWS_35 | resource | aws_cloudtrail | Ensure CloudTrail logs are encrypted at rest using KMS CMKs | Terraform |
+| 42 | CKV_AWS_36 | resource | aws_cloudtrail | Ensure CloudTrail log file validation is enabled | Terraform |
+| 43 | CKV_AWS_37 | resource | aws_eks_cluster | Ensure Amazon EKS control plane logging enabled for all log types | Terraform |
+| 44 | CKV_AWS_38 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint not accessible to 0.0.0.0/0 | Terraform |
+| 45 | CKV_AWS_39 | resource | aws_eks_cluster | Ensure Amazon EKS public endpoint disabled | Terraform |
+| 46 | CKV_AWS_40 | resource | aws_iam_user_policy | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 47 | CKV_AWS_40 | resource | aws_iam_user_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 48 | CKV_AWS_40 | resource | aws_iam_policy_attachment | Ensure IAM policies are attached only to groups or roles (Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.) | Terraform |
+| 49 | CKV_AWS_41 | provider | aws | Ensure no hard coded AWS access key and secret key exists in provider | Terraform |
+| 50 | CKV_AWS_42 | resource | aws_efs_file_system | Ensure EFS is securely encrypted | Terraform |
+| 51 | CKV_AWS_43 | resource | aws_kinesis_stream | Ensure Kinesis Stream is securely encrypted | Terraform |
+| 52 | CKV_AWS_44 | resource | aws_neptune_cluster | Ensure Neptune storage is securely encrypted | Terraform |
+| 53 | CKV_AWS_45 | resource | aws_lambda_function | Ensure no hard-coded secrets exist in lambda environment | Terraform |
+| 54 | CKV_AWS_46 | resource | aws_instance | Ensure no hard-coded secrets exist in EC2 user data | Terraform |
+| 55 | CKV_AWS_47 | resource | aws_dax_cluster | Ensure DAX is encrypted at rest (default is unencrypted) | Terraform |
+| 56 | CKV_AWS_48 | resource | aws_mq_broker | Ensure MQ Broker logging is enabled | Terraform |
+| 57 | CKV_AWS_49 | data | aws_iam_policy_document | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 58 | CKV_AWS_50 | resource | aws_lambda_function | X-ray tracing is enabled for Lambda | Terraform |
+| 59 | CKV_AWS_51 | resource | aws_ecr_repository | Ensure ECR Image Tags are immutable | Terraform |
+| 60 | CKV_AWS_53 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public ACLS enabled | Terraform |
+| 61 | CKV_AWS_54 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has block public policy enabled | Terraform |
+| 62 | CKV_AWS_55 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has ignore public ACLs enabled | Terraform |
+| 63 | CKV_AWS_56 | resource | aws_s3_bucket_public_access_block | Ensure S3 bucket has 'restrict_public_bucket' enabled | Terraform |
+| 64 | CKV_AWS_57 | resource | aws_s3_bucket | S3 Bucket has an ACL defined which allows public WRITE access. | Terraform |
+| 65 | CKV_AWS_58 | resource | aws_eks_cluster | Ensure EKS Cluster has Secrets Encryption Enabled | Terraform |
+| 66 | CKV_AWS_59 | resource | aws_api_gateway_method | Ensure there is no open access to back-end resources through API | Terraform |
+| 67 | CKV_AWS_60 | resource | aws_iam_role | Ensure IAM role allows only specific services or principals to assume it | Terraform |
+| 68 | CKV_AWS_61 | resource | aws_iam_role | Ensure IAM role allows only specific principals in account to assume it | Terraform |
+| 69 | CKV_AWS_62 | resource | aws_iam_role_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 70 | CKV_AWS_62 | resource | aws_iam_user_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 71 | CKV_AWS_62 | resource | aws_iam_group_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 72 | CKV_AWS_62 | resource | aws_iam_policy | Ensure IAM policies that allow full "*-*" administrative privileges are not created | Terraform |
+| 73 | CKV_AWS_63 | resource | aws_iam_role_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 74 | CKV_AWS_63 | resource | aws_iam_user_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 75 | CKV_AWS_63 | resource | aws_iam_group_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 76 | CKV_AWS_63 | resource | aws_iam_policy | Ensure no IAM policies documents allow "*" as a statement's actions | Terraform |
+| 77 | CKV_AWS_64 | resource | aws_redshift_cluster | Ensure all data stored in the Redshift cluster is securely encrypted at rest | Terraform |
+| 78 | CKV_AWS_65 | resource | aws_ecs_cluster | Ensure container insights are enabled on ECS cluster | Terraform |
+| 79 | CKV_AWS_66 | resource | aws_cloudwatch_log_group | Ensure that CloudWatch Log Group specifies retention days | Terraform |
+| 80 | CKV_AWS_67 | resource | aws_cloudtrail | Ensure CloudTrail is enabled in all Regions | Terraform |
+| 81 | CKV_AWS_68 | resource | aws_cloudfront_distribution | CloudFront Distribution should have WAF enabled | Terraform |
+| 82 | CKV_AWS_69 | resource | aws_mq_broker | Ensure MQ Broker is not publicly exposed | Terraform |
+| 83 | CKV_AWS_70 | resource | aws_s3_bucket | Ensure S3 bucket does not allow an action with any Principal | Terraform |
+| 84 | CKV_AWS_70 | resource | aws_s3_bucket_policy | Ensure S3 bucket does not allow an action with any Principal | Terraform |
+| 85 | CKV_AWS_71 | resource | aws_redshift_cluster | Ensure Redshift Cluster logging is enabled | Terraform |
+| 86 | CKV_AWS_72 | resource | aws_sqs_queue_policy | Ensure SQS policy does not allow ALL (*) actions. | Terraform |
+| 87 | CKV_AWS_73 | resource | aws_api_gateway_stage | Ensure API Gateway has X-Ray Tracing enabled | Terraform |
+| 88 | CKV_AWS_74 | resource | aws_docdb_cluster | Ensure DocDB is encrypted at rest (default is unencrypted) | Terraform |
+| 89 | CKV_AWS_75 | resource | aws_globalaccelerator_accelerator | Ensure Global Accelerator accelerator has flow logs enabled | Terraform |
+| 90 | CKV_AWS_76 | resource | aws_api_gateway_stage | Ensure API Gateway has Access Logging enabled | Terraform |
+| 91 | CKV_AWS_76 | resource | aws_apigatewayv2_stage | Ensure API Gateway has Access Logging enabled | Terraform |
+| 92 | CKV_AWS_77 | resource | aws_athena_database | Ensure Athena Database is encrypted at rest (default is unencrypted) | Terraform |
+| 93 | CKV_AWS_78 | resource | aws_codebuild_project | Ensure that CodeBuild Project encryption is not disabled | Terraform |
+| 94 | CKV_AWS_79 | resource | aws_instance | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
+| 95 | CKV_AWS_79 | resource | aws_launch_template | Ensure Instance Metadata Service Version 1 is not enabled | Terraform |
+| 96 | CKV_AWS_80 | resource | aws_msk_cluster | Ensure MSK Cluster logging is enabled | Terraform |
+| 97 | CKV_AWS_81 | resource | aws_msk_cluster | Ensure MSK Cluster encryption in rest and transit is enabled | Terraform |
+| 98 | CKV_AWS_82 | resource | aws_athena_workgroup | Ensure Athena Workgroup should enforce configuration to prevent client disabling encryption | Terraform |
+| 99 | CKV_AWS_83 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain enforces HTTPS | Terraform |
+| 100 | CKV_AWS_84 | resource | aws_elasticsearch_domain | Ensure Elasticsearch Domain Logging is enabled | Terraform |
+| 101 | CKV_AWS_85 | resource | aws_docdb_cluster | Ensure DocDB Logging is enabled | Terraform |
+| 102 | CKV_AWS_86 | resource | aws_cloudfront_distribution | Ensure Cloudfront distribution has Access Logging enabled | Terraform |
+| 103 | CKV_AWS_87 | resource | aws_redshift_cluster | Redshift cluster should not be publicly accessible | Terraform |
+| 104 | CKV_AWS_88 | resource | aws_instance | EC2 instance should not have public IP. | Terraform |
+| 105 | CKV_AWS_88 | resource | aws_launch_template | EC2 instance should not have public IP. | Terraform |
+| 106 | CKV_AWS_89 | resource | aws_dms_replication_instance | DMS replication instance should not be publicly accessible | Terraform |
+| 107 | CKV_AWS_90 | resource | aws_docdb_cluster_parameter_group | Ensure DocDB TLS is not disabled | Terraform |
+| 108 | CKV_AWS_91 | resource | aws_lb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
+| 109 | CKV_AWS_91 | resource | aws_alb | Ensure the ELBv2 (Application/Network) has access logging enabled | Terraform |
+| 110 | CKV_AWS_92 | resource | aws_elb | Ensure the ELB has access logging enabled | Terraform |
+| 111 | CKV_AWS_93 | resource | aws_s3_bucket | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
+| 112 | CKV_AWS_93 | resource | aws_s3_bucket_policy | Ensure S3 bucket policy does not lockout all but root user. (Prevent lockouts needing root account fixes) | Terraform |
+| 113 | CKV_AWS_94 | resource | aws_glue_data_catalog_encryption_settings | Ensure Glue Data Catalog Encryption is enabled | Terraform |
+| 114 | CKV_AWS_96 | resource | aws_rds_cluster | Ensure all data stored in Aurora is securely encrypted at rest | Terraform |
+| 115 | CKV_AWS_97 | resource | aws_ecs_task_definition | Ensure Encryption in transit is enabled for EFS volumes in ECS Task definitions | Terraform |
+| 116 | CKV_AWS_98 | resource | aws_sagemaker_endpoint_configuration | Ensure all data stored in the Sagemaker Endpoint is securely encrypted at rest | Terraform |
+| 117 | CKV_AWS_99 | resource | aws_glue_security_configuration | Ensure Glue Security Configuration Encryption is enabled | Terraform |
+| 118 | CKV_AWS_100 | resource | aws_eks_node_group | Ensure Amazon EKS Node group has implict SSH access from 0.0.0.0/0 | Terraform |
+| 119 | CKV_AWS_101 | resource | aws_neptune_cluster | Ensure Neptune logging is enabled | Terraform |
+| 120 | CKV_AWS_102 | resource | aws_neptune_cluster_instance | Ensure Neptune Cluster instance is not publicly available | Terraform |
+| 121 | CKV_AWS_103 | resource | aws_lb_listener | Ensure that load balancer is using TLS 1.2 | Terraform |
+| 122 | CKV_AWS_104 | resource | aws_docdb_cluster_parameter_group | Ensure DocDB has audit logs enabled | Terraform |
+| 123 | CKV_AWS_105 | resource | aws_redshift_parameter_group | Ensure Redshift uses SSL | Terraform |
+| 124 | CKV_AWS_106 | resource | aws_ebs_encryption_by_default | Ensure EBS default encryption is enabled | Terraform |
+| 125 | CKV_AWS_107 | data | aws_iam_policy_document | Ensure IAM policies does not allow credentials exposure | Terraform |
+| 126 | CKV_AWS_108 | data | aws_iam_policy_document | Ensure IAM policies does not allow data exfiltration | Terraform |
+| 127 | CKV_AWS_109 | data | aws_iam_policy_document | Ensure IAM policies does not allow permissions management / resource exposure without constraints | Terraform |
+| 128 | CKV_AWS_110 | data | aws_iam_policy_document | Ensure IAM policies does not allow privilege escalation | Terraform |
+| 129 | CKV_AWS_111 | data | aws_iam_policy_document | Ensure IAM policies does not allow write access without constraints | Terraform |
+| 130 | CKV_AWS_112 | resource | aws_ssm_document | Ensure Session Manager data is encrypted in transit | Terraform |
+| 131 | CKV_AWS_113 | resource | aws_ssm_document | Ensure Session Manager logs are enabled and encrypted | Terraform |
+| 132 | CKV_AWS_114 | resource | aws_emr_cluster | Ensure that EMR clusters with Kerberos have Kerberos Realm set | Terraform |
+| 133 | CKV_AWS_115 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured for function-level concurrent execution limit | Terraform |
+| 134 | CKV_AWS_116 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) | Terraform |
+| 135 | CKV_AWS_117 | resource | aws_lambda_function | Ensure that AWS Lambda function is configured inside a VPC | Terraform |
+| 136 | CKV_AWS_118 | resource | aws_db_instance | Ensure that enhanced monitoring is enabled for Amazon RDS instances | Terraform |
+| 137 | CKV_AWS_118 | resource | aws_rds_cluster_instance | Ensure that enhanced monitoring is enabled for Amazon RDS instances | Terraform |
+| 138 | CKV_AWS_119 | resource | aws_dynamodb_table | Ensure DynamoDB Tables are encrypted using KMS | Terraform |
+| 139 | CKV_AWS_120 | resource | aws_api_gateway_stage | Ensure API Gateway caching is enabled | Terraform |
+| 140 | CKV_AWS_121 | resource | aws_config_configuration_aggregator | Ensure AWS Config is enabled in all regions | Terraform |
+| 141 | CKV_AWS_122 | resource | aws_sagemaker_notebook_instance | Ensure that direct internet access is disabled for an Amazon SageMaker Notebook Instance | Terraform |
+| 142 | CKV_AWS_123 | resource | aws_vpc_endpoint_service | Ensure that VPC Endpoint Service is configured for Manual Acceptance | Terraform |
+| 143 | CKV_AWS_124 | resource | aws_cloudformation_stack | Ensure that CloudFormation stacks are sending event notifications to an SNS topic | Terraform |
+| 144 | CKV_AWS_126 | resource | aws_instance | Ensure that detailed monitoring is enabled for EC2 instances | Terraform |
+| 145 | CKV_AWS_127 | resource | aws_elb | Ensure that Elastic Load Balancer(s) uses SSL certificates provided by AWS Certificate Manager | Terraform |
+| 146 | CKV_AWS_128 | resource | aws_rds_cluster | Ensure that an Amazon RDS Clusters have AWS Identity and Access Management (IAM) authentication enabled | Terraform |
+| 147 | CKV_AWS_129 | resource | aws_db_instance | Ensure that respective logs of Amazon Relational Database Service (Amazon RDS) are enabled | Terraform |
+| 148 | CKV_AWS_130 | resource | aws_subnet | Ensure VPC subnets do not assign public IP by default | Terraform |
+| 149 | CKV_AWS_131 | resource | aws_lb | Ensure that ALB drops HTTP headers | Terraform |
+| 150 | CKV_AWS_131 | resource | aws_alb | Ensure that ALB drops HTTP headers | Terraform |
+| 151 | CKV_AWS_133 | resource | aws_rds_cluster | Ensure that RDS instances has backup policy | Terraform |
+| 152 | CKV_AWS_134 | resource | aws_elasticache_cluster | Ensure that Amazon ElastiCache Redis clusters have automatic backup turned on | Terraform |
+| 153 | CKV_AWS_135 | resource | aws_instance | Ensure that EC2 is EBS optimized | Terraform |
+| 154 | CKV_AWS_136 | resource | aws_ecr_repository | Ensure that ECR repositories are encrypted using KMS | Terraform |
+| 155 | CKV_AWS_137 | resource | aws_elasticsearch_domain | Ensure that Elasticsearch is configured inside a VPC | Terraform |
+| 156 | CKV_AWS_138 | resource | aws_elb | Ensure that ELB is cross-zone-load-balancing enabled | Terraform |
+| 157 | CKV_AWS_139 | resource | aws_rds_cluster | Ensure that RDS clusters have deletion protection enabled | Terraform |
+| 158 | CKV_AWS_140 | resource | aws_rds_global_cluster | Ensure that RDS global clusters are encrypted | Terraform |
+| 159 | CKV_AWS_141 | resource | aws_redshift_cluster | Ensured that redshift cluster allowing version upgrade by default | Terraform |
+| 160 | CKV_AWS_142 | resource | aws_redshift_cluster | Ensure that Redshift cluster is encrypted by KMS | Terraform |
+| 161 | CKV_AWS_143 | resource | aws_s3_bucket | Ensure that S3 bucket has lock configuration enabled by default | Terraform |
+| 162 | CKV_AWS_144 | resource | aws_s3_bucket | Ensure that S3 bucket has cross-region replication enabled | Terraform |
+| 163 | CKV_AWS_145 | resource | aws_s3_bucket | Ensure that S3 buckets are encrypted with KMS by default | Terraform |
+| 164 | CKV_AWS_146 | resource | aws_db_cluster_snapshot | Ensure that RDS database cluster snapshot is encrypted | Terraform |
+| 165 | CKV_AWS_147 | resource | aws_codebuild_project | Ensure that CodeBuild projects are encrypted | Terraform |
+| 166 | CKV_AWS_148 | resource | aws_default_vpc | Ensure no default VPC is planned to be provisioned | Terraform |
+| 167 | CKV_AWS_149 | resource | aws_secretsmanager_secret | Ensure that Secrets Manager secret is encrypted using KMS | Terraform |
+| 168 | CKV_AWS_150 | resource | aws_lb | Ensure that Load Balancer has deletion protection enabled | Terraform |
+| 169 | CKV_AWS_150 | resource | aws_alb | Ensure that Load Balancer has deletion protection enabled | Terraform |
+| 170 | CKV_AWS_151 | resource | aws_eks_cluster | Ensure Kubernetes Secrets are encrypted using Customer Master Keys (CMKs) managed in AWS KMS | Terraform |
+| 171 | CKV_AWS_152 | resource | aws_lb | Ensure that Load Balancer (Network/Gateway) has cross-zone load balancing enabled | Terraform |
+| 172 | CKV_AWS_152 | resource | aws_alb | Ensure that Load Balancer (Network/Gateway) has cross-zone load balancing enabled | Terraform |
+| 173 | CKV_AWS_153 | resource | aws_autoscaling_group | Autoscaling groups should supply tags to launch configurations | Terraform |
+| 174 | CKV_AWS_154 | resource | aws_redshift_cluster | Ensure Redshift is not deployed outside of a VPC | Terraform |
+| 175 | CKV_AWS_155 | resource | aws_workspaces_workspace | Ensure that Workspace user volumes are encrypted | Terraform |
+| 176 | CKV_AWS_156 | resource | aws_workspaces_workspace | Ensure that Workspace root volumes are encrypted | Terraform |
+| 177 | CKV_AWS_157 | resource | aws_db_instance | Ensure that RDS instances have Multi-AZ enabled | Terraform |
+| 178 | CKV_AWS_158 | resource | aws_cloudwatch_log_group | Ensure that CloudWatch Log Group is encrypted by KMS | Terraform |
+| 179 | CKV_AWS_159 | resource | aws_athena_workgroup | Ensure that Athena Workgroup is encrypted | Terraform |
+| 180 | CKV_AWS_160 | resource | aws_timestreamwrite_database | Ensure that Timestream database is encrypted with KMS CMK | Terraform |
+| 181 | CKV_AWS_161 | resource | aws_db_instance | Ensure RDS database has IAM authentication enabled | Terraform |
+| 182 | CKV_AWS_162 | resource | aws_rds_cluster | Ensure RDS cluster has IAM authentication enabled | Terraform |
+| 183 | CKV_AWS_163 | resource | aws_ecr_repository | Ensure ECR image scanning on push is enabled | Terraform |
+| 184 | CKV_AWS_164 | resource | aws_transfer_server | Ensure Transfer Server is not exposed publicly. | Terraform |
+| 185 | CKV_AWS_165 | resource | aws_dynamodb_global_table | Ensure Dynamodb point in time recovery (backup) is enabled for global tables | Terraform |
+| 186 | CKV_AWS_166 | resource | aws_backup_vault | Ensure Backup Vault is encrypted at rest using KMS CMK | Terraform |
+| 187 | CKV_AWS_167 | resource | aws_glacier_vault | Ensure Glacier Vault access policy is not public by only allowing specific services or principals to access it | Terraform |
+| 188 | CKV_AWS_168 | resource | aws_sqs_queue_policy | Ensure SQS queue policy is not public by only allowing specific services or principals to access it | Terraform |
+| 189 | CKV_AWS_169 | resource | aws_sns_topic_policy | Ensure SNS topic policy is not public by only allowing specific services or principals to access it | Terraform |
+| 190 | CKV_AWS_170 | resource | aws_qldb_ledger | Ensure QLDB ledger permissions mode is set to STANDARD | Terraform |
+| 191 | CKV_AWS_171 | resource | aws_emr_security_configuration | Ensure Cluster security configuration encryption is using SSE-KMS | Terraform |
+| 192 | CKV_AWS_172 | resource | aws_qldb_ledger | Ensure QLDB ledger has deletion protection enabled | Terraform |
+| 193 | CKV2_AWS_1 | resource | aws_network_acl | Ensure that all NACL are attached to subnets | Terraform |
+| 194 | CKV2_AWS_1 | resource | aws_subnet | Ensure that all NACL are attached to subnets | Terraform |
+| 195 | CKV2_AWS_2 | resource | aws_ebs_volume | Ensure that only encrypted EBS volumes are attached to EC2 instances | Terraform |
+| 196 | CKV2_AWS_2 | resource | aws_volume_attachment | Ensure that only encrypted EBS volumes are attached to EC2 instances | Terraform |
+| 197 | CKV2_AWS_3 | resource | aws_guardduty_detector | Ensure GuardDuty is enabled to specific org/region | Terraform |
+| 198 | CKV2_AWS_3 | resource | aws_guardduty_organization_configuration | Ensure GuardDuty is enabled to specific org/region | Terraform |
+| 199 | CKV2_AWS_4 | resource | aws_api_gateway_stage | Ensure API Gateway stage have logging level defined as appropriate | Terraform |
+| 200 | CKV2_AWS_4 | resource | aws_api_gateway_method_settings | Ensure API Gateway stage have logging level defined as appropriate | Terraform |
+| 201 | CKV2_AWS_5 | resource | aws_security_group | Ensure that Security Groups are attached to an other resource | Terraform |
+| 202 | CKV2_AWS_6 | resource | aws_s3_bucket_public_access_block | Ensure that S3 bucket has a Public Access block | Terraform |
+| 203 | CKV2_AWS_6 | resource | aws_s3_bucket | Ensure that S3 bucket has a Public Access block | Terraform |
+| 204 | CKV2_AWS_7 | resource | aws_security_group | Ensure that Amazon EMR clusters' security groups are not open to the world | Terraform |
+| 205 | CKV2_AWS_7 | resource | aws_emr_cluster | Ensure that Amazon EMR clusters' security groups are not open to the world | Terraform |
+| 206 | CKV2_AWS_8 | resource | aws_rds_cluster | Ensure that RDS clusters has backup plan of AWS Backup | Terraform |
+| 207 | CKV2_AWS_9 | resource | aws_backup_selection | Ensure that EBS are added in the backup plans of AWS Backup | Terraform |
+| 208 | CKV2_AWS_10 | resource | aws_cloudtrail | Ensure CloudTrail trails are integrated with CloudWatch Logs | Terraform |
+| 209 | CKV2_AWS_11 | resource | aws_vpc | Ensure VPC flow logging is enabled in all VPCs | Terraform |
+| 210 | CKV2_AWS_12 | resource | aws_vpc | Ensure the default security group of every VPC restricts all traffic | Terraform |
+| 211 | CKV2_AWS_12 | resource | aws_default_security_group | Ensure the default security group of every VPC restricts all traffic | Terraform |
+| 212 | CKV2_AWS_13 | resource | aws_redshift_cluster | Ensure that Redshift clusters has backup plan of AWS Backup | Terraform |
+| 213 | CKV2_AWS_14 | resource | aws_iam_group_membership | Ensure that IAM groups includes at least one IAM user | Terraform |
+| 214 | CKV2_AWS_14 | resource | aws_iam_group | Ensure that IAM groups includes at least one IAM user | Terraform |
+| 215 | CKV2_AWS_15 | resource | aws_autoscaling_group | Ensure that auto Scaling groups that are associated with a load balancer, are using Elastic Load Balancing health checks. | Terraform |
+| 216 | CKV2_AWS_15 | resource | aws_elb | Ensure that auto Scaling groups that are associated with a load balancer, are using Elastic Load Balancing health checks. | Terraform |
+| 217 | CKV2_AWS_16 | resource | aws_dynamodb_table | Ensure that Auto Scaling is enabled on your DynamoDB tables | Terraform |
+| 218 | CKV2_AWS_16 | resource | aws_appautoscaling_target | Ensure that Auto Scaling is enabled on your DynamoDB tables | Terraform |
+| 219 | CKV2_AWS_18 | resource | aws_backup_selection | Ensure that Elastic File System (Amazon EFS) file systems are added in the backup plans of AWS Backup | Terraform |
+| 220 | CKV2_AWS_19 | resource | aws_eip | Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances | Terraform |
+| 221 | CKV2_AWS_19 | resource | aws_eip_association | Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances | Terraform |
+| 222 | CKV2_AWS_20 | resource | aws_lb_listener | Ensure that ALB redirects HTTP requests into HTTPS ones | Terraform |
+| 223 | CKV2_AWS_20 | resource | aws_lb | Ensure that ALB redirects HTTP requests into HTTPS ones | Terraform |
+| 224 | CKV2_AWS_21 | resource | aws_iam_group_membership | Ensure that all IAM users are members of at least one IAM group. | Terraform |
+| 225 | CKV2_AWS_22 | resource | aws_iam_user | Ensure an IAM User does not have access to the console | Terraform |
+| 226 | CKV2_AWS_23 | resource | aws_route53_record | Route53 A Record has Attached Resource | Terraform |
+| 227 | CKV2_AWS_27 | resource | aws_db_instance | Postgres RDS has Query Logging enabled | Terraform |
+| 228 | CKV2_AWS_27 | resource | aws_rds_cluster_parameter_group | Postgres RDS has Query Logging enabled | Terraform |
+| 229 | CKV2_AWS_28 | resource | aws_lb | Ensure public facing ALB are protected by WAF | Terraform |
+| 230 | CKV2_AWS_29 | resource | aws_api_gateway_stage | Ensure public API gateway are protected by WAF | Terraform |
+| 231 | CKV2_AWS_29 | resource | aws_api_gateway_rest_api | Ensure public API gateway are protected by WAF | Terraform |
+| 232 | CKV_AZURE_1 | resource | azurerm_linux_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 233 | CKV_AZURE_1 | resource | azurerm_virtual_machine | Ensure Azure Instance does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 234 | CKV_AZURE_2 | resource | azurerm_managed_disk | Ensure Azure managed disk has encryption enabled | Terraform |
+| 235 | CKV_AZURE_3 | resource | azurerm_storage_account | Ensure that 'Secure transfer required' is set to 'Enabled' | Terraform |
+| 236 | CKV_AZURE_4 | resource | azurerm_kubernetes_cluster | Ensure AKS logging to Azure Monitoring is Configured | Terraform |
+| 237 | CKV_AZURE_5 | resource | azurerm_kubernetes_cluster | Ensure RBAC is enabled on AKS clusters | Terraform |
+| 238 | CKV_AZURE_6 | resource | azurerm_kubernetes_cluster | Ensure AKS has an API Server Authorized IP Ranges enabled | Terraform |
+| 239 | CKV_AZURE_7 | resource | azurerm_kubernetes_cluster | Ensure AKS cluster has Network Policy configured | Terraform |
+| 240 | CKV_AZURE_8 | resource | azurerm_kubernetes_cluster | Ensure Kube Dashboard is disabled | Terraform |
+| 241 | CKV_AZURE_9 | resource | azurerm_network_security_rule | Ensure that RDP access is restricted from the internet | Terraform |
+| 242 | CKV_AZURE_9 | resource | azurerm_network_security_group | Ensure that RDP access is restricted from the internet | Terraform |
+| 243 | CKV_AZURE_10 | resource | azurerm_network_security_rule | Ensure that SSH access is restricted from the internet | Terraform |
+| 244 | CKV_AZURE_10 | resource | azurerm_network_security_group | Ensure that SSH access is restricted from the internet | Terraform |
+| 245 | CKV_AZURE_11 | resource | azurerm_mariadb_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 246 | CKV_AZURE_11 | resource | azurerm_sql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 247 | CKV_AZURE_11 | resource | azurerm_postgresql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 248 | CKV_AZURE_11 | resource | azurerm_mysql_firewall_rule | Ensure no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP) | Terraform |
+| 249 | CKV_AZURE_12 | resource | azurerm_network_watcher_flow_log | Ensure that Network Security Group Flow Log retention period is 'greater than 90 days' | Terraform |
+| 250 | CKV_AZURE_13 | resource | azurerm_app_service | Ensure App Service Authentication is set on Azure App Service | Terraform |
+| 251 | CKV_AZURE_14 | resource | azurerm_app_service | Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service | Terraform |
+| 252 | CKV_AZURE_15 | resource | azurerm_app_service | Ensure web app is using the latest version of TLS encryption | Terraform |
+| 253 | CKV_AZURE_16 | resource | azurerm_app_service | Ensure that Register with Azure Active Directory is enabled on App Service | Terraform |
+| 254 | CKV_AZURE_17 | resource | azurerm_app_service | Ensure the web app has 'Client Certificates (Incoming client certificates)' set | Terraform |
+| 255 | CKV_AZURE_18 | resource | azurerm_app_service | Ensure that 'HTTP Version' is the latest if used to run the web app | Terraform |
+| 256 | CKV_AZURE_19 | resource | azurerm_security_center_subscription_pricing | Ensure that standard pricing tier is selected | Terraform |
+| 257 | CKV_AZURE_20 | resource | azurerm_security_center_contact | Ensure that security contact 'Phone number' is set | Terraform |
+| 258 | CKV_AZURE_21 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
+| 259 | CKV_AZURE_22 | resource | azurerm_security_center_contact | Ensure that 'Send email notification for high severity alerts' is set to 'On' | Terraform |
+| 260 | CKV_AZURE_23 | resource | azurerm_sql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
+| 261 | CKV_AZURE_23 | resource | azurerm_mssql_server | Ensure that 'Auditing' is set to 'On' for SQL servers | Terraform |
+| 262 | CKV_AZURE_24 | resource | azurerm_sql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
+| 263 | CKV_AZURE_24 | resource | azurerm_mssql_server | Ensure that 'Auditing' Retention is 'greater than 90 days' for SQL servers | Terraform |
+| 264 | CKV_AZURE_25 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Threat Detection types' is set to 'All' | Terraform |
+| 265 | CKV_AZURE_26 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Send Alerts To' is enabled for MSSQL servers | Terraform |
+| 266 | CKV_AZURE_27 | resource | azurerm_mssql_server_security_alert_policy | Ensure that 'Email service and co-administrators' is 'Enabled' for MSSQL servers | Terraform |
+| 267 | CKV_AZURE_28 | resource | azurerm_mysql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MySQL Database Server | Terraform |
+| 268 | CKV_AZURE_29 | resource | azurerm_postgresql_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for PostgreSQL Database Server | Terraform |
+| 269 | CKV_AZURE_30 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 270 | CKV_AZURE_31 | resource | azurerm_postgresql_configuration | Ensure server parameter 'log_connections' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 271 | CKV_AZURE_32 | resource | azurerm_postgresql_configuration | Ensure server parameter 'connection_throttling' is set to 'ON' for PostgreSQL Database Server | Terraform |
+| 272 | CKV_AZURE_33 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Queue service for read, write and delete requests | Terraform |
+| 273 | CKV_AZURE_34 | resource | azurerm_storage_container | Ensure that 'Public access level' is set to Private for blob containers | Terraform |
+| 274 | CKV_AZURE_35 | resource | azurerm_storage_account | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
+| 275 | CKV_AZURE_35 | resource | azurerm_storage_account_network_rules | Ensure default network access rule for Storage Accounts is set to deny | Terraform |
+| 276 | CKV_AZURE_36 | resource | azurerm_storage_account | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
+| 277 | CKV_AZURE_36 | resource | azurerm_storage_account_network_rules | Ensure 'Trusted Microsoft Services' is enabled for Storage Account access | Terraform |
+| 278 | CKV_AZURE_37 | resource | azurerm_monitor_log_profile | Ensure that Activity Log Retention is set 365 days or greater | Terraform |
+| 279 | CKV_AZURE_38 | resource | azurerm_monitor_log_profile | Ensure audit profile captures all the activities | Terraform |
+| 280 | CKV_AZURE_39 | resource | azurerm_role_definition | Ensure that no custom subscription owner roles are created | Terraform |
+| 281 | CKV_AZURE_40 | resource | azurerm_key_vault_key | Ensure that the expiration date is set on all keys | Terraform |
+| 282 | CKV_AZURE_41 | resource | azurerm_key_vault_secret | Ensure that the expiration date is set on all secrets | Terraform |
+| 283 | CKV_AZURE_42 | resource | azurerm_key_vault | Ensure the key vault is recoverable | Terraform |
+| 284 | CKV_AZURE_43 | resource | azurerm_storage_account | Ensure the Storage Account naming rules | Terraform |
+| 285 | CKV_AZURE_44 | resource | azurerm_storage_account | Ensure Storage Account is using the latest version of TLS encryption | Terraform |
+| 286 | CKV_AZURE_45 | resource | azurerm_virtual_machine | Ensure that no sensitive credentials are exposed in VM custom_data | Terraform |
+| 287 | CKV_AZURE_46 | resource | azurerm_mssql_database_extended_auditing_policy | Specifies a retention period of less than 90 days. | Terraform |
+| 288 | CKV_AZURE_47 | resource | azurerm_mariadb_server | Ensure 'Enforce SSL connection' is set to 'ENABLED' for MariaDB servers | Terraform |
+| 289 | CKV_AZURE_48 | resource | azurerm_mariadb_server | Ensure 'public network access enabled' is set to 'False' for MariaDB servers | Terraform |
+| 290 | CKV_AZURE_49 | resource | azurerm_linux_virtual_machine_scale_set | Ensure Azure linux scale set does not use basic authentication(Use SSH Key Instead) | Terraform |
+| 291 | CKV_AZURE_50 | resource | azurerm_linux_virtual_machine | Ensure Virtual Machine Extensions are not Installed | Terraform |
+| 292 | CKV_AZURE_50 | resource | azurerm_virtual_machine | Ensure Virtual Machine Extensions are not Installed | Terraform |
+| 293 | CKV_AZURE_52 | resource | azurerm_mssql_server | Ensure MSSQL is using the latest version of TLS encryption | Terraform |
+| 294 | CKV_AZURE_53 | resource | azurerm_mysql_server | Ensure 'public network access enabled' is set to 'False' for mySQL servers | Terraform |
+| 295 | CKV_AZURE_54 | resource | azurerm_mysql_server | Ensure MySQL is using the latest version of TLS encryption | Terraform |
+| 296 | CKV_AZURE_55 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Servers | Terraform |
+| 297 | CKV_AZURE_56 | resource | azurerm_function_app | Ensure that function apps enables Authentication | Terraform |
+| 298 | CKV_AZURE_57 | resource | azurerm_app_service | Ensure that CORS disallows every resource to access app services | Terraform |
+| 299 | CKV_AZURE_58 | resource | azurerm_synapse_workspace | Ensure that Azure Synapse workspaces enables managed virtual networks | Terraform |
+| 300 | CKV_AZURE_59 | resource | azurerm_storage_account | Ensure that Storage accounts disallow public access | Terraform |
+| 301 | CKV_AZURE_60 | resource | azurerm_storage_account | Ensure that storage account enables secure transfer | Terraform |
+| 302 | CKV_AZURE_61 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for App Service | Terraform |
+| 303 | CKV_AZURE_62 | resource | azurerm_function_app | Ensure function apps are not accessible from all regions | Terraform |
+| 304 | CKV_AZURE_63 | resource | azurerm_app_service | Ensure that App service enables HTTP logging | Terraform |
+| 305 | CKV_AZURE_64 | resource | azurerm_storage_sync | Ensure that Azure File Sync disables public network access | Terraform |
+| 306 | CKV_AZURE_65 | resource | azurerm_app_service | Ensure that App service enables detailed error messages | Terraform |
+| 307 | CKV_AZURE_66 | resource | azurerm_app_service | Ensure that App service enables failed request tracing | Terraform |
+| 308 | CKV_AZURE_67 | resource | azurerm_function_app | Ensure that 'HTTP Version' is the latest, if used to run the Function app | Terraform |
+| 309 | CKV_AZURE_68 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server disables public network access | Terraform |
+| 310 | CKV_AZURE_69 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Azure SQL database servers | Terraform |
+| 311 | CKV_AZURE_70 | resource | azurerm_function_app | Ensure that Function apps is only accessible over HTTPS | Terraform |
+| 312 | CKV_AZURE_71 | resource | azurerm_app_service | Ensure that Managed identity provider is enabled for app services | Terraform |
+| 313 | CKV_AZURE_72 | resource | azurerm_app_service | Ensure that remote debugging is not enabled for app services | Terraform |
+| 314 | CKV_AZURE_73 | resource | azurerm_automation_variable_bool | Ensure that Automation account variables are encrypted | Terraform |
+| 315 | CKV_AZURE_73 | resource | azurerm_automation_variable_string | Ensure that Automation account variables are encrypted | Terraform |
+| 316 | CKV_AZURE_73 | resource | azurerm_automation_variable_int | Ensure that Automation account variables are encrypted | Terraform |
+| 317 | CKV_AZURE_73 | resource | azurerm_automation_variable_datetime | Ensure that Automation account variables are encrypted | Terraform |
+| 318 | CKV_AZURE_74 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer uses disk encryption | Terraform |
+| 319 | CKV_AZURE_75 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer uses double encryption | Terraform |
+| 320 | CKV_AZURE_76 | resource | azurerm_batch_account | Ensure that Azure Batch account uses key vault to encrypt data | Terraform |
+| 321 | CKV_AZURE_77 | resource | azurerm_network_security_rule | Ensure that UDP Services are restricted from the Internet | Terraform |
+| 322 | CKV_AZURE_77 | resource | azurerm_network_security_group | Ensure that UDP Services are restricted from the Internet | Terraform |
+| 323 | CKV_AZURE_78 | resource | azurerm_app_service | Ensure FTP deployments are disabled | Terraform |
+| 324 | CKV_AZURE_79 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for SQL servers on machines | Terraform |
+| 325 | CKV_AZURE_80 | resource | azurerm_app_service | Ensure that 'Net Framework' version is the latest, if used as a part of the web app | Terraform |
+| 326 | CKV_AZURE_81 | resource | azurerm_app_service | Ensure that 'PHP version' is the latest, if used to run the web app | Terraform |
+| 327 | CKV_AZURE_82 | resource | azurerm_app_service | Ensure that 'Python version' is the latest, if used to run the web app | Terraform |
+| 328 | CKV_AZURE_83 | resource | azurerm_app_service | Ensure that 'Java version' is the latest, if used to run the web app | Terraform |
+| 329 | CKV_AZURE_84 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Storage | Terraform |
+| 330 | CKV_AZURE_85 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Kubernetes | Terraform |
+| 331 | CKV_AZURE_86 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Container Registries | Terraform |
+| 332 | CKV_AZURE_87 | resource | azurerm_security_center_subscription_pricing | Ensure that Azure Defender is set to On for Key Vault | Terraform |
+| 333 | CKV_AZURE_88 | resource | azurerm_app_service | Ensure that app services use Azure Files | Terraform |
+| 334 | CKV_AZURE_89 | resource | azurerm_redis_cache | Ensure that Azure Cache for Redis disables public network access | Terraform |
+| 335 | CKV_AZURE_90 | resource | azurerm_mysql_server | Ensure that MySQL server disables public network access | Terraform |
+| 336 | CKV_AZURE_91 | resource | azurerm_redis_cache | Ensure that only SSL are enabled for Cache for Redis | Terraform |
+| 337 | CKV_AZURE_92 | resource | azurerm_linux_virtual_machine | Ensure that Virtual Machines use managed disks | Terraform |
+| 338 | CKV_AZURE_92 | resource | azurerm_windows_virtual_machine | Ensure that Virtual Machines use managed disks | Terraform |
+| 339 | CKV_AZURE_93 | resource | azurerm_managed_disk | Ensure that managed disks use a specific set of disk encryption sets for the customer-managed key encryption | Terraform |
+| 340 | CKV_AZURE_94 | resource | azurerm_mysql_server | Ensure that My SQL server enables geo-redundant backups | Terraform |
+| 341 | CKV_AZURE_95 | resource | azurerm_virtual_machine_scale_set | Ensure that automatic OS image patching is enabled for Virtual Machine Scale Sets | Terraform |
+| 342 | CKV_AZURE_96 | resource | azurerm_mysql_server | Ensure that MySQL server enables infrastructure encryption | Terraform |
+| 343 | CKV_AZURE_97 | resource | azurerm_linux_virtual_machine_scale_set | Ensure that Virtual machine scale sets have encryption at host enabled | Terraform |
+| 344 | CKV_AZURE_97 | resource | azurerm_windows_virtual_machine_scale_set | Ensure that Virtual machine scale sets have encryption at host enabled | Terraform |
+| 345 | CKV_AZURE_98 | resource | azurerm_container_group | Ensure that Azure Container group is deployed into virtual network | Terraform |
+| 346 | CKV_AZURE_99 | resource | azurerm_cosmosdb_account | Ensure Cosmos DB accounts have restricted access | Terraform |
+| 347 | CKV_AZURE_100 | resource | azurerm_cosmosdb_account | Ensure that Cosmos DB accounts have customer-managed keys to encrypt data at rest | Terraform |
+| 348 | CKV_AZURE_101 | resource | azurerm_cosmosdb_account | Ensure that Azure Cosmos DB disables public network access | Terraform |
+| 349 | CKV_AZURE_102 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables geo-redundant backups | Terraform |
+| 350 | CKV_AZURE_103 | resource | azurerm_data_factory | Ensure that Azure Data Factory uses Git repository for source control | Terraform |
+| 351 | CKV_AZURE_104 | resource | azurerm_data_factory | Ensure that Azure Data factory public network access is disabled | Terraform |
+| 352 | CKV_AZURE_105 | resource | azurerm_data_lake_store | Ensure that Data Lake Store accounts enables encryption | Terraform |
+| 353 | CKV_AZURE_106 | resource | azurerm_eventgrid_domain | Ensure that Azure Event Grid Domain public network access is disabled | Terraform |
+| 354 | CKV_AZURE_107 | resource | azurerm_api_management | Ensure that API management services use virtual networks | Terraform |
+| 355 | CKV_AZURE_108 | resource | azurerm_iothub | Ensure that Azure IoT Hub disables public network access | Terraform |
+| 356 | CKV_AZURE_109 | resource | azurerm_key_vault | Ensure that key vault allows firewall rules settings | Terraform |
+| 357 | CKV_AZURE_110 | resource | azurerm_key_vault | Ensure that key vault enables purge protection | Terraform |
+| 358 | CKV_AZURE_111 | resource | azurerm_key_vault | Ensure that key vault enables soft delete | Terraform |
+| 359 | CKV_AZURE_112 | resource | azurerm_key_vault_key | Ensure that key vault key is backed by HSM | Terraform |
+| 360 | CKV_AZURE_113 | resource | azurerm_mssql_server | Ensure that SQL server disables public network access | Terraform |
+| 361 | CKV_AZURE_114 | resource | azurerm_key_vault_secret | Ensure that key vault secrets have "content_type" set | Terraform |
+| 362 | CKV_AZURE_115 | resource | azurerm_kubernetes_cluster | Ensure that AKS enables private clusters | Terraform |
+| 363 | CKV_AZURE_116 | resource | azurerm_kubernetes_cluster | Ensure that AKS uses Azure Policies Add-on | Terraform |
+| 364 | CKV_AZURE_117 | resource | azurerm_kubernetes_cluster | Ensure that AKS uses disk encryption set | Terraform |
+| 365 | CKV_AZURE_118 | resource | azurerm_network_interface | Ensure that Network Interfaces disable IP forwarding | Terraform |
+| 366 | CKV_AZURE_119 | resource | azurerm_network_interface | Ensure that Network Interfaces don't use public IPs | Terraform |
+| 367 | CKV_AZURE_120 | resource | azurerm_application_gateway | Ensure that Application Gateway enables WAF | Terraform |
+| 368 | CKV_AZURE_121 | resource | azurerm_frontdoor | Ensure that Azure Front Door enables WAF | Terraform |
+| 369 | CKV_AZURE_122 | resource | azurerm_web_application_firewall_policy | Ensure that Application Gateway uses WAF in "Detection" or "Prevention" modes | Terraform |
+| 370 | CKV_AZURE_123 | resource | azurerm_frontdoor_firewall_policy | Ensure that Azure Front Door uses WAF in "Detection" or "Prevention" modes | Terraform |
+| 371 | CKV_AZURE_124 | resource | azurerm_search_service | Ensure that Azure Cognitive Search disables public network access | Terraform |
+| 372 | CKV_AZURE_125 | resource | azurerm_service_fabric_cluster | Ensures that Service Fabric use three levels of protection available | Terraform |
+| 373 | CKV_AZURE_126 | resource | azurerm_service_fabric_cluster | Ensures that Active Directory is used for authentication for Service Fabric | Terraform |
+| 374 | CKV_AZURE_127 | resource | azurerm_mysql_server | Ensure that My SQL server enables Threat detection policy | Terraform |
+| 375 | CKV_AZURE_128 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables Threat detection policy | Terraform |
+| 376 | CKV_AZURE_129 | resource | azurerm_mariadb_server | Ensure that MariaDB server enables geo-redundant backups | Terraform |
+| 377 | CKV_AZURE_130 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables infrastructure encryption | Terraform |
+| 378 | CKV_AZURE_131 | resource | azurerm_security_center_contact | Ensure that 'Security contact emails' is set | Terraform |
+| 379 | CKV2_AZURE_1 | resource | azurerm_storage_account | Ensure storage for critical data are encrypted with Customer Managed Key | Terraform |
+| 380 | CKV2_AZURE_2 | resource | azurerm_mssql_server_security_alert_policy | Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account | Terraform |
+| 381 | CKV2_AZURE_2 | resource | azurerm_sql_server | Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account | Terraform |
+| 382 | CKV2_AZURE_3 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 383 | CKV2_AZURE_3 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 384 | CKV2_AZURE_3 | resource | azurerm_sql_server | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 385 | CKV2_AZURE_4 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 386 | CKV2_AZURE_4 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 387 | CKV2_AZURE_4 | resource | azurerm_sql_server | Ensure that VA setting Periodic Recurring Scans is enabled on a SQL server | Terraform |
+| 388 | CKV2_AZURE_5 | resource | azurerm_mssql_server_security_alert_policy | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 389 | CKV2_AZURE_5 | resource | azurerm_mssql_server_vulnerability_assessment | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 390 | CKV2_AZURE_5 | resource | azurerm_sql_server | Ensure that VA setting 'Also send email notifications to admins and subscription owners' is set for a SQL server | Terraform |
+| 391 | CKV2_AZURE_6 | resource | azurerm_sql_firewall_rule | Ensure 'Allow access to Azure services' for PostgreSQL Database Server is disabled | Terraform |
+| 392 | CKV2_AZURE_6 | resource | azurerm_sql_server | Ensure 'Allow access to Azure services' for PostgreSQL Database Server is disabled | Terraform |
+| 393 | CKV2_AZURE_7 | resource | azurerm_sql_server | Ensure that Azure Active Directory Admin is configured | Terraform |
+| 394 | CKV2_AZURE_8 | resource | azurerm_monitor_activity_log_alert | Ensure the storage container storing the activity logs is not publicly accessible | Terraform |
+| 395 | CKV2_AZURE_8 | resource | azurerm_storage_container | Ensure the storage container storing the activity logs is not publicly accessible | Terraform |
+| 396 | CKV2_AZURE_9 | resource | azurerm_virtual_machine | Ensure Virtual Machines are utilizing Managed Disks | Terraform |
+| 397 | CKV2_AZURE_10 | resource | azurerm_virtual_machine_extension | Ensure that Microsoft Antimalware is configured to automatically updates for Virtual Machines | Terraform |
+| 398 | CKV2_AZURE_10 | resource | azurerm_virtual_machine | Ensure that Microsoft Antimalware is configured to automatically updates for Virtual Machines | Terraform |
+| 399 | CKV2_AZURE_11 | resource | azurerm_kusto_cluster | Ensure that Azure Data Explorer encryption at rest uses a customer-managed key | Terraform |
+| 400 | CKV2_AZURE_12 | resource | azurerm_virtual_machine | Ensure that virtual machines are backed up using Azure Backup | Terraform |
+| 401 | CKV2_AZURE_13 | resource | azurerm_mssql_server_security_alert_policy | Ensure that sql servers enables data security policy | Terraform |
+| 402 | CKV2_AZURE_13 | resource | azurerm_sql_server | Ensure that sql servers enables data security policy | Terraform |
+| 403 | CKV2_AZURE_14 | resource | azurerm_managed_disk | Ensure that Unattached disks are encrypted | Terraform |
+| 404 | CKV2_AZURE_14 | resource | azurerm_virtual_machine | Ensure that Unattached disks are encrypted | Terraform |
+| 405 | CKV2_AZURE_15 | resource | azurerm_data_factory | Ensure that Azure data factories are encrypted with a customer-managed key | Terraform |
+| 406 | CKV2_AZURE_16 | resource | azurerm_mysql_server_key | Ensure that MySQL server enables customer-managed key for encryption | Terraform |
+| 407 | CKV2_AZURE_16 | resource | azurerm_mysql_server | Ensure that MySQL server enables customer-managed key for encryption | Terraform |
+| 408 | CKV2_AZURE_17 | resource | azurerm_postgresql_server | Ensure that PostgreSQL server enables customer-managed key for encryption | Terraform |
+| 409 | CKV2_AZURE_17 | resource | azurerm_postgresql_server_key | Ensure that PostgreSQL server enables customer-managed key for encryption | Terraform |
+| 410 | CKV2_AZURE_18 | resource | azurerm_storage_account | Ensure that Storage Accounts use customer-managed key for encryption | Terraform |
+| 411 | CKV2_AZURE_18 | resource | azurerm_storage_account_customer_managed_key | Ensure that Storage Accounts use customer-managed key for encryption | Terraform |
+| 412 | CKV2_AZURE_19 | resource | azurerm_synapse_workspace | Ensure that Azure Synapse workspaces have no IP firewall rules attached | Terraform |
+| 413 | CKV2_AZURE_20 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 414 | CKV2_AZURE_20 | resource | azurerm_log_analytics_storage_insights | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 415 | CKV2_AZURE_20 | resource | azurerm_storage_table | Ensure Storage logging is enabled for Table service for read requests | Terraform |
+| 416 | CKV2_AZURE_21 | resource | azurerm_storage_account | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 417 | CKV2_AZURE_21 | resource | azurerm_log_analytics_storage_insights | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 418 | CKV2_AZURE_21 | resource | azurerm_storage_container | Ensure Storage logging is enabled for Blob service for read requests | Terraform |
+| 419 | CKV_GCP_1 | resource | google_container_cluster | Ensure Stackdriver Logging is set to Enabled on Kubernetes Engine Clusters | Terraform |
+| 420 | CKV_GCP_2 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted ssh access | Terraform |
+| 421 | CKV_GCP_3 | resource | google_compute_firewall | Ensure Google compute firewall ingress does not allow unrestricted rdp access | Terraform |
+| 422 | CKV_GCP_4 | resource | google_compute_ssl_policy | Ensure no HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites | Terraform |
+| 423 | CKV_GCP_6 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance requires all incoming connections to use SSL | Terraform |
+| 424 | CKV_GCP_7 | resource | google_container_cluster | Ensure Legacy Authorization is set to Disabled on Kubernetes Engine Clusters | Terraform |
+| 425 | CKV_GCP_8 | resource | google_container_cluster | Ensure Stackdriver Monitoring is set to Enabled on Kubernetes Engine Clusters | Terraform |
+| 426 | CKV_GCP_9 | resource | google_container_node_pool | Ensure 'Automatic node repair' is enabled for Kubernetes Clusters | Terraform |
+| 427 | CKV_GCP_10 | resource | google_container_node_pool | Ensure 'Automatic node upgrade' is enabled for Kubernetes Clusters | Terraform |
+| 428 | CKV_GCP_11 | resource | google_sql_database_instance | Ensure that Cloud SQL database Instances are not open to the world | Terraform |
+| 429 | CKV_GCP_12 | resource | google_container_cluster | Ensure Network Policy is enabled on Kubernetes Engine Clusters | Terraform |
+| 430 | CKV_GCP_13 | resource | google_container_cluster | Ensure a client certificate is used by clients to authenticate to Kubernetes Engine Clusters | Terraform |
+| 431 | CKV_GCP_14 | resource | google_sql_database_instance | Ensure all Cloud SQL database instance have backup configuration enabled | Terraform |
+| 432 | CKV_GCP_15 | resource | google_bigquery_dataset | Ensure that BigQuery datasets are not anonymously or publicly accessible | Terraform |
+| 433 | CKV_GCP_16 | resource | google_dns_managed_zone | Ensure that DNSSEC is enabled for Cloud DNS | Terraform |
+| 434 | CKV_GCP_17 | resource | google_dns_managed_zone | Ensure that RSASHA1 is not used for the zone-signing and key-signing keys in Cloud DNS DNSSEC | Terraform |
+| 435 | CKV_GCP_18 | resource | google_container_cluster | Ensure GKE Control Plane is not public | Terraform |
+| 436 | CKV_GCP_19 | resource | google_container_cluster | Ensure GKE basic auth is disabled | Terraform |
+| 437 | CKV_GCP_20 | resource | google_container_cluster | Ensure master authorized networks is set to enabled in GKE clusters | Terraform |
+| 438 | CKV_GCP_21 | resource | google_container_cluster | Ensure Kubernetes Clusters are configured with Labels | Terraform |
+| 439 | CKV_GCP_22 | resource | google_container_node_pool | Ensure Container-Optimized OS (cos) is used for Kubernetes Engine Clusters Node image | Terraform |
+| 440 | CKV_GCP_23 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Alias IP ranges enabled | Terraform |
+| 441 | CKV_GCP_24 | resource | google_container_cluster | Ensure PodSecurityPolicy controller is enabled on the Kubernetes Engine Clusters | Terraform |
+| 442 | CKV_GCP_25 | resource | google_container_cluster | Ensure Kubernetes Cluster is created with Private cluster enabled | Terraform |
+| 443 | CKV_GCP_26 | resource | google_compute_subnetwork | Ensure that VPC Flow Logs is enabled for every subnet in a VPC Network | Terraform |
+| 444 | CKV_GCP_27 | resource | google_project | Ensure that the default network does not exist in a project | Terraform |
+| 445 | CKV_GCP_28 | resource | google_storage_bucket_iam_member | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
+| 446 | CKV_GCP_28 | resource | google_storage_bucket_iam_binding | Ensure that Cloud Storage bucket is not anonymously or publicly accessible | Terraform |
+| 447 | CKV_GCP_29 | resource | google_storage_bucket | Ensure that Cloud Storage buckets have uniform bucket-level access enabled | Terraform |
+| 448 | CKV_GCP_30 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account | Terraform |
+| 449 | CKV_GCP_31 | resource | google_compute_instance | Ensure that instances are not configured to use the default service account with full access to all Cloud APIs | Terraform |
+| 450 | CKV_GCP_32 | resource | google_compute_instance | Ensure 'Block Project-wide SSH keys' is enabled for VM instances | Terraform |
+| 451 | CKV_GCP_33 | resource | google_compute_project_metadata | Ensure oslogin is enabled for a Project | Terraform |
+| 452 | CKV_GCP_34 | resource | google_compute_instance | Ensure that no instance in the project overrides the project setting for enabling OSLogin(OSLogin needs to be enabled in project metadata for all instances) | Terraform |
+| 453 | CKV_GCP_35 | resource | google_compute_instance | Ensure 'Enable connecting to serial ports' is not enabled for VM Instance | Terraform |
+| 454 | CKV_GCP_36 | resource | google_compute_instance | Ensure that IP forwarding is not enabled on Instances | Terraform |
+| 455 | CKV_GCP_37 | resource | google_compute_disk | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
+| 456 | CKV_GCP_38 | resource | google_compute_instance | Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK) | Terraform |
+| 457 | CKV_GCP_39 | resource | google_compute_instance | Ensure Compute instances are launched with Shielded VM enabled | Terraform |
+| 458 | CKV_GCP_40 | resource | google_compute_instance | Ensure that Compute instances do not have public IP addresses | Terraform |
+| 459 | CKV_GCP_41 | resource | google_project_iam_member | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
+| 460 | CKV_GCP_41 | resource | google_project_iam_binding | Ensure that IAM users are not assigned the Service Account User or Service Account Token Creator roles at project level | Terraform |
+| 461 | CKV_GCP_42 | resource | google_project_iam_member | Ensure that Service Account has no Admin privileges | Terraform |
+| 462 | CKV_GCP_43 | resource | google_kms_crypto_key | Ensure KMS encryption keys are rotated within a period of 90 days | Terraform |
+| 463 | CKV_GCP_44 | resource | google_folder_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
+| 464 | CKV_GCP_44 | resource | google_folder_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a folder level | Terraform |
+| 465 | CKV_GCP_45 | resource | google_organization_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
+| 466 | CKV_GCP_45 | resource | google_organization_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at an organization level | Terraform |
+| 467 | CKV_GCP_46 | resource | google_project_iam_member | Ensure Default Service account is not used at a project level | Terraform |
+| 468 | CKV_GCP_46 | resource | google_project_iam_binding | Ensure Default Service account is not used at a project level | Terraform |
+| 469 | CKV_GCP_47 | resource | google_organization_iam_member | Ensure default service account is not used at an organization level | Terraform |
+| 470 | CKV_GCP_47 | resource | google_organization_iam_binding | Ensure default service account is not used at an organization level | Terraform |
+| 471 | CKV_GCP_48 | resource | google_folder_iam_member | Ensure Default Service account is not used at a folder level | Terraform |
+| 472 | CKV_GCP_48 | resource | google_folder_iam_binding | Ensure Default Service account is not used at a folder level | Terraform |
+| 473 | CKV_GCP_49 | resource | google_project_iam_member | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
+| 474 | CKV_GCP_49 | resource | google_project_iam_binding | Ensure no roles that enable to impersonate and manage all service accounts are used at a project level | Terraform |
+| 475 | CKV_GCP_50 | resource | google_sql_database_instance | Ensure MySQL database 'local_infile' flag is set to 'off' | Terraform |
+| 476 | CKV_GCP_51 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_checkpoints' flag is set to 'on' | Terraform |
+| 477 | CKV_GCP_52 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_connections' flag is set to 'on' | Terraform |
+| 478 | CKV_GCP_53 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_disconnections' flag is set to 'on' | Terraform |
+| 479 | CKV_GCP_54 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_lock_waits' flag is set to 'on' | Terraform |
+| 480 | CKV_GCP_55 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_messages' flag is set to a valid value | Terraform |
+| 481 | CKV_GCP_56 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_temp_files flag is set to '0' | Terraform |
+| 482 | CKV_GCP_57 | resource | google_sql_database_instance | Ensure PostgreSQL database 'log_min_duration_statement' flag is set to '-1' | Terraform |
+| 483 | CKV_GCP_58 | resource | google_sql_database_instance | Ensure SQL database 'cross db ownership chaining' flag is set to 'off' | Terraform |
+| 484 | CKV_GCP_59 | resource | google_sql_database_instance | Ensure SQL database 'contained database authentication' flag is set to 'off' | Terraform |
+| 485 | CKV_GCP_60 | resource | google_sql_database_instance | Ensure SQL database do not have public IP | Terraform |
+| 486 | CKV_GCP_61 | resource | google_container_cluster | Enable VPC Flow Logs and Intranode Visibility | Terraform |
+| 487 | CKV_GCP_62 | resource | google_storage_bucket | Bucket should log access | Terraform |
+| 488 | CKV_GCP_63 | resource | google_storage_bucket | Bucket should not log to itself | Terraform |
+| 489 | CKV_GCP_64 | resource | google_container_cluster | Ensure clusters are created with Private Nodes | Terraform |
+| 490 | CKV_GCP_65 | resource | google_container_cluster | Manage Kubernetes RBAC users with Google Groups for GKE | Terraform |
+| 491 | CKV_GCP_66 | resource | google_container_cluster | Ensure use of Binary Authorization | Terraform |
+| 492 | CKV_GCP_67 | resource | google_container_cluster | Ensure legacy Compute Engine instance metadata APIs are Disabled | Terraform |
+| 493 | CKV_GCP_68 | resource | google_container_cluster | Ensure Secure Boot for Shielded GKE Nodes is Enabled | Terraform |
+| 494 | CKV_GCP_68 | resource | google_container_node_pool | Ensure Secure Boot for Shielded GKE Nodes is Enabled | Terraform |
+| 495 | CKV_GCP_69 | resource | google_container_cluster | Ensure the GKE Metadata Server is Enabled | Terraform |
+| 496 | CKV_GCP_69 | resource | google_container_node_pool | Ensure the GKE Metadata Server is Enabled | Terraform |
+| 497 | CKV_GCP_70 | resource | google_container_cluster | Ensure the GKE Release Channel is set | Terraform |
+| 498 | CKV_GCP_71 | resource | google_container_cluster | Ensure Shielded GKE Nodes are Enabled | Terraform |
+| 499 | CKV_GCP_72 | resource | google_container_cluster | Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled | Terraform |
+| 500 | CKV_GCP_72 | resource | google_container_node_pool | Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled | Terraform |
+| 501 | CKV2_GCP_1 | resource | google_project_default_service_accounts | Ensure GKE clusters are not running using the Compute Engine default service account | Terraform |
+| 502 | CKV2_GCP_2 | resource | google_compute_network | Ensure legacy networks do not exist for a project | Terraform |
+| 503 | CKV2_GCP_3 | resource | google_compute_node_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 504 | CKV2_GCP_3 | resource | google_healthcare_dicom_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 505 | CKV2_GCP_3 | resource | google_cloudbuild_trigger | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 506 | CKV2_GCP_3 | resource | google_usage_export_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 507 | CKV2_GCP_3 | resource | google_tpu_node | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 508 | CKV2_GCP_3 | resource | google_cloud_asset_project_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 509 | CKV2_GCP_3 | resource | google_healthcare_hl7_v2_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 510 | CKV2_GCP_3 | resource | google_logging_project_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 511 | CKV2_GCP_3 | resource | google_folder_iam_audit_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 512 | CKV2_GCP_3 | resource | google_storage_object_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 513 | CKV2_GCP_3 | resource | google_access_context_manager_access_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 514 | CKV2_GCP_3 | resource | google_spanner_database | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 515 | CKV2_GCP_3 | resource | google_composer_environment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 516 | CKV2_GCP_3 | resource | google_project_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 517 | CKV2_GCP_3 | resource | google_monitoring_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 518 | CKV2_GCP_3 | resource | google_folder_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 519 | CKV2_GCP_3 | resource | google_ml_engine_model | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 520 | CKV2_GCP_3 | resource | google_network_management_connectivity_test_resource | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 521 | CKV2_GCP_3 | resource | google_dns_record_set | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 522 | CKV2_GCP_3 | resource | google_compute_shared_vpc_host_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 523 | CKV2_GCP_3 | resource | google_storage_bucket_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 524 | CKV2_GCP_3 | resource | google_compute_backend_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 525 | CKV2_GCP_3 | resource | google_storage_default_object_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 526 | CKV2_GCP_3 | resource | google_folder_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 527 | CKV2_GCP_3 | resource | google_scc_source | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 528 | CKV2_GCP_3 | resource | google_cloudfunctions_function | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 529 | CKV2_GCP_3 | resource | google_spanner_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 530 | CKV2_GCP_3 | resource | google_compute_global_address | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 531 | CKV2_GCP_3 | resource | google_compute_router_nat | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 532 | CKV2_GCP_3 | resource | google_project_iam_custom_role | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 533 | CKV2_GCP_3 | resource | google_firestore_index | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 534 | CKV2_GCP_3 | resource | google_compute_instance_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 535 | CKV2_GCP_3 | resource | google_bigtable_gc_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 536 | CKV2_GCP_3 | resource | google_healthcare_dicom_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 537 | CKV2_GCP_3 | resource | google_dataproc_cluster_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 538 | CKV2_GCP_3 | resource | google_organization_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 539 | CKV2_GCP_3 | resource | google_iap_app_engine_version_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 540 | CKV2_GCP_3 | resource | google_bigquery_data_transfer_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 541 | CKV2_GCP_3 | resource | google_compute_network_peering_routes_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 542 | CKV2_GCP_3 | resource | google_healthcare_dataset_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 543 | CKV2_GCP_3 | resource | google_sql_source_representation_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 544 | CKV2_GCP_3 | resource | google_iap_client | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 545 | CKV2_GCP_3 | resource | google_compute_vpn_gateway | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 546 | CKV2_GCP_3 | resource | google_dns_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 547 | CKV2_GCP_3 | resource | google_compute_resource_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 548 | CKV2_GCP_3 | resource | google_filestore_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 549 | CKV2_GCP_3 | resource | google_bigquery_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 550 | CKV2_GCP_3 | resource | google_dataproc_autoscaling_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 551 | CKV2_GCP_3 | resource | google_container_cluster | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 552 | CKV2_GCP_3 | resource | google_compute_interconnect_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 553 | CKV2_GCP_3 | resource | google_active_directory_domain | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 554 | CKV2_GCP_3 | resource | google_bigquery_table | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 555 | CKV2_GCP_3 | resource | google_compute_network_peering | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 556 | CKV2_GCP_3 | resource | google_identity_platform_tenant_inbound_saml_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 557 | CKV2_GCP_3 | resource | google_identity_platform_tenant_default_supported_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 558 | CKV2_GCP_3 | resource | google_compute_target_pool | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 559 | CKV2_GCP_3 | resource | google_compute_target_http_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 560 | CKV2_GCP_3 | resource | google_access_context_manager_service_perimeter_resource | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 561 | CKV2_GCP_3 | resource | google_service_account | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 562 | CKV2_GCP_3 | resource | google_compute_region_autoscaler | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 563 | CKV2_GCP_3 | resource | google_compute_target_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 564 | CKV2_GCP_3 | resource | google_compute_image | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 565 | CKV2_GCP_3 | resource | google_bigquery_dataset_access | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 566 | CKV2_GCP_3 | resource | google_logging_project_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 567 | CKV2_GCP_3 | resource | google_compute_target_ssl_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 568 | CKV2_GCP_3 | resource | google_sourcerepo_repository_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 569 | CKV2_GCP_3 | resource | google_bigtable_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 570 | CKV2_GCP_3 | resource | google_container_analysis_occurrence | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 571 | CKV2_GCP_3 | resource | google_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 572 | CKV2_GCP_3 | resource | google_cloud_tasks_queue | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 573 | CKV2_GCP_3 | resource | google_compute_ssl_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 574 | CKV2_GCP_3 | resource | google_dialogflow_agent | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 575 | CKV2_GCP_3 | resource | google_container_registry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 576 | CKV2_GCP_3 | resource | google_bigtable_app_profile | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 577 | CKV2_GCP_3 | resource | google_secret_manager_secret_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 578 | CKV2_GCP_3 | resource | google_pubsub_topic | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 579 | CKV2_GCP_3 | resource | google_compute_global_forwarding_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 580 | CKV2_GCP_3 | resource | google_storage_hmac_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 581 | CKV2_GCP_3 | resource | google_healthcare_fhir_store_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 582 | CKV2_GCP_3 | resource | google_compute_network_endpoint | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 583 | CKV2_GCP_3 | resource | google_compute_region_disk_resource_policy_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 584 | CKV2_GCP_3 | resource | google_compute_region_ssl_certificate | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 585 | CKV2_GCP_3 | resource | google_compute_region_target_http_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 586 | CKV2_GCP_3 | resource | google_billing_account_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 587 | CKV2_GCP_3 | resource | google_identity_platform_inbound_saml_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 588 | CKV2_GCP_3 | resource | google_sql_ssl_cert | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 589 | CKV2_GCP_3 | resource | google_storage_bucket_object | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 590 | CKV2_GCP_3 | resource | google_compute_instance_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 591 | CKV2_GCP_3 | resource | google_compute_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 592 | CKV2_GCP_3 | resource | google_datastore_index | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 593 | CKV2_GCP_3 | resource | google_compute_attached_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 594 | CKV2_GCP_3 | resource | google_app_engine_firewall_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 595 | CKV2_GCP_3 | resource | google_compute_network_endpoint_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 596 | CKV2_GCP_3 | resource | google_kms_key_ring_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 597 | CKV2_GCP_3 | resource | google_runtimeconfig_config_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 598 | CKV2_GCP_3 | resource | google_vpc_access_connector | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 599 | CKV2_GCP_3 | resource | google_storage_transfer_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 600 | CKV2_GCP_3 | resource | google_compute_region_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 601 | CKV2_GCP_3 | resource | google_compute_ssl_certificate | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 602 | CKV2_GCP_3 | resource | google_logging_metric | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 603 | CKV2_GCP_3 | resource | google_compute_target_tcp_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 604 | CKV2_GCP_3 | resource | google_container_analysis_note | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 605 | CKV2_GCP_3 | resource | google_iap_tunnel_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 606 | CKV2_GCP_3 | resource | google_compute_disk_resource_policy_attachment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 607 | CKV2_GCP_3 | resource | google_pubsub_subscription_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 608 | CKV2_GCP_3 | resource | google_project_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 609 | CKV2_GCP_3 | resource | google_service_account_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 610 | CKV2_GCP_3 | resource | google_compute_region_instance_group_manager | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 611 | CKV2_GCP_3 | resource | google_kms_crypto_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 612 | CKV2_GCP_3 | resource | google_data_catalog_entry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 613 | CKV2_GCP_3 | resource | google_compute_router_bgp_peer | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 614 | CKV2_GCP_3 | resource | google_storage_notification | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 615 | CKV2_GCP_3 | resource | google_logging_folder_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 616 | CKV2_GCP_3 | resource | google_dataproc_cluster | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 617 | CKV2_GCP_3 | resource | google_compute_router_interface | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 618 | CKV2_GCP_3 | resource | google_dataflow_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 619 | CKV2_GCP_3 | resource | google_cloud_scheduler_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 620 | CKV2_GCP_3 | resource | google_cloud_run_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 621 | CKV2_GCP_3 | resource | google_compute_subnetwork_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 622 | CKV2_GCP_3 | resource | google_secret_manager_secret | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 623 | CKV2_GCP_3 | resource | google_project_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 624 | CKV2_GCP_3 | resource | google_billing_account_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 625 | CKV2_GCP_3 | resource | google_folder_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 626 | CKV2_GCP_3 | resource | google_compute_autoscaler | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 627 | CKV2_GCP_3 | resource | google_app_engine_domain_mapping | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 628 | CKV2_GCP_3 | resource | google_cloud_run_domain_mapping | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 629 | CKV2_GCP_3 | resource | google_compute_https_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 630 | CKV2_GCP_3 | resource | google_cloud_run_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 631 | CKV2_GCP_3 | resource | google_compute_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 632 | CKV2_GCP_3 | resource | google_runtimeconfig_variable | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 633 | CKV2_GCP_3 | resource | google_logging_project_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 634 | CKV2_GCP_3 | resource | google_organization_iam_custom_role | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 635 | CKV2_GCP_3 | resource | google_organization_iam_audit_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 636 | CKV2_GCP_3 | resource | google_compute_instance_group_named_port | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 637 | CKV2_GCP_3 | resource | google_compute_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 638 | CKV2_GCP_3 | resource | google_compute_project_metadata_item | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 639 | CKV2_GCP_3 | resource | google_compute_reservation | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 640 | CKV2_GCP_3 | resource | google_organization_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 641 | CKV2_GCP_3 | resource | google_identity_platform_tenant | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 642 | CKV2_GCP_3 | resource | google_storage_object_access_control | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 643 | CKV2_GCP_3 | resource | google_monitoring_metric_descriptor | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 644 | CKV2_GCP_3 | resource | google_os_login_ssh_public_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 645 | CKV2_GCP_3 | resource | google_service_networking_connection | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 646 | CKV2_GCP_3 | resource | google_app_engine_application_url_dispatch_rules | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 647 | CKV2_GCP_3 | resource | google_app_engine_flexible_app_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 648 | CKV2_GCP_3 | resource | google_storage_default_object_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 649 | CKV2_GCP_3 | resource | google_compute_region_url_map | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 650 | CKV2_GCP_3 | resource | google_logging_billing_account_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 651 | CKV2_GCP_3 | resource | google_iap_web_type_app_engine_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 652 | CKV2_GCP_3 | resource | google_logging_organization_exclusion | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 653 | CKV2_GCP_3 | resource | google_compute_project_default_network_tier | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 654 | CKV2_GCP_3 | resource | google_spanner_instance_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 655 | CKV2_GCP_3 | resource | google_compute_url_map | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 656 | CKV2_GCP_3 | resource | google_endpoints_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 657 | CKV2_GCP_3 | resource | google_compute_instance_from_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 658 | CKV2_GCP_3 | resource | google_cloud_asset_folder_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 659 | CKV2_GCP_3 | resource | google_kms_key_ring_import_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 660 | CKV2_GCP_3 | resource | google_data_catalog_tag | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 661 | CKV2_GCP_3 | resource | google_iap_web_type_compute_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 662 | CKV2_GCP_3 | resource | google_endpoints_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 663 | CKV2_GCP_3 | resource | google_dialogflow_intent | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 664 | CKV2_GCP_3 | resource | google_monitoring_slo | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 665 | CKV2_GCP_3 | resource | google_compute_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 666 | CKV2_GCP_3 | resource | google_compute_backend_bucket_signed_url_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 667 | CKV2_GCP_3 | resource | google_compute_backend_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 668 | CKV2_GCP_3 | resource | google_sql_user | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 669 | CKV2_GCP_3 | resource | google_storage_bucket | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 670 | CKV2_GCP_3 | resource | google_pubsub_topic_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 671 | CKV2_GCP_3 | resource | google_bigquery_dataset_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 672 | CKV2_GCP_3 | resource | google_runtimeconfig_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 673 | CKV2_GCP_3 | resource | google_binary_authorization_attestor | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 674 | CKV2_GCP_3 | resource | google_service_account_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 675 | CKV2_GCP_3 | resource | google_access_context_manager_service_perimeter | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 676 | CKV2_GCP_3 | resource | google_deployment_manager_deployment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 677 | CKV2_GCP_3 | resource | google_storage_bucket_iam_binding | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 678 | CKV2_GCP_3 | resource | google_dialogflow_entity_type | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 679 | CKV2_GCP_3 | resource | google_resource_manager_lien | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 680 | CKV2_GCP_3 | resource | google_iap_brand | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 681 | CKV2_GCP_3 | resource | google_binary_authorization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 682 | CKV2_GCP_3 | resource | google_compute_snapshot | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 683 | CKV2_GCP_3 | resource | google_cloudfunctions_cloud_function_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 684 | CKV2_GCP_3 | resource | google_billing_account_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 685 | CKV2_GCP_3 | resource | google_folder | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 686 | CKV2_GCP_3 | resource | google_storage_bucket_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 687 | CKV2_GCP_3 | resource | google_app_engine_standard_app_version | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 688 | CKV2_GCP_3 | resource | google_app_engine_application | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 689 | CKV2_GCP_3 | resource | google_compute_security_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 690 | CKV2_GCP_3 | resource | google_compute_global_network_endpoint_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 691 | CKV2_GCP_3 | resource | google_logging_organization_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 692 | CKV2_GCP_3 | resource | google_access_context_manager_access_level | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 693 | CKV2_GCP_3 | resource | google_cloud_asset_organization_feed | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 694 | CKV2_GCP_3 | resource | google_cloudiot_device | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 695 | CKV2_GCP_3 | resource | google_monitoring_notification_channel | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 696 | CKV2_GCP_3 | resource | google_compute_firewall | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 697 | CKV2_GCP_3 | resource | google_compute_vpn_tunnel | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 698 | CKV2_GCP_3 | resource | google_healthcare_fhir_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 699 | CKV2_GCP_3 | resource | google_healthcare_dataset | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 700 | CKV2_GCP_3 | resource | google_cloudiot_device_registry | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 701 | CKV2_GCP_3 | resource | google_sourcerepo_repository | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 702 | CKV2_GCP_3 | resource | google_compute_region_disk | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 703 | CKV2_GCP_3 | resource | google_compute_network | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 704 | CKV2_GCP_3 | resource | google_storage_bucket_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 705 | CKV2_GCP_3 | resource | google_redis_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 706 | CKV2_GCP_3 | resource | google_sql_database | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 707 | CKV2_GCP_3 | resource | google_bigtable_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 708 | CKV2_GCP_3 | resource | google_data_catalog_entry_group_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 709 | CKV2_GCP_3 | resource | google_monitoring_alert_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 710 | CKV2_GCP_3 | resource | google_compute_http_health_check | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 711 | CKV2_GCP_3 | resource | google_identity_platform_tenant_oauth_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 712 | CKV2_GCP_3 | resource | google_binary_authorization_attestor_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 713 | CKV2_GCP_3 | resource | google_project_iam_member | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 714 | CKV2_GCP_3 | resource | google_compute_region_backend_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 715 | CKV2_GCP_3 | resource | google_app_engine_service_split_traffic | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 716 | CKV2_GCP_3 | resource | google_compute_region_target_https_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 717 | CKV2_GCP_3 | resource | google_container_node_pool | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 718 | CKV2_GCP_3 | resource | google_spanner_database_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 719 | CKV2_GCP_3 | resource | google_compute_global_network_endpoint | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 720 | CKV2_GCP_3 | resource | google_dns_managed_zone | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 721 | CKV2_GCP_3 | resource | google_compute_subnetwork | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 722 | CKV2_GCP_3 | resource | google_healthcare_hl7_v2_store | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 723 | CKV2_GCP_3 | resource | google_compute_project_metadata | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 724 | CKV2_GCP_3 | resource | google_storage_bucket_acl | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 725 | CKV2_GCP_3 | resource | google_dataproc_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 726 | CKV2_GCP_3 | resource | google_pubsub_subscription | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 727 | CKV2_GCP_3 | resource | google_iap_web_backend_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 728 | CKV2_GCP_3 | resource | google_sql_database_instance | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 729 | CKV2_GCP_3 | resource | google_data_catalog_tag_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 730 | CKV2_GCP_3 | resource | google_project_service | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 731 | CKV2_GCP_3 | resource | google_compute_route | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 732 | CKV2_GCP_3 | resource | google_compute_router | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 733 | CKV2_GCP_3 | resource | google_iap_app_engine_service_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 734 | CKV2_GCP_3 | resource | google_organization_iam_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 735 | CKV2_GCP_3 | resource | google_compute_backend_service_signed_url_key | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 736 | CKV2_GCP_3 | resource | google_monitoring_dashboard | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 737 | CKV2_GCP_3 | resource | google_dataflow_flex_template_job | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 738 | CKV2_GCP_3 | resource | google_logging_billing_account_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 739 | CKV2_GCP_3 | resource | google_kms_key_ring | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 740 | CKV2_GCP_3 | resource | google_identity_platform_oauth_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 741 | CKV2_GCP_3 | resource | google_compute_node_template | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 742 | CKV2_GCP_3 | resource | google_os_config_patch_deployment | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 743 | CKV2_GCP_3 | resource | google_logging_organization_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 744 | CKV2_GCP_3 | resource | google_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 745 | CKV2_GCP_3 | resource | google_data_catalog_entry_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 746 | CKV2_GCP_3 | resource | google_logging_folder_bucket_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 747 | CKV2_GCP_3 | resource | google_identity_platform_default_supported_idp_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 748 | CKV2_GCP_3 | resource | google_secret_manager_secret_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 749 | CKV2_GCP_3 | resource | google_kms_crypto_key_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 750 | CKV2_GCP_3 | resource | google_compute_target_https_proxy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 751 | CKV2_GCP_3 | resource | google_compute_address | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 752 | CKV2_GCP_3 | resource | google_logging_folder_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 753 | CKV2_GCP_3 | resource | google_logging_billing_account_sink | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 754 | CKV2_GCP_3 | resource | google_iap_web_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 755 | CKV2_GCP_3 | resource | google_compute_forwarding_rule | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 756 | CKV2_GCP_3 | resource | google_monitoring_uptime_check_config | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 757 | CKV2_GCP_3 | resource | google_bigquery_dataset | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 758 | CKV2_GCP_3 | resource | google_compute_instance_group_manager | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 759 | CKV2_GCP_3 | resource | google_dataproc_job_iam | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 760 | CKV2_GCP_3 | resource | google_kms_secret_ciphertext | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 761 | CKV2_GCP_3 | resource | google_monitoring_group | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 762 | CKV2_GCP_3 | resource | google_bigtable_table | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 763 | CKV2_GCP_3 | resource | google_folder_organization_policy | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 764 | CKV2_GCP_3 | resource | google_compute_shared_vpc_service_project | Ensure that there are only GCP-managed service account keys for each service account | Terraform |
+| 765 | CKV2_GCP_4 | resource | google_logging_project_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 766 | CKV2_GCP_4 | resource | google_storage_bucket | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 767 | CKV2_GCP_4 | resource | google_logging_folder_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 768 | CKV2_GCP_4 | resource | google_logging_organization_sink | Ensure that retention policies on log buckets are configured using Bucket Lock | Terraform |
+| 769 | CKV2_GCP_5 | resource | google_project | Ensure that Cloud Audit Logging is configured properly across all services and all users from a project | Terraform |
+| 770 | CKV2_GCP_5 | resource | google_project_iam_audit_config | Ensure that Cloud Audit Logging is configured properly across all services and all users from a project | Terraform |
+| 771 | CKV2_GCP_6 | resource | google_kms_crypto_key_iam_binding | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 772 | CKV2_GCP_6 | resource | google_kms_crypto_key_iam_member | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 773 | CKV2_GCP_6 | resource | google_kms_crypto_key | Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible | Terraform |
+| 774 | CKV2_GCP_7 | resource | google_sql_user | Ensure that a MySQL database instance does not allow anyone to connect with administrative privileges | Terraform |
+| 775 | CKV2_GCP_7 | resource | google_sql_database_instance | Ensure that a MySQL database instance does not allow anyone to connect with administrative privileges | Terraform |
+| 776 | CKV_GIT_1 | resource | github_repository | Ensure Repository is Private | Terraform |
+| 777 | CKV_LIN_1 | provider | linode | Ensure no hard coded Linode tokens exist in provider | Terraform |
+| 778 | CKV_LIN_2 | resource | linode_instance | Ensure SSH key set in authorized_keys | Terraform |
+
+
+---
+
+
diff --git a/docs/5.Contribution/New-Provider.md b/docs/6.Contribution/Contribute New Terraform Provider.md
similarity index 74%
rename from docs/5.Contribution/New-Provider.md
rename to docs/6.Contribution/Contribute New Terraform Provider.md
index c37def57d4..16c8173982 100644
--- a/docs/5.Contribution/New-Provider.md
+++ b/docs/6.Contribution/Contribute New Terraform Provider.md
@@ -1,15 +1,21 @@
-# Contributing a new Provider
+---
+layout: default
+published: true
+title: Contribute New Terraform Provider
+nav_order: 4
+---
-In this example we'll add support for a new Terraform Provider, the Linode Cloud platform.
+# Contribute New Terraform Provider
+In this example we'll add support for a new Terraform Provider, the Linode Cloud platform.
-## Add resource checks for a new provider
+## Add Resource Checks for a New Provider
-This check is going to examine resources of the type: **linode_instance**, to ensure they have the property **authorised_keys** set.
+This check is going to examine resources of the type: `linode_instance`, to ensure they have the property `authorised_keys` set.
-### Add a test
+### Add a Test
-First create a new folder **tests/terraform/checks/resource/linode/** and add **test_authorised_keys.py** with this:
+First create a new folder `tests/terraform/checks/resource/linode/` and add `test_authorised_keys.py` using the code below:
```python
import unittest
@@ -44,14 +50,11 @@ if __name__ == '__main__':
unittest.main()
```
-Add a placeholder file at **tests/terraform/checks/resource/linode/__init__.py**
-
-```python
-```
+Add a placeholder file at `tests/terraform/checks/resource/linode/__init__.py`
-### Add the check
+### Add a Check
-Create the folder **checkov/checkov/terraform/checks/resource/linode** and add **authorized_keys.py** with:
+Create the folder `checkov/checkov/terraform/checks/resource/linode` and add `authorized_keys.py`:
```python
from checkov.common.models.enums import CheckCategories, CheckResult
@@ -75,7 +78,7 @@ class authorized_keys(BaseResourceCheck):
check = authorized_keys()
```
-And also add **checkov/terraform/checks/resource/linode/__init__.py** with:
+And also add `checkov/terraform/checks/resource/linode/__init__.py`:
```python
from os.path import dirname, basename, isfile, join
@@ -84,10 +87,9 @@ modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
```
-### Include the checks
-
-In **checkov/terraform/checks/resource/__init__.py**, update include Linode resources with the entry "from checkov.terraform.checks.resource.linode import * ".
+### Include Checks
+In `checkov/terraform/checks/resource/__init__.py`, update include Linode resources with the entry `from checkov.terraform.checks.resource.linode import *`.
This will ensure that this and any future Linode resource test are included in Checkov runs:
```python
@@ -97,13 +99,13 @@ from checkov.terraform.checks.resource.github import *
from checkov.terraform.checks.resource.linode import *
```
-## Add new Provider checks
+## Add New Provider Checks
-This Provider check verifies that the user hasn't added their Linode token secret to their file. Adding that to a Public repository, well that would be bad, very bad.
+This Provider check verifies that the user hasn't added their Linode secret token to their file. Adding the secret token to a Public repository would cause many problems.
-### Adding a test
+### Adding a Test
-Create the folder **tests/terraform/checks/provider/linode/** and **test_credentials.py**
+Create the folder `tests/terraform/checks/provider/linode/` and `test_credentials.py`
```python
import unittest
@@ -129,14 +131,11 @@ if __name__ == '__main__':
unittest.main()
```
-Then add the placeholder **tests/terraform/checks/provider/linode/__init__.py**
+Then add the placeholder `tests/terraform/checks/provider/linode/__init__.py`
-```python
-```
-
-### Add the Provider check
+### Add the Provider Check
-Create a directory **checkov/terraform/checks/provider/linode** and add **credentials.py**
+Create a directory `checkov/terraform/checks/provider/linode` and add `credentials.py`
```python
import re
@@ -171,9 +170,8 @@ class LinodeCredentials(BaseProviderCheck):
check = LinodeCredentials()
```
-And also **checkov/terraform/checks/provider/linode/__init__.py**
-
-Update the security constants **checkov/common/models/consts.py** with the new pattern.
+And also `checkov/terraform/checks/provider/linode/__init__.py`
+Update the security constants `checkov/common/models/consts.py` with the new pattern.
```python
SUPPORTED_FILE_EXTENSIONS = [".tf", ".yml", ".yaml", ".json", ".template"]
@@ -182,7 +180,6 @@ DOCKER_IMAGE_REGEX = r'(?:[^\s\/]+/)?([^\s:]+):?([^\s]*)'
access_key_pattern = "(?/checks//`, where **type ** is the Custom Policy's type and **provider** is the Custom Policy's provider.
+
+A Custom Policy is a class implementing an abstract base class that corresponds to some provider and type.
+
+For example, all Custom Policies of **resource** type and **aws **provider implement the resource base class found at `checkov/terraform/checks/resource/base_check.py`. The resource check needs to implement its base abstract method named `scan_resource_conf`, which accepts as an input a dictionary of all the key-valued resource attributes, and outputs a CheckResult.
+
+Define a policy as described [here](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html).
+
+## Example
+`checkov/terraform/checks/resource/aws/APIGatewayCacheEnable.py`
+
+```python
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from checkov.common.models.enums import CheckCategories
+
+
+class APIGatewayCacheEnable(BaseResourceValueCheck):
+
+ def __init__(self):
+ name = "Ensure API Gateway caching is enabled"
+ id = "CKV_AWS_120"
+ supported_resources = ['aws_api_gateway_stage']
+ categories = [CheckCategories.BACKUP_AND_RECOVERY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def get_inspected_key(self):
+ return "cache_cluster_enabled"
+
+
+check = APIGatewayCacheEnable()
+```
+
+# Testing
+
+Assuming the implemented check’s class is file is found in checkov/terraform/checks// directory, named .py, create an appropriate unit test file in tests/terraform/checks// directory, named test_.py.
+
+The test suite should cover different check results; Test if the check outputs PASSED on a compliant configuration, and test if it output FAILED on a non-compliant configuration. You are also encouraged to test more specific components of the check, according to their complexity.
+
+
+## Example
+
+`tests/terraform/checks/resource/aws/test_APIGatewayCacheEnable.py`
+```python
+import unittest
+import hcl2
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.APIGatewayCacheEnable import check
+
+
+class TestAPIGatewayCacheEnable(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_api_gateway_rest_api" "example" {
+ name = "example"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_api_gateway_rest_api']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_api_gateway_rest_api" "example" {
+ name = "example"
+ cache_cluster_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_api_gateway_rest_api']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
+```
+
diff --git a/docs/6.Contribution/Contribute YAML-based Policies.md b/docs/6.Contribution/Contribute YAML-based Policies.md
new file mode 100644
index 0000000000..869ab819fc
--- /dev/null
+++ b/docs/6.Contribution/Contribute YAML-based Policies.md
@@ -0,0 +1,104 @@
+---
+layout: default
+published: true
+title: Contribute YAML-based Policies
+nav_order: 3
+---
+
+# Contributing YAML-based Custom Policies
+
+1. Define a policy as described [here](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html).
+2. Create a branch under the `checkov2` fork (will be changed + the URLs after merge) - `https://github.com/nimrodkor/checkov`
+3. Add `.yaml` file to `https://github.com/nimrodkor/checkov/tree/master/checkov/graph/terraform/checks` inside the relevant provider folder that matches your current policy.
+
+## Example
+`checkov/terraform/checks/graph_checks/aws/EBSAddedBackup.yaml`
+
+```yaml
+metadata:
+ name: "Ensure that EBS are added in the backup plans of AWS Backup"
+ id: "CKV2_AWS_9"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: connection
+ resource_types:
+ - aws_backup_selection
+ connected_resource_types:
+ - aws_ebs_volume
+ operator: exists
+ - cond_type: filter
+ attribute: resource_type
+ value:
+ - aws_ebs_volume
+ operator: within
+```
+
+## YAML Format Testing
+1 - Add the test resources directory to: `https://github.com/nimrodkor/checkov/tree/master/tests/graph/terraform/checks/resources` and create a folder with the same name as your Custom Policy. In this folder, add the Terraform file(s) which are resources for testing the policy, and `expected.yaml` - all the resources that should pass and the resources that should fail.
+
+### Terraform Files Example
+`tests/graph/terraform/checks/resources/EBSAddedBackup/main.tf`
+
+```yaml
+resource "aws_ebs_volume" "ebs_good" {
+ availability_zone = "us-west-2a"
+ size = 40
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_ebs_volume" "ebs_bad" {
+ availability_zone = "us-west-2a"
+ size = 40
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_backup_selection" "backup_good" {
+ iam_role_arn = "arn"
+ name = "tf_example_backup_selection"
+ plan_id = "123456"
+
+ resources = [
+ aws_ebs_volume.ebs_good.arn
+ ]
+}
+
+resource "aws_backup_selection" "backup_bad" {
+ iam_role_arn = "arn"
+ name = "tf_example_backup_selection"
+ plan_id = "123456"
+
+ resources = [
+ ]
+}
+
+```
+
+## 'expected.yaml' File Example
+
+`tests/graph/terraform/checks/resources/EBSAddedBackup/expected.yaml`
+
+```yaml
+pass:
+ - "aws_ebs_volume.ebs_good"
+fail:
+ - "aws_ebs_volume.ebs_bad"
+```
+
+2 - Add the test call into tests file -
+`tests/graph/terraform/checks/test_yaml_policies.py`
+### Example
+
+```yaml
+...
+ def test_EBSAddedBackup(self):
+ self.go("EBSAddedBackup")
+...
+
+```
diff --git a/docs/6.Contribution/Contribution Overview.md b/docs/6.Contribution/Contribution Overview.md
new file mode 100644
index 0000000000..c50791556e
--- /dev/null
+++ b/docs/6.Contribution/Contribution Overview.md
@@ -0,0 +1,96 @@
+---
+layout: default
+published: true
+title: Contribution Overview
+nav_order: 1
+---
+
+# Contribution Overview
+
+Checkov users are encouraged to contribute their custom Policies to help increase our existing IaC coverage.
+Our aim is to help close gaps in real-world hardening, assessments, auditing and forensics. In other words, we specifically encourage contribution of new Policies that you think should be globally accepted when provisioning and changing infrastructure.
+
+The main aspects of contributing new Policies are:
+ * Preparing the prerequisites
+ * Creating and Testing the Custom Policy (either YAML or Python format)
+ * Pull Request
+
+## Prerequisites
+
+### Video guide
+
+
+
+### Installation
+
+First, make sure you installed and configured Checkov correctly. If you are unsure, go back and read the [Installing Checkov documentation](https://www.checkov.io/2.Basics/Installing%20Checkov.html).
+
+Preferably by now you have either scanned a folder containing Terraform state-files or went ahead and integrated Checkov as part of your CI/CD pipeline.
+
+### Custom Policy Structure
+
+Each check consists of the following mandatory properties:
+
+**name:** A new Custom Policy's unique purpose. It should ideally specify the positive desired outcome of the policy.
+
+**ID:** A mandatory unique identifier of a policy. Policies written by Checkov maintainers follow the following convention: **CKV_providerType_serialNumber**. (e.g., CKV_AWS_9 , CKV_GCP_12)
+
+**Categories:** A categorization of a scan. This is usually helpful when producing compliance reports, pipeline analytics and health metrics. Check out our existing categories before creating a new one.
+
+When contributing a Custom Policy, please increment the ID number to be x+1, where x is the serial number of the latest implemented Custom Policy, with respect to its provider (e.g., AWS).
+
+A more specific type of Custom Policy may also include additional attributes. For example, a check that scans a Terraform resource configuration also contains the supported_resources attribute, which is a list of the supported resource types of the check.
+
+
+### Result
+
+The result of a scan should be a binary result of either PASSED or FAILED. We have also included an UNKNOWN option, which means that it is unknown if the scanned configuration complied with the check. If your check could have edge cases that might not be supported by the scanner’s current logic, consider support the UNKNOWN option.
+
+Additionally, a Policy can be suppressed by Checkov on a given configuration by inserting a skip comment inside a specific configuration scope. Then, the result for that Policy would be SKIPPED.
+For further details, see [Suppressions](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html).
+
+### IaC Type Scanner
+
+Identify which IaC type the check will test. Currently, Checkov can scan either Terraform or CloudFormation configuration files.
+Place your code in the `checkov/` folder, where `` is either `terraform` or `cloudformation`.
+
+Identify which IaC type will be tested under the Custom Policy. Currently, Checkov scans either Terraform or CloudFormation configuration files. Place your code in the `checkov/` folder, where `` is either terraform or cloudformation.
+
+### Custom Policy Type and Provider
+
+Custom Policies are divided first into folders grouped by type, and then grouped by provider.
+
+Custom Policies should relate to a common IaC configuration type of a specific public cloud provider. For example, a Custom Policy that validates the encryption configuration of an S3 bucket is considered to be of type `resource`, and of `aws` provider.
+
+Identify the type and provider of the new Custom Policy in order to place it correctly under the project structure. For example, the mentioned above check is already implemented in Checkov under `checkov/terraform/checks/resource/aws/S3Encryption.py`.
+
+Notice that Custom Policies are divided into folders grouped by type, and then grouped provider.
+
+### Review IaC Configuration Documentation
+
+If available, please provide the official Terraform or CloudFormation documentation of the checked configuration. This helps users to better understand the Custom Policy's scanned configuration and usage.
+
+For example, the documentation for the Custom Policy mentioned above is [here](https://www.checkov.io/3.Custom%20Policies/Custom%20Policies%20Overview.html).
+
+### Sample IaC Configuration
+
+In order to develop the Custom Policy, a relevant example configuration should be presented as an input to Checkov. Provide a sample configuration (e.g., `example.tf`, `template.json`) that contains both passing and failing configurations with respect to the Custom Policy's logic. The file can be served as an input to the appropriate Custom Policy's unit tests.
+
+## Creating and Testing the Custom Policy
+ * See [Create Python Policies](https://www.checkov.io/3.Custom%20Policies/Python%20Custom%20Policies.html) and [Contribute Python-Based Policies](https://www.checkov.io/6.Contribution/Contribute%20Python-Based%20Policies.html).
+ * See [Create YAML Policies](https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html) and [Contribute YAML-Based Policies](https://www.checkov.io/6.Contribution/Contribute%20YAML-based%20Policies.html).
+
+## Pull Request
+Open a PR that contains the implementation code and testing suite, with the following information:
+
+ * Custom Policy `id`.
+ * Custom Policy `name`.
+ * Custom Policy IaC type.
+ * Custom Policy type and provider.
+ * IaC configuration documentation (If available).
+ * Sample Terraform configuration file.
+ * Any additional information that would help other members to better understand the check.
diff --git a/docs/Gemfile b/docs/Gemfile
new file mode 100644
index 0000000000..61f586fb3f
--- /dev/null
+++ b/docs/Gemfile
@@ -0,0 +1,34 @@
+source "https://rubygems.org"
+# Hello! This is where you manage which Jekyll version is used to run.
+# When you want to use a different version, change it below, save the
+# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
+#
+# bundle exec jekyll serve
+#
+# This will help ensure the proper Jekyll version is running.
+# Happy Jekylling!
+#gem "jekyll", "~> 4.2.0"
+# This is the default theme for new Jekyll sites. You may change this to anything you like.
+#gem "minima", "~> 2.5"
+# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
+# uncomment the line below. To upgrade, run `bundle update github-pages`.
+gem "github-pages", group: :jekyll_plugins
+# If you have any plugins, put them here!
+group :jekyll_plugins do
+ gem "jekyll-feed", "~> 0.11"
+ gem "jekyll-redirect-from"
+ gem "jekyll-remote-theme"
+end
+
+# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
+# and associated library.
+platforms :mingw, :x64_mingw, :mswin, :jruby do
+ gem "tzinfo", "~> 1.2"
+ gem "tzinfo-data"
+end
+
+# Performance-booster for watching directories on Windows
+gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
+
+
+gem "jekyll-get-json", "~> 1.0"
diff --git a/docs/_config.yml b/docs/_config.yml
index fa5156e0ec..506c5aa822 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-remote_theme: bridgecrewio/checkov-theme
+remote_theme: bridgecrewio/checkov-theme-2
site_author: Bridgecrew
repo_url: "https://github.com/bridgecrewio/checkov/"
@@ -6,7 +6,15 @@ repo_url: "https://github.com/bridgecrewio/checkov/"
edit_on_github: true
github_docs_folder: true
sticky_navigation: true
-prev_next_buttons_location: bottom
+prev_next_buttons_location: both
search_enabled: false
hljs_style: github-gist
google_analytics: UA-156134275-1
+plugins:
+ - jekyll-redirect-from
+ - jekyll-remote-theme
+ - jekyll-get-json
+
+jekyll_get_json:
+ - data: menus
+ json: './menus.json'
diff --git a/docs/checkov-recording.gif b/docs/checkov-recording.gif
index b3b5e24fb8..ace04c82b6 100644
Binary files a/docs/checkov-recording.gif and b/docs/checkov-recording.gif differ
diff --git a/docs/documentation.md b/docs/documentation.md
deleted file mode 100644
index 9a556ba3a6..0000000000
--- a/docs/documentation.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-# Checkov Documentation
-
-Checkov is a static code analysis tool for infrastructure-as-code.
-
-
-
-Checkov is written in Python and aims to simplify and increase the adoption of security and compliance best practices that prevent common cloud misconfigurations. Its scans adhere and implement common industry standards such as the Center for Internet Security (CIS) Amazon Web Services (AWS) Foundations Benchmark.
-
-
-See how to [install and get Checkov up and running](1.Introduction/Getting%20Started.html).
-
-Next [learn how to customize and add policies](1.Introduction/Policies.md).
-
-Last, Checkov supports export to JUnitXML format that enables simple integration to CI/CD pipelines. Read more about [here](1.Introduction/Results.md)
-
-
-
diff --git a/docs/index.html b/docs/index.html
deleted file mode 100644
index c8fd834887..0000000000
--- a/docs/index.html
+++ /dev/null
@@ -1,418 +0,0 @@
-
-
-
-
-
-
-
-
-
- Checkov - Open-source infrastructure-as-code static analysis tool by Bridgecrew
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Prevent cloud misconfigurations during build time
-
Checkov is a static code analysis tool for infrastructure-as-code. It scans cloud infrastructure managed in Terraform, Cloudformation, Kubernetes, Arm templates or Serverless Framework and detects misconfigurations.
Install
-
-
-
-
-
-
-
Checkov is written in Python and provides a simple method to write and manage codified, version-controlled policies.
-
-
-
-
- Features
-
-
-
-
Built-in policies cover security and compliance best practices for AWS, Azure & Google Cloud
-
-
-
-
Get your output in CLI, JSON or JUnit XML
-
-
-
-
Handles variables by building a dynamic code dependency graph
-
-
-
-
Supports in-line suppression for accepted risk
-
-
-
-
-
-
-
Built-in policies cover security and compliance best practices for AWS, Azure & Google Cloud
-
-
-
-
-
Get your output in CLI, JSON or JUnit XML
-
-
-
-
-
Handles variables by building a dynamic code dependency graph
-
-
-
-
-
Supports in-line suppression for accepted risk
-
-
-
-
-
-
-
-
-
- Simple and open-source
-
-
-
Install from pypi using pip
-
-
-
-
Select an input folder that contains your Terraform & Cloudformation files and run scans
-
-
-
-
-
Export results to a color-coded cli print
-
-
-
-
-
Integrate scans to your ci/cd pipelines
-
-
-
-
-
-
-
-
Install from pypi using pip
-
-
-
-
Select an input folder that contains your Terraform & Cloudformation files and run scans
-
-
-
-
Export results to a color-coded cli print
-
-
-
-
Integrate scans to your ci/cd pipelines
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000000..0671507841
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,6 @@
+---
+# Feel free to add content and custom Front Matter to this file.
+# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
+
+layout: home
+---
diff --git a/docs/menus.json b/docs/menus.json
new file mode 100644
index 0000000000..5baa58e10c
--- /dev/null
+++ b/docs/menus.json
@@ -0,0 +1,31 @@
+{
+ "utility": [
+ {
+ "label": "About Bridgecrew by Prisma Cloud",
+ "url": "https://bridgecrew.io/?utm_source=checkovio&utm_medium=organic_oss&utm_campaign=checkov",
+ "target": "_self",
+ "submenu": false
+ }
+ ],
+ "main": [
+ {
+ "label": "Overview",
+ "url": "/1.Welcome/What%20is%20Checkov.html",
+ "target": "_self",
+ "submenu": false
+ },
+ {
+ "label": "Docs",
+ "url": "/1.Welcome/Quick%20Start.html",
+ "target": "_self",
+ "submenu": false
+ },
+ {
+ "label": "Download",
+ "url": "/2.Basics/Installing%20Checkov.html",
+ "target": "_self",
+ "submenu": false
+ }
+ ]
+}
+
diff --git a/github_action_resources/checkov-problem-matcher-softfail.json b/github_action_resources/checkov-problem-matcher-softfail.json
new file mode 100644
index 0000000000..8bbe0e8f4a
--- /dev/null
+++ b/github_action_resources/checkov-problem-matcher-softfail.json
@@ -0,0 +1,22 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "checkov",
+ "pattern": [
+ {
+ "regexp": "^Check: (\\w+: .*)$",
+ "message": 1
+ },
+ {
+ "regexp": "^\\WFAILED.*$",
+ },
+ {
+ "regexp": "^\\WFile: \/(.+):(\\d+)-(\\d+)$",
+ "file": 1,
+ "line": 2
+ }
+ ],
+ "severity": "warning"
+ }
+ ]
+}
diff --git a/github_action_resources/checkov-problem-matcher.json b/github_action_resources/checkov-problem-matcher.json
new file mode 100644
index 0000000000..b9b96aebe0
--- /dev/null
+++ b/github_action_resources/checkov-problem-matcher.json
@@ -0,0 +1,21 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "checkov",
+ "pattern": [
+ {
+ "regexp": "^Check: (\\w+: .*)$",
+ "message": 1
+ },
+ {
+ "regexp": "^\\WFAILED.*$",
+ },
+ {
+ "regexp": "^\\WFile: \/(.+):(\\d+)-(\\d+)$",
+ "file": 1,
+ "line": 2
+ }
+ ]
+ }
+ ]
+}
diff --git a/github_action_resources/entrypoint.sh b/github_action_resources/entrypoint.sh
new file mode 100755
index 0000000000..65e3532a79
--- /dev/null
+++ b/github_action_resources/entrypoint.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+
+# Leverage the default env variables as described in:
+# https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
+if [[ $GITHUB_ACTIONS != "true" ]]
+then
+ checkov $@
+ exit $?
+fi
+
+matcher_path=`pwd`/checkov-problem-matcher.json
+warning_matcher_path=`pwd`/checkov-problem-matcher-softfail.json
+cp /usr/local/lib/checkov-problem-matcher.json "$matcher_path"
+cp /usr/local/lib/checkov-problem-matcher-softfail.json "$warning_matcher_path"
+
+export BC_SOURCE=githubActions
+
+# Actions pass inputs as $INPUT_ environment variables
+#
+[[ -n "$INPUT_CHECK" ]] && CHECK_FLAG="--check $INPUT_CHECK"
+[[ -n "$INPUT_SKIP_CHECK" ]] && SKIP_CHECK_FLAG="--skip-check $INPUT_SKIP_CHECK"
+[[ -n "$INPUT_FRAMEWORK" ]] && FRAMEWORK_FLAG="--framework $INPUT_FRAMEWORK"
+[[ -n "$INPUT_OUTPUT_FORMAT" ]] && OUTPUT_FLAG="--output $INPUT_OUTPUT_FORMAT"
+[[ -n "$INPUT_BASELINE" ]] && BASELINE_FLAG="--baseline $INPUT_BASELINE"
+[[ -n "$INPUT_CONFIG_FILE" ]] && CONFIG_FILE_FLAG="--config-file $INPUT_CONFIG_FILE"
+[[ -n "$INPUT_SOFT_FAIL_ON" ]] && SOFT_FAIL_ON_FLAG="--soft-fail-on $INPUT_SOFT_FAIL_ON"
+[[ -n "$INPUT_HARD_FAIL_ON" ]] && HARD_FAIL_ON_FLAG="--hard-fail-on $INPUT_HARD_FAIL_ON"
+
+
+if [ -n "$INPUT_QUIET" ] && [ "$INPUT_QUIET" = "true" ]; then
+ QUIET_FLAG="--quiet"
+fi
+
+if [ -n "$INPUT_DOWNLOAD_EXTERNAL_MODULES" ] && [ "$INPUT_DOWNLOAD_EXTERNAL_MODULES" = "true" ]; then
+ DOWNLOAD_EXTERNAL_MODULES_FLAG="--download-external-modules true"
+fi
+
+if [ -n "$INPUT_SOFT_FAIL" ] && [ "$INPUT_SOFT_FAIL" = "true" ]; then
+ SOFT_FAIL_FLAG="--soft-fail"
+fi
+
+if [ -n "$INPUT_LOG_LEVEL" ]; then
+ export LOG_LEVEL=$INPUT_LOG_LEVEL
+fi
+
+EXTCHECK_DIRS_FLAG=""
+if [ -n "$INPUT_EXTERNAL_CHECKS_DIRS" ]; then
+ IFS=', ' read -r -a extchecks_dir <<< "$INPUT_EXTERNAL_CHECKS_DIRS"
+ for d in "${extchecks_dir[@]}"
+ do
+ EXTCHECK_DIRS_FLAG="$EXTCHECK_DIRS_FLAG --external-checks-dir $d"
+ done
+fi
+
+EXTCHECK_REPOS_FLAG=""
+if [ -n "$INPUT_EXTERNAL_CHECKS_REPOS" ]; then
+ IFS=', ' read -r -a extchecks_git <<< "$INPUT_EXTERNAL_CHECKS_REPOS"
+ for repo in "${extchecks_git[@]}"
+ do
+ EXTCHECK_REPOS_FLAG="$EXTCHECK_REPOS_FLAG --external-checks-git $repo"
+ done
+fi
+
+if [ ! -z "$INPUT_SOFT_FAIL" ]; then
+ echo "::add-matcher::checkov-problem-matcher.json"
+ else
+ echo "::add-matcher::checkov-problem-matcher-softfail.json"
+fi
+
+API_KEY=${API_KEY_VARIABLE}
+
+GIT_BRANCH=${GITHUB_HEAD_REF:=master}
+export BC_FROM_BRANCH=${GIT_BRANCH}
+export BC_TO_BRANCH=${GITHUB_BASE_REF}
+export BC_PR_ID=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
+export BC_PR_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${BC_PR_ID}"
+export BC_COMMIT_HASH=${GITHUB_SHA}
+export BC_COMMIT_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}"
+export BC_AUTHOR_NAME=${GITHUB_ACTOR}
+export BC_AUTHOR_URL="${GITHUB_SERVER_URL}/${BC_AUTHOR_NAME}"
+export BC_RUN_ID=${GITHUB_RUN_NUMBER}
+export BC_RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
+export BC_REPOSITORY_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
+
+echo "BC_FROM_BRANCH=${GIT_BRANCH}"
+echo "BC_TO_BRANCH=${GITHUB_BASE_REF}"
+echo "BC_PR_ID=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')"
+echo "BC_PR_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${BC_PR_ID}""
+echo "BC_COMMIT_HASH=${GITHUB_SHA}"
+echo "BC_COMMIT_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}""
+echo "BC_AUTHOR_NAME=${GITHUB_ACTOR}"
+echo "BC_AUTHOR_URL="${GITHUB_SERVER_URL}/${BC_AUTHOR_NAME}""
+echo "BC_RUN_ID=${GITHUB_RUN_NUMBER}"
+echo "BC_RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}""
+echo "BC_REPOSITORY_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}""
+
+echo "running checkov on directory: $1"
+
+if [ -n "$API_KEY_VARIABLE" ]; then
+ echo "checkov --bc-api-key XXXXXXXXX-XXX-XXXXX --branch $GIT_BRANCH --repo-id $GITHUB_REPOSITORY -d $INPUT_DIRECTORY $CHECK_FLAG $SKIP_CHECK_FLAG $QUIET_FLAG $SOFT_FAIL_FLAG $EXTERNAL_CHECKS_DIR_FLAG $OUTPUT_FLAG $DOWNLOAD_EXTERNAL_MODULES_FLAG $CONFIG_FILE_FLAG $SOFT_FAIL_ON_FLAG $HARD_FAIL_ON_FLAG"
+ checkov --bc-api-key $API_KEY_VARIABLE --branch $GIT_BRANCH --repo-id $GITHUB_REPOSITORY -d $INPUT_DIRECTORY $CHECK_FLAG $SKIP_CHECK_FLAG $QUIET_FLAG $SOFT_FAIL_FLAG $EXTERNAL_CHECKS_DIR_FLAG $OUTPUT_FLAG $DOWNLOAD_EXTERNAL_MODULES_FLAG $CONFIG_FILE_FLAG $SOFT_FAIL_ON_FLAG $HARD_FAIL_ON_FLAG
+ else
+ echo "checkov -d $INPUT_DIRECTORY $CHECK_FLAG $SKIP_CHECK_FLAG $QUIET_FLAG $EXTERNAL_CHECKS_DIR_FLAG $OUTPUT_FLAG $SOFT_FAIL_FLAG $DOWNLOAD_EXTERNAL_MODULES_FLAG $CONFIG_FILE_FLAG $SOFT_FAIL_ON_FLAG $HARD_FAIL_ON_FLAG"
+ checkov -d $INPUT_DIRECTORY $CHECK_FLAG $SKIP_CHECK_FLAG $QUIET_FLAG $EXTERNAL_CHECKS_DIR_FLAG $OUTPUT_FLAG $SOFT_FAIL_FLAG $DOWNLOAD_EXTERNAL_MODULES_FLAG $CONFIG_FILE_FLAG $SOFT_FAIL_ON_FLAG $HARD_FAIL_ON_FLAG
+fi
+
+CHECKOV_EXIT_CODE=$?
+
+if [ -n "$INPUT_DOWNLOAD_EXTERNAL_MODULES" ] && [ "$INPUT_DOWNLOAD_EXTERNAL_MODULES" = "true" ]; then
+ echo "Cleaning up $INPUT_DIRECTORY/.external_modules directory"
+ #This directory must be removed here for the self hosted github runners run as non-root user.
+ rm -fr $INPUT_DIRECTORY/.external_modules
+ exit $CHECKOV_EXIT_CODE
+fi
+exit $CHECKOV_EXIT_CODE
diff --git a/integration_tests/example_config_files/config.yaml b/integration_tests/example_config_files/config.yaml
new file mode 100644
index 0000000000..340a6dec52
--- /dev/null
+++ b/integration_tests/example_config_files/config.yaml
@@ -0,0 +1,5 @@
+---
+# Test comment.
+framework: terraform
+no-guide: true
+output: json
\ No newline at end of file
diff --git a/integration_tests/prepare_data.sh b/integration_tests/prepare_data.sh
new file mode 100755
index 0000000000..e8b24ee503
--- /dev/null
+++ b/integration_tests/prepare_data.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+pipenv run checkov -s --framework terraform -d terragoat/terraform/ -o json > checkov_report_terragoat.json
+pipenv run checkov -s --framework terraform -d terragoat/terraform/ -o junitxml > checkov_report_terragoat.xml
+pipenv run checkov -s -d cfngoat/ -o json --external-checks-dir ./checkov/cloudformation/checks/graph_checks/aws > checkov_report_cfngoat.json
+pipenv run checkov -s -d kubernetes-goat/ --framework kubernetes -o json > checkov_report_kubernetes-goat.json
+pipenv run checkov -s -d kubernetes-goat/ --framework helm -o json > checkov_report_kubernetes-goat-helm.json
+pipenv run checkov -s --framework terraform --skip-check CKV_AWS_33,CKV_AWS_41 -d terragoat/terraform/ -o json > checkov_report_terragoat_with_skip.json
+pipenv run checkov -s -d cfngoat/ -o json --quiet > checkov_report_cfngoat_quiet.json
+pipenv run checkov -s -d terragoat/terraform/ --config-file integration_tests/example_config_files/config.yaml -o json > checkov_config_report_terragoat.json
+if [[ "$1" == "3.7" ]]
+then
+ pipenv run checkov -s -f terragoat/terraform/aws/s3.tf --bc-api-key $BC_KEY > checkov_report_s3_singlefile_api_key_terragoat.txt
+ pipenv run checkov -s -d terragoat/terraform/azure/ --bc-api-key $BC_KEY > checkov_report_azuredir_api_key_terragoat.txt
+fi
diff --git a/integration_tests/test_checkov_cli_integration_report.py b/integration_tests/test_checkov_cli_integration_report.py
new file mode 100644
index 0000000000..12d5936492
--- /dev/null
+++ b/integration_tests/test_checkov_cli_integration_report.py
@@ -0,0 +1,28 @@
+import os
+import sys
+import unittest
+
+current_dir = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestCheckovJsonReport(unittest.TestCase):
+
+ def test_terragoat_report_dir_api_key(self):
+ report_path = current_dir + "/../checkov_report_azuredir_api_key_terragoat.txt"
+ self.validate_report(os.path.abspath(report_path))
+
+ def test_terragoat_report_file_api_key(self):
+ report_path = current_dir + "/../checkov_report_s3_singlefile_api_key_terragoat.txt"
+ self.validate_report(os.path.abspath(report_path))
+
+ def validate_report(self, report_path):
+ if sys.version_info[1] == 7:
+ platform_url_found = False
+ with open(report_path) as f:
+ if 'More details: https://www.bridgecrew.cloud/codeReview/' in f.read():
+ platform_url_found = True
+ self.assertTrue(platform_url_found, "when using api key, platform code review url should exist")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/integration_tests/test_checkov_config.py b/integration_tests/test_checkov_config.py
new file mode 100644
index 0000000000..d05491434a
--- /dev/null
+++ b/integration_tests/test_checkov_config.py
@@ -0,0 +1,29 @@
+import json
+import os
+import unittest
+
+current_dir = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestCheckovConfig(unittest.TestCase):
+
+ def test_terragoat_report(self):
+ # Report to be generated using following command:
+ # checkov -d path/to/terragoat --config-file \
+ # path/to/checkov/integration_tests/example_config_files/config.yaml \
+ # > path/to/checkov/checkov_config_report_terragoat.json
+ report_path = current_dir + "/../checkov_config_report_terragoat.json"
+ with open(report_path) as json_file:
+ data = json.load(json_file)
+ self.assertEqual(data["summary"]["parsing_errors"], 0,
+ f"expecting 0 parsing errors but got: {data['results']['parsing_errors']}")
+ self.assertGreater(data["summary"]["failed"], 1,
+ f"expecting more than 1 failed checks, got: {data['summary']['failed']}")
+ self.assertEqual(data['check_type'], 'terraform',
+ f"expecting 'terraform' but got: {data['check_type']}")
+ self.assertNotIn('guideline', data['results']['failed_checks'][0].keys(),
+ "expecting no guideline for checks.")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/integration_tests/test_checkov_json_report.py b/integration_tests/test_checkov_json_report.py
index ab9485f8e5..55e38e6673 100644
--- a/integration_tests/test_checkov_json_report.py
+++ b/integration_tests/test_checkov_json_report.py
@@ -1,29 +1,70 @@
-import unittest
-
+import itertools
import json
import os
+import unittest
current_dir = os.path.dirname(os.path.realpath(__file__))
+
class TestCheckovJsonReport(unittest.TestCase):
def test_terragoat_report(self):
report_path = current_dir + "/../checkov_report_terragoat.json"
- self.validate_report(report_path)
+ self.validate_report(os.path.abspath(report_path))
def test_cfngoat_report(self):
report_path = current_dir + "/../checkov_report_cfngoat.json"
- self.validate_report(report_path)
+ self.validate_report(os.path.abspath(report_path))
+ self.validate_check_in_report(report_path, "CKV2_AWS_26")
def test_k8goat_report(self):
report_path = current_dir + "/../checkov_report_kubernetes-goat.json"
- self.validate_report(report_path)
+ self.validate_report(os.path.abspath(report_path))
+
+ def test_k8goat_report(self):
+ report_path = current_dir + "/../checkov_report_kubernetes-goat-helm.json"
+ self.validate_report(os.path.abspath(report_path))
+
+ def test_checkov_report_terragoat_with_skip(self):
+ report_path = current_dir + "/../checkov_report_terragoat_with_skip.json"
+ checkov2_graph_findings = 0
+ with open(report_path) as json_file:
+ data = json.load(json_file)
+ for check_result in data["results"]["passed_checks"]:
+ self.assertNotEqual(check_result["check_id"], "CKV_AWS_33")
+ self.assertNotEqual(check_result["check_id"], "CKV_AWS_41")
+ if check_result["check_id"].startswith('CKV2'):
+ checkov2_graph_findings += 1
+ self.assertGreater(checkov2_graph_findings, 5)
def validate_report(self, report_path):
with open(report_path) as json_file:
data = json.load(json_file)
- self.assertEqual(data["summary"]["parsing_errors"], 0, "expecting 0 parsing errors")
- self.assertGreater(data["summary"]["failed"], 1, "expecting more then 1 failed checks")
+ if isinstance(data, list):
+ for framework_report in data:
+ self.validate_report_not_empty(framework_report)
+ else:
+ self.validate_report_not_empty(data)
+
+ def validate_report_not_empty(self, report):
+ self.assertEqual(report["summary"]["parsing_errors"], 0,
+ f"expecting 0 parsing errors but got: {report['results']['parsing_errors']}")
+ self.assertGreater(report["summary"]["failed"], 1,
+ f"expecting more than 1 failed checks, got: {report['summary']['failed']}")
+
+ def validate_json_quiet(self):
+ report_path = current_dir + "/../checkov_report_cfngoat_quiet.json"
+ with open(report_path) as json_file:
+ data = json.load(json_file)
+ self.assertTrue(data["results"]["failed_checks"])
+ self.assertFalse(data["results"]["passed_checks"])
+ self.assertTrue(data["summary"])
+
+ def validate_check_in_report(self, report_path, check_id):
+ with open(report_path) as json_file:
+ data = json.load(json_file)[0]
+ assert any(check["check_id"] == check_id for check in
+ itertools.chain(data["results"]["failed_checks"], data["results"]["passed_checks"]))
if __name__ == '__main__':
diff --git a/integration_tests/test_checkov_junit_report.py b/integration_tests/test_checkov_junit_report.py
new file mode 100644
index 0000000000..0f3577cb0b
--- /dev/null
+++ b/integration_tests/test_checkov_junit_report.py
@@ -0,0 +1,14 @@
+import unittest
+import os
+import xml.etree.ElementTree as ET
+
+current_dir = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestCheckovJunitReport(unittest.TestCase):
+ def test_terragoat_junit_report(self):
+
+ report_path = current_dir + "/../checkov_report_terragoat.xml"
+ tree = ET.parse(report_path)
+ root = tree.getroot()
+ self.assertEqual(root.attrib['errors'], '0')
diff --git a/kubernetes/Dockerfile b/kubernetes/Dockerfile
index b52e227c08..d7eb7e6f60 100644
--- a/kubernetes/Dockerfile
+++ b/kubernetes/Dockerfile
@@ -1,11 +1,11 @@
-FROM python
+FROM python:3.9
COPY kubernetes/requirements.txt /requirements.txt
# Install checkov
RUN pip install -r /requirements.txt
-RUN apt-get update
-RUN apt install -y git
+RUN apt-get update && apt-get install -y --no-install-recommends git && \
+ rm -rf /var/lib/apt/lists/*;
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl \
&& chmod +x kubectl && mv kubectl /usr/local/bin
diff --git a/kubernetes/requirements.txt b/kubernetes/requirements.txt
index 6efce7e9d2..7c1a0b08aa 100644
--- a/kubernetes/requirements.txt
+++ b/kubernetes/requirements.txt
@@ -1 +1 @@
-checkov==1.0.717
+checkov==2.0.363
diff --git a/kubernetes/run_checkov.sh b/kubernetes/run_checkov.sh
index eea5f4f7e1..93f57c6351 100644
--- a/kubernetes/run_checkov.sh
+++ b/kubernetes/run_checkov.sh
@@ -41,8 +41,8 @@ if [ -f /etc/checkov/apikey ]; then
repoid="runtime/unknown"
fi
- checkov -s -d /data --bc-api-key "$apikey" --repo-id "$repoid" --branch runtime --framework kubernetes
+ checkov -s -d /data --bc-api-key "$apikey" --repo-id "$repoid" --branch runtime --framework kubernetes "$@"
else
- checkov -s -d /data --framework kubernetes
+ checkov -s -d /data --framework kubernetes "$@"
fi
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000000..e8f3ce6b19
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,11 @@
+[mypy]
+files = checkov
+
+[mypy-cloudsplaining.*]
+ignore_missing_imports = True
+
+[mypy-dpath.*]
+ignore_missing_imports = True
+
+[mypy-networkx.*]
+ignore_missing_imports = True
diff --git a/policy-definition.png b/policy-definition.png
new file mode 100644
index 0000000000..d732f3c151
Binary files /dev/null and b/policy-definition.png differ
diff --git a/setup.py b/setup.py
index 36b040d192..e9b8f80ad9 100644
--- a/setup.py
+++ b/setup.py
@@ -27,28 +27,37 @@
"pytest==5.3.1",
"coverage",
"coverage-badge",
- "pipenv-setup",
"GitPython==3.1.7",
- "bandit"
+ "bandit",
+ "jsonschema",
]
},
install_requires=[
- "bc-python-hcl2",
+ "bc-python-hcl2>=0.3.18",
+ "cloudsplaining>=0.4.1",
"deep_merge",
"tabulate",
"colorama",
"termcolor",
- "junit-xml",
+ "junit-xml>=1.9",
"dpath>=1.5.0,<2",
- "pyyaml>=5.3.1",
- "boto3",
+ "pyyaml>=5.4.1",
+ "boto3==1.17.*",
"GitPython",
"six==1.15.0",
"jmespath",
"tqdm",
"update_checker",
"semantic_version",
- "packaging"
+ "packaging",
+ "networkx",
+ "dockerfile-parse",
+ "docker",
+ "configargparse",
+ "detect-secrets",
+ "policyuniverse",
+ "typing-extensions",
+ "cfn-lint==0.53.*",
],
license="Apache License 2.0",
name="checkov",
@@ -58,16 +67,29 @@
author="bridgecrew",
author_email="meet@bridgecrew.io",
url="https://github.com/bridgecrewio/checkov",
- packages=setuptools.find_packages(exclude=["tests*","integration_tests*"]),
- scripts=["bin/checkov","bin/checkov.cmd"],
+ packages=setuptools.find_packages(exclude=["tests*", "integration_tests*"]),
+ include_package_data=True,
+ package_dir={
+ "checkov.terraform.checks.graph_checks": "checkov/terraform/checks/graph_checks"
+ },
+ package_data={
+ "checkov.terraform.checks.graph_checks": [
+ "aws/*.yaml",
+ "gcp/*.yaml",
+ "azure/*.yaml",
+ ]
+ },
+ scripts=["bin/checkov", "bin/checkov.cmd"],
long_description=long_description,
long_description_content_type="text/markdown",
classifiers=[
- 'Environment :: Console',
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
- 'Programming Language :: Python :: 3.7',
- 'Topic :: Security',
- 'Topic :: Software Development :: Build Tools'
- ]
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "Intended Audience :: System Administrators",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Topic :: Security",
+ "Topic :: Software Development :: Build Tools",
+ ],
)
diff --git a/tests/arm/checks/parameter/__init__.py b/tests/arm/checks/parameter/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue.py b/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue.py
new file mode 100644
index 0000000000..19a0262638
--- /dev/null
+++ b/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.arm.checks.parameter.SecureStringParameterNoHardcodedValue import check
+from checkov.arm.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestSecureStringParameterNoHardcodedValue(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/test_SecureStringParameterNoHardcodedValue"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue/test_parameters.json b/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue/test_parameters.json
new file mode 100644
index 0000000000..ff16312ee6
--- /dev/null
+++ b/tests/arm/checks/parameter/test_SecureStringParameterNoHardcodedValue/test_parameters.json
@@ -0,0 +1,68 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.1",
+ "parameters": {
+ "passing_no_default": {
+ "type": "secureString"
+ },
+ "passing_blank_default": {
+ "type": "secureString",
+ "defaultValue": ""
+ },
+ "failing": {
+ "type": "secureString",
+ "defaultValue": "xyz"
+ },
+ "not_securestring": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "The location of the Managed Cluster resource."
+ }
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2019-02-01",
+ "type": "Microsoft.ContainerService/managedClusters",
+ "location": "[parameters('location')]",
+ "name": "[parameters('clusterName')]",
+ "properties": {
+ "dnsPrefix": "[parameters('dnsPrefix')]",
+ "agentPoolProfiles": [
+ {
+ "name": "agentpool",
+ "osDiskSizeGB": "[parameters('osDiskSizeGB')]",
+ "count": "[parameters('agentCount')]",
+ "vmSize": "[parameters('agentVMSize')]",
+ "osType": "[parameters('osType')]",
+ "storageProfile": "ManagedDisks"
+ }
+ ],
+ "linuxProfile": {
+ "adminUsername": "[parameters('linuxAdminUsername')]",
+ "ssh": {
+ "publicKeys": [
+ {
+ "keyData": "[parameters('sshRSAPublicKey')]"
+ }
+ ]
+ }
+ },
+ "servicePrincipalProfile": {
+ "clientId": "[parameters('servicePrincipalClientId')]",
+ "Secret": "[parameters('servicePrincipalClientSecret')]"
+ },
+ "apiServerAuthorizedIPRanges": [
+ "73.140.245.0/24"
+ ]
+ }
+ }
+ ],
+ "outputs": {
+ "controlPlaneFQDN": {
+ "type": "string",
+ "value": "[reference(parameters('clusterName')).fqdn]"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/arm/checks/resource/__init__.py b/tests/arm/checks/resource/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-FAILED.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-FAILED.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-FAILED.json
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-PASSED.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-PASSED.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-preview-PASSED.json
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED-2.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED-2.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED-2.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED-2.json
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-FAILED.json
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-PASSED.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-PASSED.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-supported-PASSED.json
diff --git a/tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-unsupported-FAILED.json b/tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-unsupported-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-unsupported-FAILED.json
rename to tests/arm/checks/resource/example_AKSApiServerAuthorizedIpRanges/aks-authIPRanges-unsupported-FAILED.json
diff --git a/tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED.json b/tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED.json
rename to tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED.json
diff --git a/tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED2.json b/tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED2.json
rename to tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED2.json
diff --git a/tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED3.json b/tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED3.json
similarity index 100%
rename from tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED3.json
rename to tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-FAILED3.json
diff --git a/tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-PASSED.json b/tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSDashboardDisabled/AKSDashboardDisabled-PASSED.json
rename to tests/arm/checks/resource/example_AKSDashboardDisabled/AKSDashboardDisabled-PASSED.json
diff --git a/tests/arm/checks/example_AKSLoggingEnabled/AKSLoggingEnabled-FAILED2.json b/tests/arm/checks/resource/example_AKSLoggingEnabled/AKSLoggingEnabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AKSLoggingEnabled/AKSLoggingEnabled-FAILED2.json
rename to tests/arm/checks/resource/example_AKSLoggingEnabled/AKSLoggingEnabled-FAILED2.json
diff --git a/tests/arm/checks/example_AKSLoggingEnabled/aksLoggingEnabled-FAILED.json b/tests/arm/checks/resource/example_AKSLoggingEnabled/aksLoggingEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSLoggingEnabled/aksLoggingEnabled-FAILED.json
rename to tests/arm/checks/resource/example_AKSLoggingEnabled/aksLoggingEnabled-FAILED.json
diff --git a/tests/arm/checks/example_AKSLoggingEnabled/aksLoggingEnabled-PASSED.json b/tests/arm/checks/resource/example_AKSLoggingEnabled/aksLoggingEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSLoggingEnabled/aksLoggingEnabled-PASSED.json
rename to tests/arm/checks/resource/example_AKSLoggingEnabled/aksLoggingEnabled-PASSED.json
diff --git a/tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED.json b/tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED.json
rename to tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED.json
diff --git a/tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED2.json b/tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED2.json
rename to tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED2.json
diff --git a/tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED3.json b/tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED3.json
similarity index 100%
rename from tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED3.json
rename to tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-FAILED3.json
diff --git a/tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-PASSED.json b/tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSNetworkPolicy/aksNetworkPolicy-PASSED.json
rename to tests/arm/checks/resource/example_AKSNetworkPolicy/aksNetworkPolicy-PASSED.json
diff --git a/tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-FAILED.json b/tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-FAILED.json
rename to tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-FAILED.json
diff --git a/tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-FAILED2.json b/tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-FAILED2.json
rename to tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-FAILED2.json
diff --git a/tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-PASSED.json b/tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AKSRbacEnabled/aksEnableRbac-PASSED.json
rename to tests/arm/checks/resource/example_AKSRbacEnabled/aksEnableRbac-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-FAILED.json b/tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-PASSED.json b/tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-PASSED2.json b/tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceAuthentication/appServiceAuthentication-PASSED2.json
rename to tests/arm/checks/resource/example_AppServiceAuthentication/appServiceAuthentication-PASSED2.json
diff --git a/tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED.json b/tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED2.json b/tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED2.json
rename to tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-FAILED2.json
diff --git a/tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED.json b/tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED2.json b/tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED2.json
rename to tests/arm/checks/resource/example_AppServiceClientCertificate/appServiceClientCertificate-PASSED2.json
diff --git a/tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED.json b/tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED2.json b/tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED2.json
rename to tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-FAILED2.json
diff --git a/tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED.json b/tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED2.json b/tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED2.json
rename to tests/arm/checks/resource/example_AppServiceHTTPSOnly/appServiceHTTPSOnly-PASSED2.json
diff --git a/tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED.json b/tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED2.json b/tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED2.json
rename to tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-FAILED2.json
diff --git a/tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-PASSED.json b/tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceHttps20Enabled/appServiceHttps20Enabled-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-FAILED.json b/tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-PASSED.json b/tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-PASSED.json
diff --git a/tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-PASSED2.json b/tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceIdentity/appServiceIdentity-PASSED2.json
rename to tests/arm/checks/resource/example_AppServiceIdentity/appServiceIdentity-PASSED2.json
diff --git a/tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED.json b/tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED.json
rename to tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED.json
diff --git a/tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED2.json b/tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED2.json
rename to tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-FAILED2.json
diff --git a/tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-PASSED.json b/tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-PASSED.json
rename to tests/arm/checks/resource/example_AppServiceMinTLSVersion/appServiceMinTLSVersion-PASSED.json
diff --git a/tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-FAILED.json b/tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-FAILED.json
rename to tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-FAILED.json
diff --git a/tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-PASSED.json b/tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-PASSED.json
rename to tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-PASSED.json
diff --git a/tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-PASSED2.json b/tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_AzureInstancePassword/azureInstancePassword-PASSED2.json
rename to tests/arm/checks/resource/example_AzureInstancePassword/azureInstancePassword-PASSED2.json
diff --git a/tests/arm/checks/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-FAILED.json b/tests/arm/checks/resource/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-FAILED.json
rename to tests/arm/checks/resource/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-FAILED.json
diff --git a/tests/arm/checks/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-PASSED.json b/tests/arm/checks/resource/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-PASSED.json
rename to tests/arm/checks/resource/example_AzureManagedDiscEncryption/azureManagedDiscEncryption-PASSED.json
diff --git a/tests/arm/checks/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-FAILED.json b/tests/arm/checks/resource/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-FAILED.json
rename to tests/arm/checks/resource/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-FAILED.json
diff --git a/tests/arm/checks/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-PASSED.json b/tests/arm/checks/resource/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-PASSED.json
rename to tests/arm/checks/resource/example_CustomRoleDefinitionSubscriptionOwner/example_customRoleDefinitionSubscriptionOwner-PASSED.json
diff --git a/tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED.json b/tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED.json
rename to tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED.json
diff --git a/tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED2.json b/tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED2.json
rename to tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-FAILED2.json
diff --git a/tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-PASSED.json b/tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-PASSED.json
rename to tests/arm/checks/resource/example_KeyvaultRecoveryEnabled/keyvaultRecoveryEnabled-PASSED.json
diff --git a/tests/arm/checks/example_MonitorLogProfileCategories/monitorLogProfileCategories-FAILED.json b/tests/arm/checks/resource/example_MonitorLogProfileCategories/monitorLogProfileCategories-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_MonitorLogProfileCategories/monitorLogProfileCategories-FAILED.json
rename to tests/arm/checks/resource/example_MonitorLogProfileCategories/monitorLogProfileCategories-FAILED.json
diff --git a/tests/arm/checks/example_MonitorLogProfileCategories/monitorLogProfileCategories-PASSED.json b/tests/arm/checks/resource/example_MonitorLogProfileCategories/monitorLogProfileCategories-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_MonitorLogProfileCategories/monitorLogProfileCategories-PASSED.json
rename to tests/arm/checks/resource/example_MonitorLogProfileCategories/monitorLogProfileCategories-PASSED.json
diff --git a/tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED.json b/tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED.json
rename to tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED.json
diff --git a/tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED2.json b/tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED2.json
rename to tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-FAILED2.json
diff --git a/tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-PASSED.json b/tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-PASSED.json
rename to tests/arm/checks/resource/example_MonitorLogProfileRetentionDays/monitorLogProfileRetentionDays-PASSED.json
diff --git a/tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED.json b/tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED.json
rename to tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED.json
diff --git a/tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED2.json b/tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED2.json
rename to tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-FAILED2.json
diff --git a/tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-PASSED.json b/tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-PASSED.json
rename to tests/arm/checks/resource/example_MySQLServerSSLEnforcementEnabled/mysqlSSLEnforcementEnabled-PASSED.json
diff --git a/tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-FAILED.json b/tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
rename to tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
diff --git a/tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json b/tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
rename to tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
diff --git a/tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json b/tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
rename to tests/arm/checks/resource/example_NSGRuleRDPAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
diff --git a/tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-FAILED.json b/tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
rename to tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-FAILED.json
diff --git a/tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json b/tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
rename to tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-1Pass-1Fail.json
diff --git a/tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json b/tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
rename to tests/arm/checks/resource/example_NSGRuleSSHAccessRestricted/NSGRulePortAccessRestricted-RULE-PASSED.json
diff --git a/tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED.json b/tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED.json
rename to tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED.json
diff --git a/tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED2.json b/tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED2.json
rename to tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED2.json
diff --git a/tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED3.json b/tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED3.json
similarity index 100%
rename from tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED3.json
rename to tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-FAILED3.json
diff --git a/tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-PASSED.json b/tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-PASSED.json
rename to tests/arm/checks/resource/example_NetworkWatcherFlowLogPeriod/networkWatcherFlowLogPeriod-PASSED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-FAILED.json b/tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-FAILED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-FAILED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED.json b/tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED2.json b/tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED2.json
rename to tests/arm/checks/resource/example_PostgreSQLServerConnectionThrottlingEnabled/postgreSQL-ConnectionThrottlingEnabled-PASSED2.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-FAILED.json b/tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-FAILED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-FAILED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED.json b/tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED2.json b/tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED2.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogCheckpointsEnabled/postgreSQL-LogCheckpointsEnabled-PASSED2.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-FAILED.json b/tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-FAILED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-FAILED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED.json b/tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED2.json b/tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED2.json
rename to tests/arm/checks/resource/example_PostgreSQLServerLogConnectionsEnabled/postgreSQL-LogConnectionsEnabled-PASSED2.json
diff --git a/tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED.json b/tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED.json
diff --git a/tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED2.json b/tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED2.json
rename to tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-FAILED2.json
diff --git a/tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-PASSED.json b/tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-PASSED.json
rename to tests/arm/checks/resource/example_PostgreSQLServerSSLEnforcementEnabled/postgreSQL-SSL-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-PASSED.json b/tests/arm/checks/resource/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-TDE-FAILED.json b/tests/arm/checks/resource/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-TDE-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-TDE-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerAuditingEnabled/sqlServerAuditingEnabled-TDE-FAILED.json
diff --git a/tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED2.json b/tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED2.json
rename to tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED2.json
diff --git a/tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED3.json b/tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED3.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED3.json
rename to tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-FAILED3.json
diff --git a/tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-PASSED.json b/tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-TDE-FAILED.json b/tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-TDE-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-TDE-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerAuditingRetention90Days/sqlServerAuditingRetention90Days-TDE-FAILED.json
diff --git a/tests/arm/checks/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-FAILED.json b/tests/arm/checks/resource/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-FAILED.json
diff --git a/tests/arm/checks/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-PASSED.json b/tests/arm/checks/resource/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerEmailAlertsEnabled/sqlServerEmailAlertsEnabled-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-FAILED.json b/tests/arm/checks/resource/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-FAILED.json
diff --git a/tests/arm/checks/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-PASSED.json b/tests/arm/checks/resource/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerEmailAlertsToAdminsEnabled/sqlServerEmailAlertsToAdminsEnabled-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-PASSED.json b/tests/arm/checks/resource/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-FAILED.json
similarity index 97%
rename from tests/arm/checks/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-FAILED.json
index e449edd125..7d8aa824f0 100644
--- a/tests/arm/checks/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-PASSED.json
+++ b/tests/arm/checks/resource/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-FAILED.json
@@ -92,8 +92,8 @@
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties": {
- "endIpAddress": "10.255.255.255",
- "startIpAddress": "10.0.0.0"
+ "endIpAddress": "255.255.255.255",
+ "startIpAddress": "0.0.0.0"
},
"dependsOn": [
"[variables('sqlServerName')]"
diff --git a/tests/arm/checks/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-FAILED.json b/tests/arm/checks/resource/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerNoPublicAccess/sqlServerNoPublicAccess-TDE-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-FAILED.json b/tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-FAILED.json
rename to tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-FAILED.json
diff --git a/tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED.json b/tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED.json
rename to tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED.json
diff --git a/tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED2.json b/tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED2.json
rename to tests/arm/checks/resource/example_SQLServerThreatDetectionTypes/sqlServerThreatDetectionTypes-PASSED2.json
diff --git a/tests/arm/checks/example_SecretExpirationDate/SecretExpirationDate-FAILED.json b/tests/arm/checks/resource/example_SecretExpirationDate/SecretExpirationDate-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SecretExpirationDate/SecretExpirationDate-FAILED.json
rename to tests/arm/checks/resource/example_SecretExpirationDate/SecretExpirationDate-FAILED.json
diff --git a/tests/arm/checks/example_SecretExpirationDate/SecretExpirationDate-PASSED.json b/tests/arm/checks/resource/example_SecretExpirationDate/SecretExpirationDate-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SecretExpirationDate/SecretExpirationDate-PASSED.json
rename to tests/arm/checks/resource/example_SecretExpirationDate/SecretExpirationDate-PASSED.json
diff --git a/tests/arm/checks/example_SecurityCenter/securityCenter-FAILED.json b/tests/arm/checks/resource/example_SecurityCenter/securityCenter-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_SecurityCenter/securityCenter-FAILED.json
rename to tests/arm/checks/resource/example_SecurityCenter/securityCenter-FAILED.json
diff --git a/tests/arm/checks/example_SecurityCenter/securityCenter-PASSED.json b/tests/arm/checks/resource/example_SecurityCenter/securityCenter-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_SecurityCenter/securityCenter-PASSED.json
rename to tests/arm/checks/resource/example_SecurityCenter/securityCenter-PASSED.json
diff --git a/tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED.json b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED.json
rename to tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED.json
diff --git a/tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED2.json b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED2.json
rename to tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-FAILED2.json
diff --git a/tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED.json b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED.json
rename to tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED.json
diff --git a/tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED2.json b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED2.json
rename to tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-PASSED2.json
diff --git a/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-VARIABLE.json b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-VARIABLE.json
new file mode 100644
index 0000000000..577309f902
--- /dev/null
+++ b/tests/arm/checks/resource/example_StorageAccountAzureServicesAccessEnabled/storageAccountAzureServicesAccessEnabled-VARIABLE.json
@@ -0,0 +1,292 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "adminUsername": {
+ "type": "string",
+ "metadata": {
+ "description": "VM admin user name"
+ }
+ },
+ "adminPassword": {
+ "type": "securestring",
+ "metadata": {
+ "description": "VM admin password"
+ }
+ },
+ "vnetName": {
+ "type": "string",
+ "defaultValue": "VNet1",
+ "metadata": {
+ "description": "Name of the virtual network"
+ }
+ },
+ "vnetAddressPrefix": {
+ "type": "string",
+ "defaultValue": "10.0.0.0/16",
+ "metadata": {
+ "description": "Address prefix for the virtual network"
+ }
+ },
+ "subnet1Name": {
+ "type": "string",
+ "defaultValue": "subnet1",
+ "metadata": {
+ "description": "Name of the first subnet in the VNet"
+ }
+ },
+ "subnet1Prefix": {
+ "type": "string",
+ "defaultValue": "10.0.1.0/24",
+ "metadata": {
+ "description": "Address prefix for subnet1"
+ }
+ },
+ "subnet2Name": {
+ "type": "string",
+ "defaultValue": "subnet2",
+ "metadata": {
+ "description": "Name of the second subnet in the VNet"
+ }
+ },
+ "subnet2Prefix": {
+ "type": "string",
+ "defaultValue": "10.0.2.0/24",
+ "metadata": {
+ "description": "Address prefix for subnet2"
+ }
+ },
+ "vmSize": {
+ "type": "string",
+ "defaultValue": "Standard_A1",
+ "metadata": {
+ "description": "Size of VM"
+ }
+ },
+ "storageAccountType": {
+ "type": "string",
+ "defaultValue": "Standard_LRS",
+ "metadata": {
+ "description": "Geo-replication type of Storage account"
+ },
+ "allowedValues": [
+ "Standard_LRS",
+ "Standard_GRS",
+ "Standard_ZRS",
+ "Premium_LRS"
+ ]
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Location for all resources."
+ }
+ }
+ },
+ "variables": {
+ "storageAccountName": "[uniqueString(resourceGroup().id)]",
+ "publicIpAddressName": "pip",
+ "vmName": "testvm",
+ "subnetId": [
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnet1Name'))]",
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnet2Name'))]"
+ ],
+ "networkSecurityGroupName": "default-NSG"
+ },
+ "resources": [
+ {
+ "comments": "Default Network Security Group for template",
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "apiVersion": "2019-08-01",
+ "name": "[variables('networkSecurityGroupName')]",
+ "location": "[parameters('location')]",
+ "properties": {
+ "securityRules": [
+ {
+ "name": "default-allow-3389",
+ "properties": {
+ "priority": 1000,
+ "access": "Allow",
+ "direction": "Inbound",
+ "destinationPortRange": "3389",
+ "protocol": "Tcp",
+ "sourceAddressPrefix": "*",
+ "sourcePortRange": "*",
+ "destinationAddressPrefix": "*"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2017-09-01",
+ "type": "Microsoft.Network/virtualNetworks",
+ "name": "[parameters('vnetName')]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
+ ],
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "[parameters('vnetAddressPrefix')]"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "subnet1",
+ "properties": {
+ "addressPrefix": "[parameters('subnet1Prefix')]",
+ "serviceEndpoints": [
+ {
+ "service": "Microsoft.Storage"
+ }
+ ],
+ "networkSecurityGroup": {
+ "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
+ }
+ }
+ },
+ {
+ "name": "subnet2",
+ "properties": {
+ "addressPrefix": "[parameters('subnet2Prefix')]"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2017-09-01",
+ "type": "Microsoft.Network/publicIPAddresses",
+ "name": "[concat(variables('publicIPAddressName'), copyIndex())]",
+ "location": "[parameters('location')]",
+ "copy": {
+ "name": "pipLoop",
+ "count": 2
+ },
+ "properties": {
+ "publicIPAllocationMethod": "Dynamic"
+ }
+ },
+ {
+ "apiVersion": "2016-10-01",
+ "type": "Microsoft.Network/networkInterfaces",
+ "name": "[concat('nic', copyIndex())]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[parameters('vnetName')]",
+ "pipLoop"
+ ],
+ "copy": {
+ "name": "nicLoop",
+ "count": 2
+ },
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfig1",
+ "properties": {
+ "privateIPAllocationMethod": "Dynamic",
+ "subnet": {
+ "id": "[variables('subnetId')[copyIndex()]]"
+ },
+ "publicIPAddress": {
+ "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('publicIPAddressName'), copyIndex()))]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "name": "[variables('storageAccountName')]",
+ "apiVersion": "[variables('some-variable')]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[concat('Microsoft.Network/virtualNetworks/', parameters('vnetName'))]"
+ ],
+ "sku": {
+ "name": "[parameters('storageAccountType')]"
+ },
+ "kind": "Storage",
+ "properties": {
+ "networkAcls": {
+ "bypass": "None",
+ "virtualNetworkRules": [
+ {
+ "id": "[variables('subnetId')[0]]",
+ "action": "Allow"
+ }
+ ],
+ "defaultAction": "Deny"
+ }
+ }
+ },
+ {
+ "apiVersion": "2017-03-30",
+ "type": "Microsoft.Compute/availabilitySets",
+ "name": "as1",
+ "location": "[parameters('location')]",
+ "sku": {
+ "name": "Aligned"
+ },
+ "properties": {
+ "platformFaultDomainCount": 2,
+ "platformUpdateDomainCount": 2
+ }
+ },
+ {
+ "apiVersion": "2017-03-30",
+ "type": "Microsoft.Compute/virtualMachines",
+ "name": "[concat(variables('vmName'), copyIndex())]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
+ "nicLoop",
+ "Microsoft.Compute/availabilitySets/as1"
+ ],
+ "copy": {
+ "name": "vmLoop",
+ "count": 2
+ },
+ "properties": {
+ "availabilitySet": {
+ "id": "[resourceId('Microsoft.Compute/availabilitySets', 'as1')]"
+ },
+ "hardwareProfile": {
+ "vmSize": "[parameters('vmSize')]"
+ },
+ "osProfile": {
+ "computername": "[concat(variables('vmName'), copyIndex())]",
+ "adminUsername": "[parameters('adminUserName')]",
+ "adminPassword": "[parameters('adminPassword')]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "MicrosoftWindowsServer",
+ "offer": "WindowsServer",
+ "sku": "2016-Datacenter",
+ "version": "latest"
+ },
+ "osDisk": {
+ "createOption": "FromImage",
+ "managedDisk": {
+ "storageAccountType": "Standard_LRS"
+ }
+ },
+ "dataDisks": []
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces', concat('nic',copyIndex()))]"
+ }
+ ]
+ }
+ }
+ }
+ ]
+}
diff --git a/tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED.json b/tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED.json
rename to tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED.json
diff --git a/tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED2.json b/tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED2.json
rename to tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-FAILED2.json
diff --git a/tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED.json b/tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED.json
rename to tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED.json
diff --git a/tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED2.json b/tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED2.json
rename to tests/arm/checks/resource/example_StorageAccountDefaultNetworkAccessDeny/storageAccountDefaultNetworkAccessDeny-PASSED2.json
diff --git a/tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed.json b/tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed.json
rename to tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed.json
diff --git a/tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed2.json b/tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed2.json
rename to tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-Failed2.json
diff --git a/tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-PASSED.json b/tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-PASSED.json
rename to tests/arm/checks/resource/example_StorageAccountLoggingQueueServiceEnabled/exampleStorageAccountLoggingQueueServiceEnabled-PASSED.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/notes.txt b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/notes.txt
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/notes.txt
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/notes.txt
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-FAILED.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-FAILED.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-FAILED.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-FAILED2.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-FAILED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-FAILED2.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-FAILED2.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED2.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED2.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED2.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED3.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED3.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-PASSED3.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-PASSED3.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED2.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED2.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED2.json
diff --git a/tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED3.json b/tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED3.json
similarity index 100%
rename from tests/arm/checks/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED3.json
rename to tests/arm/checks/resource/example_StorageAccountsTransportEncryption/storageAccount-SKIPPED3.json
diff --git a/tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-FAILED.json b/tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-FAILED.json
similarity index 100%
rename from tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-FAILED.json
rename to tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-FAILED.json
diff --git a/tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED.json b/tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED.json
similarity index 100%
rename from tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED.json
rename to tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED.json
diff --git a/tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED2.json b/tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED2.json
similarity index 100%
rename from tests/arm/checks/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED2.json
rename to tests/arm/checks/resource/example_StorageBlobServiceContainerPrivateAccess/storageBlobServiceContainerPrivateAccess-PASSED2.json
diff --git a/tests/arm/checks/example_WildcardEntities/main.json b/tests/arm/checks/resource/example_WildcardEntities/main.json
similarity index 100%
rename from tests/arm/checks/example_WildcardEntities/main.json
rename to tests/arm/checks/resource/example_WildcardEntities/main.json
diff --git a/tests/arm/checks/test_AKSApiServerAuthorizedIpRanges.py b/tests/arm/checks/resource/test_AKSApiServerAuthorizedIpRanges.py
similarity index 90%
rename from tests/arm/checks/test_AKSApiServerAuthorizedIpRanges.py
rename to tests/arm/checks/resource/test_AKSApiServerAuthorizedIpRanges.py
index 46992bc353..ae0b1e0eaa 100644
--- a/tests/arm/checks/test_AKSApiServerAuthorizedIpRanges.py
+++ b/tests/arm/checks/resource/test_AKSApiServerAuthorizedIpRanges.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AKSApiServerAuthorizedIpRanges import check
+from checkov.arm.checks.resource.AKSApiServerAuthorizedIpRanges import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AKSDashboardDisabled.py b/tests/arm/checks/resource/test_AKSDashboardDisabled.py
similarity index 91%
rename from tests/arm/checks/test_AKSDashboardDisabled.py
rename to tests/arm/checks/resource/test_AKSDashboardDisabled.py
index 3501de7c4f..9b9eac6eb3 100644
--- a/tests/arm/checks/test_AKSDashboardDisabled.py
+++ b/tests/arm/checks/resource/test_AKSDashboardDisabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AKSDashboardDisabled import check
+from checkov.arm.checks.resource.AKSDashboardDisabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AKSLoggingEnabled.py b/tests/arm/checks/resource/test_AKSLoggingEnabled.py
similarity index 92%
rename from tests/arm/checks/test_AKSLoggingEnabled.py
rename to tests/arm/checks/resource/test_AKSLoggingEnabled.py
index b954ae17ba..18976322ed 100644
--- a/tests/arm/checks/test_AKSLoggingEnabled.py
+++ b/tests/arm/checks/resource/test_AKSLoggingEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AKSLoggingEnabled import check
+from checkov.arm.checks.resource.AKSLoggingEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AKSNetworkPolicy.py b/tests/arm/checks/resource/test_AKSNetworkPolicy.py
similarity index 92%
rename from tests/arm/checks/test_AKSNetworkPolicy.py
rename to tests/arm/checks/resource/test_AKSNetworkPolicy.py
index 5823562820..577e3de64a 100644
--- a/tests/arm/checks/test_AKSNetworkPolicy.py
+++ b/tests/arm/checks/resource/test_AKSNetworkPolicy.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AKSNetworkPolicy import check
+from checkov.arm.checks.resource.AKSNetworkPolicy import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AKSRbacEnabled.py b/tests/arm/checks/resource/test_AKSRbacEnabled.py
similarity index 92%
rename from tests/arm/checks/test_AKSRbacEnabled.py
rename to tests/arm/checks/resource/test_AKSRbacEnabled.py
index 9f9984b24e..0340449827 100644
--- a/tests/arm/checks/test_AKSRbacEnabled.py
+++ b/tests/arm/checks/resource/test_AKSRbacEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AKSRbacEnabled import check
+from checkov.arm.checks.resource.AKSRbacEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceAuthentication.py b/tests/arm/checks/resource/test_AppServiceAuthentication.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceAuthentication.py
rename to tests/arm/checks/resource/test_AppServiceAuthentication.py
index 0ecee7d6d0..2b8f7ed550 100644
--- a/tests/arm/checks/test_AppServiceAuthentication.py
+++ b/tests/arm/checks/resource/test_AppServiceAuthentication.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceAuthentication import check
+from checkov.arm.checks.resource.AppServiceAuthentication import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceClientCertificate.py b/tests/arm/checks/resource/test_AppServiceClientCertificate.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceClientCertificate.py
rename to tests/arm/checks/resource/test_AppServiceClientCertificate.py
index b737c11a15..b91e2fa106 100644
--- a/tests/arm/checks/test_AppServiceClientCertificate.py
+++ b/tests/arm/checks/resource/test_AppServiceClientCertificate.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceClientCertificate import check
+from checkov.arm.checks.resource.AppServiceClientCertificate import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceHTTPSOnly.py b/tests/arm/checks/resource/test_AppServiceHTTPSOnly.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceHTTPSOnly.py
rename to tests/arm/checks/resource/test_AppServiceHTTPSOnly.py
index efd271362c..6674e2d8c1 100644
--- a/tests/arm/checks/test_AppServiceHTTPSOnly.py
+++ b/tests/arm/checks/resource/test_AppServiceHTTPSOnly.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceHTTPSOnly import check
+from checkov.arm.checks.resource.AppServiceHTTPSOnly import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceHttps20Enabled.py b/tests/arm/checks/resource/test_AppServiceHttps20Enabled.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceHttps20Enabled.py
rename to tests/arm/checks/resource/test_AppServiceHttps20Enabled.py
index 5dd5dac09d..e984e0444f 100644
--- a/tests/arm/checks/test_AppServiceHttps20Enabled.py
+++ b/tests/arm/checks/resource/test_AppServiceHttps20Enabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceHttps20Enabled import check
+from checkov.arm.checks.resource.AppServiceHttps20Enabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceIdentity.py b/tests/arm/checks/resource/test_AppServiceIdentity.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceIdentity.py
rename to tests/arm/checks/resource/test_AppServiceIdentity.py
index 1eb3e07482..cb43a3ec34 100644
--- a/tests/arm/checks/test_AppServiceIdentity.py
+++ b/tests/arm/checks/resource/test_AppServiceIdentity.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceIdentity import check
+from checkov.arm.checks.resource.AppServiceIdentity import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AppServiceMinTLSVersion.py b/tests/arm/checks/resource/test_AppServiceMinTLSVersion.py
similarity index 91%
rename from tests/arm/checks/test_AppServiceMinTLSVersion.py
rename to tests/arm/checks/resource/test_AppServiceMinTLSVersion.py
index 5e6955cf02..a43425d918 100644
--- a/tests/arm/checks/test_AppServiceMinTLSVersion.py
+++ b/tests/arm/checks/resource/test_AppServiceMinTLSVersion.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AppServiceMinTLSVersion import check
+from checkov.arm.checks.resource.AppServiceMinTLSVersion import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AzureInstancePassword.py b/tests/arm/checks/resource/test_AzureInstancePassword.py
similarity index 91%
rename from tests/arm/checks/test_AzureInstancePassword.py
rename to tests/arm/checks/resource/test_AzureInstancePassword.py
index 3446b52477..db1d440010 100644
--- a/tests/arm/checks/test_AzureInstancePassword.py
+++ b/tests/arm/checks/resource/test_AzureInstancePassword.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AzureInstancePassword import check
+from checkov.arm.checks.resource.AzureInstancePassword import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_AzureManagedDiscEncryption.py b/tests/arm/checks/resource/test_AzureManagedDiscEncryption.py
similarity index 91%
rename from tests/arm/checks/test_AzureManagedDiscEncryption.py
rename to tests/arm/checks/resource/test_AzureManagedDiscEncryption.py
index 5f0f7051be..87bbaf744d 100644
--- a/tests/arm/checks/test_AzureManagedDiscEncryption.py
+++ b/tests/arm/checks/resource/test_AzureManagedDiscEncryption.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.AzureManagedDiscEncryption import check
+from checkov.arm.checks.resource.AzureManagedDiscEncryption import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_CustomRoleDefinitionSubscriptionOwner.py b/tests/arm/checks/resource/test_CustomRoleDefinitionSubscriptionOwner.py
similarity index 90%
rename from tests/arm/checks/test_CustomRoleDefinitionSubscriptionOwner.py
rename to tests/arm/checks/resource/test_CustomRoleDefinitionSubscriptionOwner.py
index a63c56d815..367546737c 100644
--- a/tests/arm/checks/test_CustomRoleDefinitionSubscriptionOwner.py
+++ b/tests/arm/checks/resource/test_CustomRoleDefinitionSubscriptionOwner.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.CustomRoleDefinitionSubscriptionOwner import check
+from checkov.arm.checks.resource.CustomRoleDefinitionSubscriptionOwner import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_KeyvaultRecoveryEnabled.py b/tests/arm/checks/resource/test_KeyvaultRecoveryEnabled.py
similarity index 91%
rename from tests/arm/checks/test_KeyvaultRecoveryEnabled.py
rename to tests/arm/checks/resource/test_KeyvaultRecoveryEnabled.py
index 5442d334cf..3cc4fe77b4 100644
--- a/tests/arm/checks/test_KeyvaultRecoveryEnabled.py
+++ b/tests/arm/checks/resource/test_KeyvaultRecoveryEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.KeyvaultRecoveryEnabled import check
+from checkov.arm.checks.resource.KeyvaultRecoveryEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_MonitorLogProfileCategories.py b/tests/arm/checks/resource/test_MonitorLogProfileCategories.py
similarity index 91%
rename from tests/arm/checks/test_MonitorLogProfileCategories.py
rename to tests/arm/checks/resource/test_MonitorLogProfileCategories.py
index f89ed4111f..efb7ec102c 100644
--- a/tests/arm/checks/test_MonitorLogProfileCategories.py
+++ b/tests/arm/checks/resource/test_MonitorLogProfileCategories.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.MonitorLogProfileCategories import check
+from checkov.arm.checks.resource.MonitorLogProfileCategories import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_MonitorLogRetentionDays.py b/tests/arm/checks/resource/test_MonitorLogRetentionDays.py
similarity index 90%
rename from tests/arm/checks/test_MonitorLogRetentionDays.py
rename to tests/arm/checks/resource/test_MonitorLogRetentionDays.py
index 08702f6ef5..9440faed47 100644
--- a/tests/arm/checks/test_MonitorLogRetentionDays.py
+++ b/tests/arm/checks/resource/test_MonitorLogRetentionDays.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.MonitorLogProfileRetentionDays import check
+from checkov.arm.checks.resource.MonitorLogProfileRetentionDays import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_MySQLServerSSLEnforcementEnabled.py b/tests/arm/checks/resource/test_MySQLServerSSLEnforcementEnabled.py
similarity index 90%
rename from tests/arm/checks/test_MySQLServerSSLEnforcementEnabled.py
rename to tests/arm/checks/resource/test_MySQLServerSSLEnforcementEnabled.py
index cc97e8cdcd..b069e6d379 100644
--- a/tests/arm/checks/test_MySQLServerSSLEnforcementEnabled.py
+++ b/tests/arm/checks/resource/test_MySQLServerSSLEnforcementEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.MySQLServerSSLEnforcementEnabled import check
+from checkov.arm.checks.resource.MySQLServerSSLEnforcementEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_NSGRuleRDPAccessRestricted.py b/tests/arm/checks/resource/test_NSGRuleRDPAccessRestricted.py
similarity index 91%
rename from tests/arm/checks/test_NSGRuleRDPAccessRestricted.py
rename to tests/arm/checks/resource/test_NSGRuleRDPAccessRestricted.py
index 6df2030daf..cf7982d547 100644
--- a/tests/arm/checks/test_NSGRuleRDPAccessRestricted.py
+++ b/tests/arm/checks/resource/test_NSGRuleRDPAccessRestricted.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.NSGRuleRDPAccessRestricted import check
+from checkov.arm.checks.resource.NSGRuleRDPAccessRestricted import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_NSGRuleSSHAccessRestricted.py b/tests/arm/checks/resource/test_NSGRuleSSHAccessRestricted.py
similarity index 91%
rename from tests/arm/checks/test_NSGRuleSSHAccessRestricted.py
rename to tests/arm/checks/resource/test_NSGRuleSSHAccessRestricted.py
index 99123fdaa9..b60ce5bb75 100644
--- a/tests/arm/checks/test_NSGRuleSSHAccessRestricted.py
+++ b/tests/arm/checks/resource/test_NSGRuleSSHAccessRestricted.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.NSGRuleSSHAccessRestricted import check
+from checkov.arm.checks.resource.NSGRuleSSHAccessRestricted import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_NetworkWatcherFlowLogPeriod.py b/tests/arm/checks/resource/test_NetworkWatcherFlowLogPeriod.py
similarity index 91%
rename from tests/arm/checks/test_NetworkWatcherFlowLogPeriod.py
rename to tests/arm/checks/resource/test_NetworkWatcherFlowLogPeriod.py
index fdd6824310..4eb27f4e72 100644
--- a/tests/arm/checks/test_NetworkWatcherFlowLogPeriod.py
+++ b/tests/arm/checks/resource/test_NetworkWatcherFlowLogPeriod.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.NetworkWatcherFlowLogPeriod import check
+from checkov.arm.checks.resource.NetworkWatcherFlowLogPeriod import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_PostgreSQLServerConnectionThrottlingEnabled.py b/tests/arm/checks/resource/test_PostgreSQLServerConnectionThrottlingEnabled.py
similarity index 89%
rename from tests/arm/checks/test_PostgreSQLServerConnectionThrottlingEnabled.py
rename to tests/arm/checks/resource/test_PostgreSQLServerConnectionThrottlingEnabled.py
index 79c655d0f0..1abeb40f34 100644
--- a/tests/arm/checks/test_PostgreSQLServerConnectionThrottlingEnabled.py
+++ b/tests/arm/checks/resource/test_PostgreSQLServerConnectionThrottlingEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.PostgreSQLServerConnectionThrottlingEnabled import check
+from checkov.arm.checks.resource.PostgreSQLServerConnectionThrottlingEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_PostgreSQLServerLogCheckpointsEnabled.py b/tests/arm/checks/resource/test_PostgreSQLServerLogCheckpointsEnabled.py
similarity index 90%
rename from tests/arm/checks/test_PostgreSQLServerLogCheckpointsEnabled.py
rename to tests/arm/checks/resource/test_PostgreSQLServerLogCheckpointsEnabled.py
index e736c17c3a..7c2cc74af6 100644
--- a/tests/arm/checks/test_PostgreSQLServerLogCheckpointsEnabled.py
+++ b/tests/arm/checks/resource/test_PostgreSQLServerLogCheckpointsEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.PostgreSQLServerLogCheckpointsEnabled import check
+from checkov.arm.checks.resource.PostgreSQLServerLogCheckpointsEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_PostgreSQLServerLogConnectionsEnabled.py b/tests/arm/checks/resource/test_PostgreSQLServerLogConnectionsEnabled.py
similarity index 90%
rename from tests/arm/checks/test_PostgreSQLServerLogConnectionsEnabled.py
rename to tests/arm/checks/resource/test_PostgreSQLServerLogConnectionsEnabled.py
index 07683671d3..34eeb8817e 100644
--- a/tests/arm/checks/test_PostgreSQLServerLogConnectionsEnabled.py
+++ b/tests/arm/checks/resource/test_PostgreSQLServerLogConnectionsEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.PostgreSQLServerLogConnectionsEnabled import check
+from checkov.arm.checks.resource.PostgreSQLServerLogConnectionsEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_PostgreSQLServerSSLEnforcementEnabled.py b/tests/arm/checks/resource/test_PostgreSQLServerSSLEnforcementEnabled.py
similarity index 90%
rename from tests/arm/checks/test_PostgreSQLServerSSLEnforcementEnabled.py
rename to tests/arm/checks/resource/test_PostgreSQLServerSSLEnforcementEnabled.py
index 486208ac34..d032bc7f16 100644
--- a/tests/arm/checks/test_PostgreSQLServerSSLEnforcementEnabled.py
+++ b/tests/arm/checks/resource/test_PostgreSQLServerSSLEnforcementEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.PostgreSQLServerSSLEnforcementEnabled import check
+from checkov.arm.checks.resource.PostgreSQLServerSSLEnforcementEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerAuditingEnabled.py b/tests/arm/checks/resource/test_SQLServerAuditingEnabled.py
similarity index 91%
rename from tests/arm/checks/test_SQLServerAuditingEnabled.py
rename to tests/arm/checks/resource/test_SQLServerAuditingEnabled.py
index d850873793..06791f3d4d 100644
--- a/tests/arm/checks/test_SQLServerAuditingEnabled.py
+++ b/tests/arm/checks/resource/test_SQLServerAuditingEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerAuditingEnabled import check
+from checkov.arm.checks.resource.SQLServerAuditingEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerAuditingRetention90Days.py b/tests/arm/checks/resource/test_SQLServerAuditingRetention90Days.py
similarity index 90%
rename from tests/arm/checks/test_SQLServerAuditingRetention90Days.py
rename to tests/arm/checks/resource/test_SQLServerAuditingRetention90Days.py
index ffcdf1ddbe..28d9abbcee 100644
--- a/tests/arm/checks/test_SQLServerAuditingRetention90Days.py
+++ b/tests/arm/checks/resource/test_SQLServerAuditingRetention90Days.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerAuditingRetention90Days import check
+from checkov.arm.checks.resource.SQLServerAuditingRetention90Days import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerEmailAlertsEnabled.py b/tests/arm/checks/resource/test_SQLServerEmailAlertsEnabled.py
similarity index 91%
rename from tests/arm/checks/test_SQLServerEmailAlertsEnabled.py
rename to tests/arm/checks/resource/test_SQLServerEmailAlertsEnabled.py
index 9a1cd1ef8c..521dcc279f 100644
--- a/tests/arm/checks/test_SQLServerEmailAlertsEnabled.py
+++ b/tests/arm/checks/resource/test_SQLServerEmailAlertsEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerEmailAlertsEnabled import check
+from checkov.arm.checks.resource.SQLServerEmailAlertsEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerEmailAlertsToAdminsEnabled.py b/tests/arm/checks/resource/test_SQLServerEmailAlertsToAdminsEnabled.py
similarity index 90%
rename from tests/arm/checks/test_SQLServerEmailAlertsToAdminsEnabled.py
rename to tests/arm/checks/resource/test_SQLServerEmailAlertsToAdminsEnabled.py
index ea2685a374..797c62c962 100644
--- a/tests/arm/checks/test_SQLServerEmailAlertsToAdminsEnabled.py
+++ b/tests/arm/checks/resource/test_SQLServerEmailAlertsToAdminsEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerEmailAlertsToAdminsEnabled import check
+from checkov.arm.checks.resource.SQLServerEmailAlertsToAdminsEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerNoPublicAccess.py b/tests/arm/checks/resource/test_SQLServerNoPublicAccess.py
similarity index 91%
rename from tests/arm/checks/test_SQLServerNoPublicAccess.py
rename to tests/arm/checks/resource/test_SQLServerNoPublicAccess.py
index 8d4b3062c5..5c07c3163e 100644
--- a/tests/arm/checks/test_SQLServerNoPublicAccess.py
+++ b/tests/arm/checks/resource/test_SQLServerNoPublicAccess.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerNoPublicAccess import check
+from checkov.arm.checks.resource.SQLServerNoPublicAccess import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SQLServerThreatDetectionTypes.py b/tests/arm/checks/resource/test_SQLServerThreatDetectionTypes.py
similarity index 90%
rename from tests/arm/checks/test_SQLServerThreatDetectionTypes.py
rename to tests/arm/checks/resource/test_SQLServerThreatDetectionTypes.py
index 1993b798ee..e7a689527b 100644
--- a/tests/arm/checks/test_SQLServerThreatDetectionTypes.py
+++ b/tests/arm/checks/resource/test_SQLServerThreatDetectionTypes.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SQLServerThreatDetectionTypes import check
+from checkov.arm.checks.resource.SQLServerThreatDetectionTypes import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SecretExpirationDate.py b/tests/arm/checks/resource/test_SecretExpirationDate.py
similarity index 91%
rename from tests/arm/checks/test_SecretExpirationDate.py
rename to tests/arm/checks/resource/test_SecretExpirationDate.py
index 0929215a7e..eaecf30bb5 100644
--- a/tests/arm/checks/test_SecretExpirationDate.py
+++ b/tests/arm/checks/resource/test_SecretExpirationDate.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SecretExpirationDate import check
+from checkov.arm.checks.resource.SecretExpirationDate import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SecurityCenterContactEmailAlert.py b/tests/arm/checks/resource/test_SecurityCenterContactEmailAlert.py
similarity index 90%
rename from tests/arm/checks/test_SecurityCenterContactEmailAlert.py
rename to tests/arm/checks/resource/test_SecurityCenterContactEmailAlert.py
index f8fa983fc2..64fefefca5 100644
--- a/tests/arm/checks/test_SecurityCenterContactEmailAlert.py
+++ b/tests/arm/checks/resource/test_SecurityCenterContactEmailAlert.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SecurityCenterContactEmailAlert import check
+from checkov.arm.checks.resource.SecurityCenterContactEmailAlert import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SecurityCenterContactEmailAlertAdmins.py b/tests/arm/checks/resource/test_SecurityCenterContactEmailAlertAdmins.py
similarity index 90%
rename from tests/arm/checks/test_SecurityCenterContactEmailAlertAdmins.py
rename to tests/arm/checks/resource/test_SecurityCenterContactEmailAlertAdmins.py
index f921f7aa64..4b5309c55e 100644
--- a/tests/arm/checks/test_SecurityCenterContactEmailAlertAdmins.py
+++ b/tests/arm/checks/resource/test_SecurityCenterContactEmailAlertAdmins.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SecurityCenterContactEmailAlertAdmins import check
+from checkov.arm.checks.resource.SecurityCenterContactEmailAlertAdmins import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SecurityCenterContactPhone.py b/tests/arm/checks/resource/test_SecurityCenterContactPhone.py
similarity index 91%
rename from tests/arm/checks/test_SecurityCenterContactPhone.py
rename to tests/arm/checks/resource/test_SecurityCenterContactPhone.py
index 7821929c18..f1a09795bb 100644
--- a/tests/arm/checks/test_SecurityCenterContactPhone.py
+++ b/tests/arm/checks/resource/test_SecurityCenterContactPhone.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SecurityCenterContactPhone import check
+from checkov.arm.checks.resource.SecurityCenterContactPhone import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_SecurityCenterStandardPricing.py b/tests/arm/checks/resource/test_SecurityCenterStandardPricing.py
similarity index 90%
rename from tests/arm/checks/test_SecurityCenterStandardPricing.py
rename to tests/arm/checks/resource/test_SecurityCenterStandardPricing.py
index ddd3520b5d..123e8d0020 100644
--- a/tests/arm/checks/test_SecurityCenterStandardPricing.py
+++ b/tests/arm/checks/resource/test_SecurityCenterStandardPricing.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.SecurityCenterStandardPricing import check
+from checkov.arm.checks.resource.SecurityCenterStandardPricing import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_StorageAccountAzureServicesAccessEnabled.py b/tests/arm/checks/resource/test_StorageAccountAzureServicesAccessEnabled.py
similarity index 90%
rename from tests/arm/checks/test_StorageAccountAzureServicesAccessEnabled.py
rename to tests/arm/checks/resource/test_StorageAccountAzureServicesAccessEnabled.py
index d8563e3acf..91268a3d08 100644
--- a/tests/arm/checks/test_StorageAccountAzureServicesAccessEnabled.py
+++ b/tests/arm/checks/resource/test_StorageAccountAzureServicesAccessEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.StorageAccountAzureServicesAccessEnabled import check
+from checkov.arm.checks.resource.StorageAccountAzureServicesAccessEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_StorageAccountDefaultNetworkAccessDeny.py b/tests/arm/checks/resource/test_StorageAccountDefaultNetworkAccessDeny.py
similarity index 90%
rename from tests/arm/checks/test_StorageAccountDefaultNetworkAccessDeny.py
rename to tests/arm/checks/resource/test_StorageAccountDefaultNetworkAccessDeny.py
index b48a05115b..25cbccccf1 100644
--- a/tests/arm/checks/test_StorageAccountDefaultNetworkAccessDeny.py
+++ b/tests/arm/checks/resource/test_StorageAccountDefaultNetworkAccessDeny.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.StorageAccountDefaultNetworkAccessDeny import check
+from checkov.arm.checks.resource.StorageAccountDefaultNetworkAccessDeny import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_StorageAccountLoggingQueueServiceEnabled.py b/tests/arm/checks/resource/test_StorageAccountLoggingQueueServiceEnabled.py
similarity index 90%
rename from tests/arm/checks/test_StorageAccountLoggingQueueServiceEnabled.py
rename to tests/arm/checks/resource/test_StorageAccountLoggingQueueServiceEnabled.py
index 141c315698..320bf70f7b 100644
--- a/tests/arm/checks/test_StorageAccountLoggingQueueServiceEnabled.py
+++ b/tests/arm/checks/resource/test_StorageAccountLoggingQueueServiceEnabled.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.StorageAccountLoggingQueueServiceEnabled import check
+from checkov.arm.checks.resource.StorageAccountLoggingQueueServiceEnabled import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_StorageAccountsTransportEncryption.py b/tests/arm/checks/resource/test_StorageAccountsTransportEncryption.py
similarity index 90%
rename from tests/arm/checks/test_StorageAccountsTransportEncryption.py
rename to tests/arm/checks/resource/test_StorageAccountsTransportEncryption.py
index 39d07d19d1..b643442cf5 100644
--- a/tests/arm/checks/test_StorageAccountsTransportEncryption.py
+++ b/tests/arm/checks/resource/test_StorageAccountsTransportEncryption.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.StorageAccountsTransportEncryption import check
+from checkov.arm.checks.resource.StorageAccountsTransportEncryption import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_StorageBlobServiceContainerPrivateAccess.py b/tests/arm/checks/resource/test_StorageBlobServiceContainerPrivateAccess.py
similarity index 90%
rename from tests/arm/checks/test_StorageBlobServiceContainerPrivateAccess.py
rename to tests/arm/checks/resource/test_StorageBlobServiceContainerPrivateAccess.py
index 7477685367..9ee65d62a2 100644
--- a/tests/arm/checks/test_StorageBlobServiceContainerPrivateAccess.py
+++ b/tests/arm/checks/resource/test_StorageBlobServiceContainerPrivateAccess.py
@@ -1,7 +1,7 @@
import os
import unittest
-from checkov.arm.checks.StorageBlobServiceContainerPrivateAccess import check
+from checkov.arm.checks.resource.StorageBlobServiceContainerPrivateAccess import check
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/checks/test_wildcard_entities.py b/tests/arm/checks/resource/test_wildcard_entities.py
similarity index 95%
rename from tests/arm/checks/test_wildcard_entities.py
rename to tests/arm/checks/resource/test_wildcard_entities.py
index f7ea752f05..16bf1b355d 100644
--- a/tests/arm/checks/test_wildcard_entities.py
+++ b/tests/arm/checks/resource/test_wildcard_entities.py
@@ -3,7 +3,7 @@
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.arm.base_resource_check import BaseResourceCheck
-from checkov.arm.registry import arm_registry as registry
+from checkov.arm.registry import arm_resource_registry as registry
from checkov.arm.runner import Runner
from checkov.runner_filter import RunnerFilter
diff --git a/tests/arm/runner/test_runner.py b/tests/arm/runner/test_runner.py
index 2ce966786c..32a8323ed6 100644
--- a/tests/arm/runner/test_runner.py
+++ b/tests/arm/runner/test_runner.py
@@ -1,5 +1,8 @@
+import dis
+import inspect
import os
import unittest
+from pathlib import Path
from checkov.runner_filter import RunnerFilter
from checkov.arm.runner import Runner
@@ -24,7 +27,7 @@ def test_record_relative_path_with_relative_dir(self):
runner_filter=RunnerFilter(framework='arm', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -47,7 +50,7 @@ def test_record_relative_path_with_abs_dir(self):
runner_filter=RunnerFilter(framework='arm', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -69,7 +72,7 @@ def test_record_relative_path_with_relative_file(self):
runner_filter=RunnerFilter(framework='arm', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
@@ -91,11 +94,28 @@ def test_record_relative_path_with_abs_file(self):
runner_filter=RunnerFilter(framework='arm', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
+ def test_wrong_check_imports(self):
+ wrong_imports = ["cloudformation", "dockerfile", "helm", "kubernetes", "serverless", "terraform"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
def tearDown(self):
pass
diff --git a/tests/arm/test_scanner_registry.py b/tests/arm/test_scanner_registry.py
index 468f8edfd1..3772c22716 100644
--- a/tests/arm/test_scanner_registry.py
+++ b/tests/arm/test_scanner_registry.py
@@ -1,20 +1,30 @@
import unittest
-from checkov.arm.registry import arm_registry
+from checkov.arm.registry import arm_resource_registry, arm_parameter_registry
class TestScannerRegistry(unittest.TestCase):
def test_num_of_scanners(self):
- scanners_counter = 0
- for key in list(arm_registry.checks.keys()):
- scanners_counter += len(arm_registry.checks[key])
+ resource_scanners_counter = 0
+ for key in list(arm_resource_registry.checks.keys()):
+ resource_scanners_counter += len(arm_resource_registry.checks[key])
- self.assertGreater(scanners_counter, 0)
+ self.assertGreater(resource_scanners_counter, 0)
+
+ parameter_scanners_counter = 0
+ for key in list(arm_parameter_registry.checks.keys()):
+ parameter_scanners_counter += len(arm_parameter_registry.checks[key])
+
+ self.assertGreater(parameter_scanners_counter, 0)
def test_non_colliding_check_ids(self):
check_id_check_class_map = {}
- for (resource_type, checks) in arm_registry.checks.items():
+ for (resource_type, checks) in arm_resource_registry.checks.items():
+ for check in checks:
+ check_id_check_class_map.setdefault(check.id, []).append(check)
+
+ for (resource_type, checks) in arm_parameter_registry.checks.items():
for check in checks:
check_id_check_class_map.setdefault(check.id, []).append(check)
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/FAILED.yml
new file mode 100644
index 0000000000..722acba98a
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/FAILED.yml
@@ -0,0 +1,43 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Group with multiple policies
+Resources:
+ PolicyOnePassPolicyTwoFailAdmin:
+ Type: 'AWS::IAM::Group'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ PolicyOneFailPolicyTwoFailPermissionsWildcard:
+ Type: 'AWS::IAM::Group'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutBucketAcl'
+ Resource: '*'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/PASSED.yaml
new file mode 100644
index 0000000000..15990e7f3b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMGroup/PASSED.yaml
@@ -0,0 +1,45 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Groups with multiple policies
+Resources:
+ NotPermissionsScopedAndWildcard:
+ Type: 'AWS::IAM::Group'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ AdminDenyAndPermissionsScoped:
+ Type: 'AWS::IAM::Group'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource:
+ - 'foo'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/FAILED.yml
new file mode 100644
index 0000000000..323a4822fc
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/FAILED.yml
@@ -0,0 +1,47 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ AdminAllow:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ PermissionsWildcard0:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ PermissionsWildcard1:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutBucketAcl'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/PASSED.yaml
new file mode 100644
index 0000000000..2f7f43783b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMPermissionsManagement/PASSED.yaml
@@ -0,0 +1,59 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ NotPermissionsScoped:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ NotPermissionsWildcard:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ Roles:
+ - example_role
+ AdminDeny:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ PermissionsScoped:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/FAILED.yml
new file mode 100644
index 0000000000..c553953135
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/FAILED.yml
@@ -0,0 +1,61 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Roles with multiple policies
+Resources:
+ PolicyOnePassPolicyTwoFailAdmin:
+ Type: 'AWS::IAM::Role'
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ PolicyOneFailPolicyTwoFailPermissionsWildcard:
+ Type: 'AWS::IAM::Role'
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutBucketAcl'
+ Resource: '*'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/PASSED.yaml
new file mode 100644
index 0000000000..253fd20072
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMRole/PASSED.yaml
@@ -0,0 +1,63 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Roles with multiple policies
+Resources:
+ NotPermissionsScopedAndWildcard:
+ Type: 'AWS::IAM::Role'
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ AdminDenyAndPermissionsScoped:
+ Type: 'AWS::IAM::Role'
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource:
+ - 'foo'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/FAILED.yml
new file mode 100644
index 0000000000..145325ded7
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/FAILED.yml
@@ -0,0 +1,43 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Users with multiple policies
+Resources:
+ PolicyOnePassPolicyTwoFailAdmin:
+ Type: 'AWS::IAM::User'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ PolicyOneFailPolicyTwoFailPermissionsWildcard:
+ Type: 'AWS::IAM::User'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutBucketAcl'
+ Resource: '*'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/PASSED.yaml
new file mode 100644
index 0000000000..d71c312d60
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMUser/PASSED.yaml
@@ -0,0 +1,45 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: IAM Users with multiple policies
+Resources:
+ NotPermissionsScopedAndWildcard:
+ Type: 'AWS::IAM::User'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ AdminDenyAndPermissionsScoped:
+ Type: 'AWS::IAM::User'
+ Properties:
+ Policies:
+ - PolicyName: a
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ - PolicyName: b
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource:
+ - 'foo'
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/FAILED.yml
new file mode 100644
index 0000000000..3b07da3156
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/FAILED.yml
@@ -0,0 +1,47 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ AdminAllow:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ WriteWildcard0:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutObject'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ WriteWildcard1:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:CreateBucket'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/PASSED.yaml
new file mode 100644
index 0000000000..c0aeaabe83
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_IAMWriteAccess/PASSED.yaml
@@ -0,0 +1,59 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ NotWriteScoped:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ NotWriteWildcard:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ Roles:
+ - example_role
+ AdminDeny:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ WriteScoped:
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutObject'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/FAILED.yml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/FAILED.yml
new file mode 100644
index 0000000000..46063acfce
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/FAILED.yml
@@ -0,0 +1,47 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ AdminAllow:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ PermissionsWildcard0:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
+ PermissionsWildcard1:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:PutBucketAcl'
+ Resource: '*'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/PASSED.yaml b/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/PASSED.yaml
new file mode 100644
index 0000000000..a124927c87
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/Cloudsplaining_ManagedPolicy/PASSED.yaml
@@ -0,0 +1,59 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: IAM policy
+Resources:
+ NotPermissionsScoped:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ NotPermissionsWildcard:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 's3:Get*'
+ Resource:
+ - '*'
+ Roles:
+ - example_role
+ AdminDeny:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Deny
+ Action: '*'
+ Resource: '*'
+ Roles:
+ - example_role
+ PermissionsScoped:
+ Type: 'AWS::IAM::ManagedPolicy'
+ Properties:
+ ManagedPolicyName: root
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'iam:ChangePassword'
+ Resource:
+ - 'foo'
+ Roles:
+ - example_role
+ Users:
+ - admin
diff --git a/tests/cloudformation/checks/resource/aws/cloudsplaining.md b/tests/cloudformation/checks/resource/aws/cloudsplaining.md
new file mode 100644
index 0000000000..8090a22754
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/cloudsplaining.md
@@ -0,0 +1,12 @@
+# Cloudsplaining policy testing
+
+We make use of the Cloudsplaining project to perform some IAM-related checks.
+
+This folder is for test data to ensure we are making use of the Cloudsplaining policies as expected.
+
+## Cloudsplaining
+https://github.com/salesforce/cloudsplaining
+
+## Context Links
+* https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_understand-policy-summary-access-level-summaries.html
+* https://github.com/bridgecrewio/checkov/issues/1037
diff --git a/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/FAIL.yaml
new file mode 100644
index 0000000000..3e7d0f30bc
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/FAIL.yaml
@@ -0,0 +1,39 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ FailDefaultType:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ FailExplicitALB:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: application
+ FailExplicitFalse:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: application
+ LoadBalancerAttributes:
+ - Key: routing.http.drop_invalid_header_fields.enabled
+ Value: "false"
+ FailKeyNotExist:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: application
+ LoadBalancerAttributes:
+ - Key: deletion_protection.enabled
+ Value: "true"
diff --git a/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/PASS.yaml
new file mode 100644
index 0000000000..1bce46c962
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/PASS.yaml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ PassDefaultType:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ LoadBalancerAttributes:
+ - Key: routing.http.drop_invalid_header_fields.enabled
+ Value: "true"
+ PassExplicitALB:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: application
+ LoadBalancerAttributes:
+ - Key: routing.http.drop_invalid_header_fields.enabled
+ Value: "true"
+ PassMultipleAttributes:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: application
+ LoadBalancerAttributes:
+ - Key: deletion_protection.enabled
+ Value: "true"
+ - Key: routing.http.drop_invalid_header_fields.enabled
+ Value: "true"
diff --git a/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/UNKNOWN.yaml b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/UNKNOWN.yaml
new file mode 100644
index 0000000000..5bb99b4dea
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ALBDropHttpHeaders/UNKNOWN.yaml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ UnknownNLB:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: network
+ UnknownGatewayLB:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Name: test
+ Subnets:
+ - test-0
+ - test-1
+ Type: gateway
diff --git a/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-FAILED.yaml
index d266690210..acb2ad8b90 100644
--- a/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-FAILED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-FAILED.yaml
@@ -14,3 +14,4 @@ Resources:
ResourceId: MyResourceID
HttpMethod: POST
AuthorizationType: NONE
+ ApiKeyRequired: false
diff --git a/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-PASSED.yaml
index 83c1ffc5c1..3ab313921f 100644
--- a/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_APIGatewayAuthorization/APIGatewayAuthorization-PASSED.yaml
@@ -22,3 +22,11 @@ Resources:
HttpMethod: GET
AuthorizationType: CUSTOM
AuthorizerId: MyAuthorizerID
+ MyMethod3:
+ Type: 'AWS::ApiGateway::Method'
+ Properties:
+ RestApiId: MyAPIID
+ ResourceId: MyResourceID
+ HttpMethod: GET
+ AuthorizationType: NONE
+ ApiKeyRequired: true
diff --git a/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/FAIL.yaml
new file mode 100644
index 0000000000..b866ff2f56
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/FAIL.yaml
@@ -0,0 +1,16 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ CacheDefault:
+ Type: AWS::ApiGateway::Stage
+ Properties:
+ StageName: test
+ Description: test
+ RestApiId: test
+ DeploymentId: test
+ CacheFalse:
+ Type: AWS::ApiGateway::Stage
+ Properties:
+ StageName: test
+ Description: test
+ RestApiId: test
+ CacheClusterEnabled: false
diff --git a/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/PASS.yaml
new file mode 100644
index 0000000000..2bc38b05a0
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_APIGatewayCacheEnable/PASS.yaml
@@ -0,0 +1,10 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ CacheTrue:
+ Type: AWS::ApiGateway::Stage
+ Properties:
+ StageName: test
+ Description: test
+ RestApiId: test
+ DeploymentId: test
+ CacheClusterEnabled: true
diff --git a/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-1.yaml b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-1.yaml
new file mode 100644
index 0000000000..504062b3a7
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-1.yaml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Create a single-instance Amazon MQ for RabbitMQ broker without public accessibility
+
+Parameters:
+ AmazonMqPassword:
+ Type: String
+ NoEcho: true
+ AmazonMqUsername:
+ Type: String
+
+Resources:
+ PublicBroker0:
+ Type: 'AWS::AmazonMQ::Broker'
+ Properties:
+ AutoMinorVersionUpgrade: false
+ BrokerName: MyComplexRabbitBroker-Yes
+ DeploymentMode: SINGLE_INSTANCE
+ EngineType: RABBITMQ
+ EngineVersion: 3.8.6
+ HostInstanceType: mq.t3.micro
+ Logs:
+ General: true
+ MaintenanceWindowStartTime:
+ DayOfWeek: Monday
+ TimeOfDay: '22:45'
+ TimeZone: America/Los_Angeles
+ PubliclyAccessible: Yes
+ SubnetIds:
+ - 'subnet-0a66efd758816811b'
+ Users:
+ - Password: !Ref AmazonMqPassword
+ Username: !Ref AmazonMqUsername
+
diff --git a/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-2.yaml b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-2.yaml
new file mode 100644
index 0000000000..3529f34242
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-FAILED-2.yaml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Create a single-instance Amazon MQ for RabbitMQ broker without public accessibility
+
+Parameters:
+ AmazonMqPassword:
+ Type: String
+ NoEcho: true
+ AmazonMqUsername:
+ Type: String
+
+Resources:
+ PublicBroker1:
+ Type: 'AWS::AmazonMQ::Broker'
+ Properties:
+ AutoMinorVersionUpgrade: false
+ BrokerName: MyComplexRabbitBroker-Yes
+ DeploymentMode: SINGLE_INSTANCE
+ EngineType: RABBITMQ
+ EngineVersion: 3.8.6
+ HostInstanceType: mq.t3.micro
+ Logs:
+ General: true
+ MaintenanceWindowStartTime:
+ DayOfWeek: Monday
+ TimeOfDay: '22:45'
+ TimeZone: America/Los_Angeles
+ PubliclyAccessible: true
+ SubnetIds:
+ - 'subnet-0a66efd758816811b'
+ Users:
+ - Password: !Ref AmazonMqPassword
+ Username: !Ref AmazonMqUsername
+
diff --git a/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-1.yaml b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-1.yaml
new file mode 100644
index 0000000000..7a8d09b073
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-1.yaml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Create a single-instance Amazon MQ for RabbitMQ broker without public accessibility
+
+Parameters:
+ AmazonMqPassword:
+ Type: String
+ NoEcho: true
+ AmazonMqUsername:
+ Type: String
+
+Resources:
+ PrivateBroker0:
+ Type: 'AWS::AmazonMQ::Broker'
+ Properties:
+ AutoMinorVersionUpgrade: false
+ BrokerName: MyComplexRabbitBroker-Yes
+ DeploymentMode: SINGLE_INSTANCE
+ EngineType: RABBITMQ
+ EngineVersion: 3.8.6
+ HostInstanceType: mq.t3.micro
+ Logs:
+ General: true
+ MaintenanceWindowStartTime:
+ DayOfWeek: Monday
+ TimeOfDay: '22:45'
+ TimeZone: America/Los_Angeles
+ PubliclyAccessible: No
+ SubnetIds:
+ - 'subnet-0a66efd758816811b'
+ Users:
+ - Password: !Ref AmazonMqPassword
+ Username: !Ref AmazonMqUsername
+
diff --git a/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-2.yaml b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-2.yaml
new file mode 100644
index 0000000000..83249ac121
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_AmazonMQBrokerPublicAccess/AmazonMQBrokerPublicAccess-PASSED-2.yaml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Create a single-instance Amazon MQ for RabbitMQ broker without public accessibility
+
+Parameters:
+ AmazonMqPassword:
+ Type: String
+ NoEcho: true
+ AmazonMqUsername:
+ Type: String
+
+Resources:
+ PrivateBroker1:
+ Type: 'AWS::AmazonMQ::Broker'
+ Properties:
+ AutoMinorVersionUpgrade: false
+ BrokerName: MyComplexRabbitBroker-Yes
+ DeploymentMode: SINGLE_INSTANCE
+ EngineType: RABBITMQ
+ EngineVersion: 3.8.6
+ HostInstanceType: mq.t3.micro
+ Logs:
+ General: true
+ MaintenanceWindowStartTime:
+ DayOfWeek: Monday
+ TimeOfDay: '22:45'
+ TimeZone: America/Los_Angeles
+ PubliclyAccessible: false
+ SubnetIds:
+ - 'subnet-0a66efd758816811b'
+ Users:
+ - Password: !Ref AmazonMqPassword
+ Username: !Ref AmazonMqUsername
+
diff --git a/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-FAIL.yaml
index 765d0726e2..5622e8deae 100644
--- a/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-FAIL.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-FAIL.yaml
@@ -6,11 +6,10 @@ Resources:
Description: My WorkGroup
State: ENABLED
Tags:
- Tags:
- - Key: "key1"
- Value: "value1"
- - Key: "key2"
- Value: "value2"
+ - Key: "key1"
+ Value: "value1"
+ - Key: "key2"
+ Value: "value2"
WorkGroupConfiguration:
BytesScannedCutoffPerQuery: 200000000
EnforceWorkGroupConfiguration: false
@@ -25,11 +24,10 @@ Resources:
Description: My WorkGroup
State: ENABLED
Tags:
- Tags:
- - Key: "key1"
- Value: "value1"
- - Key: "key2"
- Value: "value2"
+ - Key: "key1"
+ Value: "value1"
+ - Key: "key2"
+ Value: "value2"
WorkGroupConfiguration:
BytesScannedCutoffPerQuery: 200000000
PublishCloudWatchMetricsEnabled: false
diff --git a/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-PASSED.yaml
index 1fcd8cc957..451b2f1349 100644
--- a/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_AthenaWorkgroupConfiguration/AthenaWorkgroupConfiguration-PASSED.yaml
@@ -6,11 +6,10 @@ Resources:
Description: My WorkGroup
State: ENABLED
Tags:
- Tags:
- - Key: "key1"
- Value: "value1"
- - Key: "key2"
- Value: "value2"
+ - Key: "key1"
+ Value: "value1"
+ - Key: "key2"
+ Value: "value2"
WorkGroupConfiguration:
BytesScannedCutoffPerQuery: 200000000
EnforceWorkGroupConfiguration: true
diff --git a/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/FAIL.yaml
new file mode 100644
index 0000000000..c6fa879ca8
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/FAIL.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Fail:
+ Type: AWS::Backup::BackupVault
+ Properties:
+ BackupVaultName: test
diff --git a/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/PASS.yaml
new file mode 100644
index 0000000000..91085ad334
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_BackupVaultEncrypted/PASS.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::Backup::BackupVault
+ Properties:
+ BackupVaultName: test
+ EncryptionKeyArn: arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
diff --git a/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/FAIL.yaml
new file mode 100644
index 0000000000..9c5190fe6f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/FAIL.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Fail:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: test
diff --git a/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/PASS.yaml
new file mode 100644
index 0000000000..d4143490ce
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupKMSKey/PASS.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: test
+ KmsKeyId: arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
diff --git a/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/FAIL.yaml
new file mode 100644
index 0000000000..9c5190fe6f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/FAIL.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Fail:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: test
diff --git a/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/PASS.yaml
new file mode 100644
index 0000000000..0c13c9b922
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_CloudWatchLogGroupRetention/PASS.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: test
+ RetentionInDays: 3
diff --git a/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-FAILED.yml
new file mode 100644
index 0000000000..314e4a9c44
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-FAILED.yml
@@ -0,0 +1,11 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::DMS::ReplicationInstance
+ Properties:
+ ReplicationInstanceClass: dms.c4.large
+ Resource1:
+ Type: AWS::DMS::ReplicationInstance
+ Properties:
+ PubliclyAccessible: True
+ ReplicationInstanceClass: dms.c4.large
diff --git a/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-PASSED.yml
new file mode 100644
index 0000000000..93a3411a48
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DMSReplicationInstancePubliclyAccessible/DMSReplicationInstancePubliclyAccessible-PASSED.yml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::DMS::ReplicationInstance
+ Properties:
+ PubliclyAccessible: False
+ ReplicationInstanceClass: dms.c4.large
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-FAILED.yaml
new file mode 100644
index 0000000000..dab88e3abf
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-FAILED.yaml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ DocDBParameterGroupDefault:
+ Type: AWS::DocDB::DBClusterParameterGroup
+ Properties:
+ Description: docdb cluster parameter group
+ Family: docdb4.0
+ Name: test
+ Parameters:
+ ttl_monitor: "enabled"
+ DocDBParameterGroupDisabled:
+ Type: AWS::DocDB::DBClusterParameterGroup
+ Properties:
+ Description: docdb cluster parameter group
+ Family: docdb4.0
+ Name: test
+ Parameters:
+ audit_logs: "disabled"
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-PASSED.yaml
new file mode 100644
index 0000000000..4c778c6dc5
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBAuditLogs/DocDBAuditLogs-PASSED.yaml
@@ -0,0 +1,10 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ DocDBParameterGroupEnabled:
+ Type: AWS::DocDB::DBClusterParameterGroup
+ Properties:
+ Description: docdb cluster parameter group
+ Family: docdb4.0
+ Name: test
+ Parameters:
+ audit_logs: "enabled"
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-FAILED.yaml
index c887011ca2..7c22ce2b59 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-FAILED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-FAILED.yaml
@@ -1,6 +1,11 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDB:
+ DocDBDefault:
+ Type: AWS::DocDB::DBCluster
+ Properties:
+ MasterUsername: name
+ MasterUserPassword: password
+ DocDBDisabled:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-PASSED.yaml
index 54069ad4bb..2c37dce726 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBEncryption/DocDBEncryption-PASSED.yaml
@@ -1,6 +1,6 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDB:
+ DocDBEnabled:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-FAILED.yaml
index 2f538e6fa5..4bac47fb2f 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-FAILED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-FAILED.yaml
@@ -1,20 +1,20 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDB0:
+ DocDBProfiler:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
MasterUserPassword: password
StorageEncrypted: false
EnableCloudwatchLogsExports: ["profiler"]
- MyDocDB1:
+ DocDBAudit:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
MasterUserPassword: password
StorageEncrypted: false
EnableCloudwatchLogsExports: ["audit"]
- MyDocDB2:
+ DocDBDefault:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-PASSED.yaml
index 8166d922f3..8cae2dae49 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBLogging/DocDBLogging-PASSED.yaml
@@ -1,6 +1,6 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDB:
+ DocDBEnabled:
Type: AWS::DocDB::DBCluster
Properties:
MasterUsername: name
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-FAILED.yaml
index 97e56b89d3..38ee7462ab 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-FAILED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-FAILED.yaml
@@ -1,6 +1,6 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDBParamGroup:
+ DocDBParameterGroupDisabled:
Type: AWS::DocDB::DBClusterParameterGroup
Properties:
Description: docdb cluster parameter group
diff --git a/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-PASSED.yaml
index 08c7ae6793..c1b95ee331 100644
--- a/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_DocDBTLS/DocDBTLS-PASSED.yaml
@@ -1,6 +1,6 @@
AWSTemplateFormatVersion: "2010-09-09"
Resources:
- MyDocDBParamGroup0:
+ DocDBParameterGroupEnabled:
Type: AWS::DocDB::DBClusterParameterGroup
Properties:
Description: docdb cluster parameter group
@@ -9,7 +9,7 @@ Resources:
Parameters:
tls: "enabled"
ttl_monitor: "enabled"
- MyDocDBParamGroup1:
+ DocDBParameterGroupDefault:
Type: AWS::DocDB::DBClusterParameterGroup
Properties:
Description: docdb cluster parameter group
diff --git a/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED-2.yaml b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED-2.yaml
new file mode 100644
index 0000000000..50f587a500
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED-2.yaml
@@ -0,0 +1,37 @@
+AWSTemplateFormatVersion: 2010-09-09
+
+Resources:
+ MyGlobalTableRecoveryDisabled:
+ Type: 'AWS::DynamoDB::GlobalTable'
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: PK
+ AttributeType: S
+ - AttributeName: SK
+ AttributeType: S
+ - AttributeName: GSI1PK
+ AttributeType: S
+ - AttributeName: GSI1SK
+ AttributeType: S
+ BillingMode: PAY_PER_REQUEST
+ GlobalSecondaryIndexes:
+ - IndexName: GSI1
+ KeySchema:
+ - AttributeName: GSI1PK
+ KeyType: HASH
+ - AttributeName: GSI1SK
+ KeyType: RANGE
+ Projection:
+ ProjectionType: ALL
+ KeySchema:
+ - AttributeName: PK
+ KeyType: HASH
+ - AttributeName: SK
+ KeyType: RANGE
+ TableName: MyGT
+ Replicas:
+ - PointInTimeRecoverySpecification:
+ PointInTimeRecoveryEnabled: false
+ Region: eu-west-2
+ StreamSpecification:
+ StreamViewType: NEW_AND_OLD_IMAGES
diff --git a/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED.yaml
new file mode 100644
index 0000000000..5008af572f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-FAILED.yaml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: 2010-09-09
+
+Resources:
+ MyGlobalTableRecoveryDefault:
+ Type: 'AWS::DynamoDB::GlobalTable'
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: PK
+ AttributeType: S
+ - AttributeName: SK
+ AttributeType: S
+ - AttributeName: GSI1PK
+ AttributeType: S
+ - AttributeName: GSI1SK
+ AttributeType: S
+ BillingMode: PAY_PER_REQUEST
+ GlobalSecondaryIndexes:
+ - IndexName: GSI1
+ KeySchema:
+ - AttributeName: GSI1PK
+ KeyType: HASH
+ - AttributeName: GSI1SK
+ KeyType: RANGE
+ Projection:
+ ProjectionType: ALL
+ KeySchema:
+ - AttributeName: PK
+ KeyType: HASH
+ - AttributeName: SK
+ KeyType: RANGE
+ TableName: MyGT
+ Replicas:
+ - Region: eu-west-2
+ StreamSpecification:
+ StreamViewType: NEW_AND_OLD_IMAGES
+
diff --git a/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-PASSED.yaml
new file mode 100644
index 0000000000..6fdcea46f0
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_DynamodbGlobalTableRecovery/DynamodbGlobalTableRecovery-PASSED.yaml
@@ -0,0 +1,38 @@
+AWSTemplateFormatVersion: 2010-09-09
+
+Resources:
+ MyGlobalTableRecoveryEnabled:
+ Type: 'AWS::DynamoDB::GlobalTable'
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: PK
+ AttributeType: S
+ - AttributeName: SK
+ AttributeType: S
+ - AttributeName: GSI1PK
+ AttributeType: S
+ - AttributeName: GSI1SK
+ AttributeType: S
+ BillingMode: PAY_PER_REQUEST
+ GlobalSecondaryIndexes:
+ - IndexName: GSI1
+ KeySchema:
+ - AttributeName: GSI1PK
+ KeyType: HASH
+ - AttributeName: GSI1SK
+ KeyType: RANGE
+ Projection:
+ ProjectionType: ALL
+ KeySchema:
+ - AttributeName: PK
+ KeyType: HASH
+ - AttributeName: SK
+ KeyType: RANGE
+ TableName: MyGT
+ Replicas:
+ - PointInTimeRecoverySpecification:
+ PointInTimeRecoveryEnabled: true
+ Region: eu-west-2
+ StreamSpecification:
+ StreamViewType: NEW_AND_OLD_IMAGES
+
diff --git a/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-FAILED.yaml
new file mode 100644
index 0000000000..5023ae4669
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-FAILED.yaml
@@ -0,0 +1,19 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ UserData:
+ Fn::Base64:
+ !Sub |
+ #! /bin/bash
+ sudo apt-get update
+ sudo apt-get install -y apache2
+ sudo systemctl start apache2
+ sudo systemctl enable apache2
+ export AWS_ACCESS_KEY_ID
+ export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
+ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
+ export AWS_DEFAULT_REGION=us-west-2
+ echo "Deployed via Terraform " | sudo tee /var/www/html/index.html
diff --git a/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-PASSED.yaml
new file mode 100644
index 0000000000..1d9bbfb2eb
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EC2Credentials/EC2Credentials-PASSED.yaml
@@ -0,0 +1,23 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ Resource1:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ UserData:
+ Fn::Base64:
+ !Sub |
+ #! /bin/bash
+ sudo apt-get update
+ sudo apt-get install -y apache2
+ sudo systemctl start apache2
+ sudo systemctl enable apache2
+ export AWS_ACCESS_KEY_ID
+ export AWS_ACCESS_KEY_ID=FOO
+ export AWS_SECRET_ACCESS_KEY=bar
+ export AWS_DEFAULT_REGION=us-west-2
+ echo "Deployed via Terraform " | sudo tee /var/www/html/index.html
diff --git a/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-FAILED.yaml
new file mode 100644
index 0000000000..b3447c3681
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-FAILED.yaml
@@ -0,0 +1,23 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ EC2InstanceResource0:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - AssociatePublicIpAddress: true
+ DeviceIndex: "0"
+ GroupSet:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
+ EC2LaunchTemplateResource0:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateData:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - AssociatePublicIpAddress: true
+ DeviceIndex: 0
+ Groups:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
diff --git a/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-PASSED.yaml
new file mode 100644
index 0000000000..547b5c106d
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-PASSED.yaml
@@ -0,0 +1,32 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ EC2InstanceResource0:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ EC2InstanceResource1:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - AssociatePublicIpAddress: false
+ DeviceIndex: "0"
+ GroupSet:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
+ EC2LaunchTemplateResource0:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateData:
+ ImageId: ami-04169656fea786776
+ EC2LaunchTemplateResource1:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateData:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - AssociatePublicIpAddress: false
+ DeviceIndex: 0
+ Groups:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
diff --git a/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-UNKNOWN.yaml b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-UNKNOWN.yaml
new file mode 100644
index 0000000000..1c15ab2774
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EC2PublicIP/EC2PublicIP-UNKNOWN.yaml
@@ -0,0 +1,21 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ EC2InstanceResource0:
+ Type: AWS::EC2::Instance
+ Properties:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - DeviceIndex: "0"
+ GroupSet:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
+ EC2LaunchTemplateResource0:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateData:
+ ImageId: ami-04169656fea786776
+ NetworkInterfaces:
+ - DeviceIndex: 0
+ Groups:
+ - "myVPCEC2SecurityGroup"
+ SubnetId: "PublicSubnet"
diff --git a/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/FAILED.yml b/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/FAILED.yml
new file mode 100644
index 0000000000..b6ed868afc
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/FAILED.yml
@@ -0,0 +1,13 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ ImageScanFalse:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+ ImageScanningConfiguration:
+ ScanOnPush: false
+ ImageScanNotSet:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+
diff --git a/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/PASSED.yml b/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/PASSED.yml
new file mode 100644
index 0000000000..4c7a0a6785
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECRImageScanning/PASSED.yml
@@ -0,0 +1,8 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ ImageScanTrue:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+ ImageScanningConfiguration:
+ ScanOnPush: true
diff --git a/tests/cloudformation/checks/resource/aws/example_ECRPolicy/ECRPolicy_passed.json b/tests/cloudformation/checks/resource/aws/example_ECRPolicy/ECRPolicy_passed.json
new file mode 100644
index 0000000000..1588c4003d
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECRPolicy/ECRPolicy_passed.json
@@ -0,0 +1,11 @@
+{
+ "Resources": {
+ "vpc16AA8B31E": {
+ "Type": "AWS::ECR::Repository",
+ "Properties": {
+ "RepositoryName": "app1",
+ "RepositoryPolicyText": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"CodeBuildReadAccess\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"codebuild.amazonaws.com\"\n },\n \"Action\": [\n \"ecr:BatchCheckLayerAvailability\",\n \"ecr:BatchGetImage\",\n \"ecr:GetDownloadUrlForLayer\"\n ]\n }\n ]\n}\n"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/FAILED.yml b/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/FAILED.yml
new file mode 100644
index 0000000000..fc1763c043
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/FAILED.yml
@@ -0,0 +1,13 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ AES256Encryption:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+ EncryptionConfiguration:
+ EncryptionType: "AES256"
+ NoEncryption:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+
diff --git a/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/PASSED.yml b/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/PASSED.yml
new file mode 100644
index 0000000000..f9067037ce
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECRRepositoryEncrypted/PASSED.yml
@@ -0,0 +1,9 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ KMSEncryption:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: "test"
+ EncryptionConfiguration:
+ EncryptionType: "KMS"
+ KmsKey: "KeyID"
diff --git a/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-FAILED.yaml
new file mode 100644
index 0000000000..2bc694c31a
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-FAILED.yaml
@@ -0,0 +1,39 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ ContainerDefinitions:
+ - Name: "busybox"
+ Image: "busybox"
+ Cpu: 256
+ EntryPoint:
+ - "sh"
+ - "-c"
+ Memory: 512
+ Command:
+ - "/bin/sh -c \"while true; do /bin/date > /var/www/my-vol/date; sleep 1; done\""
+ Essential: true
+ Volumes:
+ - Name: MyVolume
+ EFSVolumeConfiguration:
+ FilesystemId: FilesystemId
+ TransitEncryption: "DISABLED"
+ Resource1:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ ContainerDefinitions:
+ - Name: "busybox"
+ Image: "busybox"
+ Cpu: 256
+ EntryPoint:
+ - "sh"
+ - "-c"
+ Memory: 512
+ Command:
+ - "/bin/sh -c \"while true; do /bin/date > /var/www/my-vol/date; sleep 1; done\""
+ Essential: true
+ Volumes:
+ - Name: MyVolume
+ EFSVolumeConfiguration:
+ FilesystemId: FilesystemId
diff --git a/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-PASSED.yaml
new file mode 100644
index 0000000000..1dcf6f167c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ECSTaskDefinitionEFSVolumeEncryption/ECSTaskDefinitionEFSVolumeEncryption-PASSED.yaml
@@ -0,0 +1,53 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Resource0:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ ContainerDefinitions:
+ - Name: "busybox"
+ Image: "busybox"
+ Cpu: 256
+ EntryPoint:
+ - "sh"
+ - "-c"
+ Memory: 512
+ Command:
+ - "/bin/sh -c \"while true; do /bin/date > /var/www/my-vol/date; sleep 1; done\""
+ Essential: true
+ Volumes:
+ - Name: MyVolume
+ EFSVolumeConfiguration:
+ FilesystemId: FilesystemId
+ TransitEncryption: "ENABLED"
+ Resource1:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ ContainerDefinitions:
+ - Name: "busybox"
+ Image: "busybox"
+ Cpu: 256
+ EntryPoint:
+ - "sh"
+ - "-c"
+ Memory: 512
+ Command:
+ - "/bin/sh -c \"while true; do /bin/date > /var/www/my-vol/date; sleep 1; done\""
+ Essential: true
+ Resource2:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ ContainerDefinitions:
+ - Name: "busybox"
+ Image: "busybox"
+ Cpu: 256
+ EntryPoint:
+ - "sh"
+ - "-c"
+ Memory: 512
+ Command:
+ - "/bin/sh -c \"while true; do /bin/date > /var/www/my-vol/date; sleep 1; done\""
+ Essential: true
+ Volumes:
+ - Name: MyVolume
+ Host:
+ SourcePath: "/source/path"
diff --git a/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-FAILED.yml
new file mode 100644
index 0000000000..90ec567840
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-FAILED.yml
@@ -0,0 +1,19 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::EKS::Nodegroup'
+ Properties:
+ ClusterName: test
+ NodeRole: 'arn:aws:iam::012345678910:role/eksInstanceRole'
+ ScalingConfig:
+ MinSize: 3
+ DesiredSize: 5
+ MaxSize: 7
+ Labels:
+ Key1: Value1
+ Key2: Value2
+ Subnets:
+ - subnet-6782e71e
+ - subnet-e7e761ac
+ RemoteAccess:
+ Ec2SshKey: SshKeyString
diff --git a/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-PASSED.yml
new file mode 100644
index 0000000000..9315e75265
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EKSNodeGroupRemoteAccess/EKSNodeGroupRemoteAccess-PASSED.yml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::EKS::Nodegroup'
+ Properties:
+ ClusterName: test
+ NodeRole: 'arn:aws:iam::012345678910:role/eksInstanceRole'
+ ScalingConfig:
+ MinSize: 3
+ DesiredSize: 5
+ MaxSize: 7
+ Labels:
+ Key1: Value1
+ Key2: Value2
+ Subnets:
+ - subnet-6782e71e
+ - subnet-e7e761ac
+ RemoteAccess:
+ Ec2SshKey: SshKeyString
+ SourceSecurityGroups:
+ - sg-0
+ Resource1:
+ Type: 'AWS::EKS::Nodegroup'
+ Properties:
+ ClusterName: test
+ NodeRole: 'arn:aws:iam::012345678910:role/eksInstanceRole'
+ ScalingConfig:
+ MinSize: 3
+ DesiredSize: 5
+ MaxSize: 7
+ Labels:
+ Key1: Value1
+ Key2: Value2
+ Subnets:
+ - subnet-6782e71e
+ - subnet-e7e761ac
diff --git a/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-FAILED.yml
new file mode 100644
index 0000000000..acc646a8bc
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-FAILED.yml
@@ -0,0 +1,31 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::EKS::Cluster'
+ Properties:
+ Name: prod
+ Version: '1.14'
+ RoleArn: >-
+ arn:aws:iam::012345678910:role/eks-service-role-AWSServiceRoleForAmazonEKS-EXAMPLEBQ4PI
+ ResourcesVpcConfig:
+ SecurityGroupIds:
+ - sg-6979fe18
+ SubnetIds:
+ - subnet-6782e71e
+ - subnet-e7e761ac
+ Resource1:
+ Type: 'AWS::EKS::Cluster'
+ Properties:
+ Name: prod
+ Version: '1.14'
+ RoleArn: >-
+ arn:aws:iam::012345678910:role/eks-service-role-AWSServiceRoleForAmazonEKS-EXAMPLEBQ4PI
+ ResourcesVpcConfig:
+ SecurityGroupIds:
+ - sg-6979fe18
+ SubnetIds:
+ - subnet-6782e71e
+ - subnet-e7e761ac
+ EncryptionConfig:
+ - Resources:
+ - not_secrets
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-PASSED.yml
new file mode 100644
index 0000000000..cdbb897027
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_EKSSecretEncryption/EKSSecretEncryption-PASSED.yml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ myCluster:
+ Type: 'AWS::EKS::Cluster'
+ Properties:
+ Name: prod
+ Version: '1.14'
+ RoleArn: >-
+ arn:aws:iam::012345678910:role/eks-service-role-AWSServiceRoleForAmazonEKS-EXAMPLEBQ4PI
+ ResourcesVpcConfig:
+ SecurityGroupIds:
+ - sg-6979fe18
+ SubnetIds:
+ - subnet-6782e71e
+ - subnet-e7e761ac
+ EncryptionConfig:
+ - Resources:
+ - secrets
diff --git a/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-FAILED.yml
new file mode 100644
index 0000000000..0a44293251
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-FAILED.yml
@@ -0,0 +1,21 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::ElasticLoadBalancing::LoadBalancer'
+ Properties:
+ Listeners:
+ - InstancePort: '80'
+ InstanceProtocol: HTTP
+ LoadBalancerPort: '80'
+ Protocol: HTTP
+ AccessLoggingPolicy:
+ Enabled: false
+ S3BucketName: MyBucket
+ Resource1:
+ Type: 'AWS::ElasticLoadBalancing::LoadBalancer'
+ Properties:
+ Listeners:
+ - InstancePort: '80'
+ InstanceProtocol: HTTP
+ LoadBalancerPort: '80'
+ Protocol: HTTP
diff --git a/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-PASSED.yml
new file mode 100644
index 0000000000..8dde5347c9
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ELBAccessLogs/ELBAccessLogs-PASSED.yml
@@ -0,0 +1,13 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::ElasticLoadBalancing::LoadBalancer'
+ Properties:
+ Listeners:
+ - InstancePort: '80'
+ InstanceProtocol: HTTP
+ LoadBalancerPort: '80'
+ Protocol: HTTP
+ AccessLoggingPolicy:
+ Enabled: true
+ S3BucketName: MyBucket
diff --git a/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-FAILED.yml
new file mode 100644
index 0000000000..72f65971d3
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-FAILED.yml
@@ -0,0 +1,31 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
+ Properties:
+ Name: MyLB
+ LoadBalancerAttributes:
+ - Key: access_logs.s3.enabled
+ Value: "false"
+ - Key: access_logs.s3.bucket
+ Value: MyBucket
+ Subnets:
+ - SubnetID0
+ - SubnetID1
+ Resource1:
+ Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
+ Properties:
+ Name: MyLB
+ LoadBalancerAttributes:
+ - Key: idle_timeout.timeout_seconds
+ Value: "60"
+ Subnets:
+ - SubnetID0
+ - SubnetID1
+ Resource2:
+ Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
+ Properties:
+ Name: MyLB
+ Subnets:
+ - SubnetID0
+ - SubnetID1
diff --git a/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-PASSED.yml
new file mode 100644
index 0000000000..2f7d673687
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ELBv2AccessLogs/ELBv2AccessLogs-PASSED.yml
@@ -0,0 +1,14 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
+ Properties:
+ Name: MyLB
+ LoadBalancerAttributes:
+ - Key: access_logs.s3.enabled
+ Value: "true"
+ - Key: access_logs.s3.bucket
+ Value: MyBucket
+ Subnets:
+ - SubnetID0
+ - SubnetID1
diff --git a/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-FAILED.yaml
new file mode 100644
index 0000000000..95f741239e
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-FAILED.yaml
@@ -0,0 +1,16 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ EnforceHTTPS: False
+ Resource1:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ TLSSecurityPolicy: "Policy-Min-TLS-1-2-2019-07"
+ Resource2:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ ElasticsearchVersion: "2.3"
diff --git a/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-PASSED.yaml
new file mode 100644
index 0000000000..f7fab7c7ef
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainEnforceHTTPS/ElasticsearchDomainEnforceHTTPS-PASSED.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ EnforceHTTPS: True
diff --git a/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-FAILED.yaml
new file mode 100644
index 0000000000..92e45bf57b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-FAILED.yaml
@@ -0,0 +1,15 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ EnforceHTTPS: True
+ LogPublishingOptions:
+ AUDIT_LOGS:
+ Enabled: False
+ Resource1:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ EnforceHTTPS: True
diff --git a/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-PASSED.yaml
new file mode 100644
index 0000000000..5de171e6ab
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_ElasticsearchDomainLogging/ElasticsearchDomainLogging-PASSED.yaml
@@ -0,0 +1,11 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Elasticsearch::Domain'
+ Properties:
+ DomainEndpointOptions:
+ EnforceHTTPS: True
+ LogPublishingOptions:
+ AUDIT_LOGS:
+ Enabled: True
+ CloudWatchLogsLogGroupArn: CloudWatchLogsLogGroupArn
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-FAILED.yml
new file mode 100644
index 0000000000..9727d10372
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-FAILED.yml
@@ -0,0 +1,29 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Glue::DataCatalogEncryptionSettings'
+ Properties:
+ CatalogId: "CatalogId"
+ DataCatalogEncryptionSettings:
+ ConnectionPasswordEncryption:
+ KmsKeyId: "KmsKeyId"
+ ReturnConnectionPasswordEncrypted: True
+ EncryptionAtRest:
+ CatalogEncryptionMode: "Disabled"
+ SseAwsKmsKeyId: "SseAwsKmsKeyId"
+ Resource1:
+ Type: 'AWS::Glue::DataCatalogEncryptionSettings'
+ Properties:
+ CatalogId: "CatalogId"
+ DataCatalogEncryptionSettings:
+ ConnectionPasswordEncryption:
+ KmsKeyId: "KmsKeyId"
+ ReturnConnectionPasswordEncrypted: False
+ Resource2:
+ Type: 'AWS::Glue::DataCatalogEncryptionSettings'
+ Properties:
+ CatalogId: "CatalogId"
+ DataCatalogEncryptionSettings:
+ EncryptionAtRest:
+ CatalogEncryptionMode: "Disabled"
+ SseAwsKmsKeyId: "SseAwsKmsKeyId"
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-PASSED.yml
new file mode 100644
index 0000000000..5f2c494108
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueDataCatalogEncryption/GlueDataCatalogEncryption-PASSED.yml
@@ -0,0 +1,13 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: 'AWS::Glue::DataCatalogEncryptionSettings'
+ Properties:
+ CatalogId: "CatalogId"
+ DataCatalogEncryptionSettings:
+ ConnectionPasswordEncryption:
+ KmsKeyId: "KmsKeyId"
+ ReturnConnectionPasswordEncrypted: True
+ EncryptionAtRest:
+ CatalogEncryptionMode: "SSE-KMS"
+ SseAwsKmsKeyId: "SseAwsKmsKeyId"
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-FAILED.yml
new file mode 100644
index 0000000000..3af4988290
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-FAILED.yml
@@ -0,0 +1,49 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: AWS::Glue::SecurityConfiguration
+ Properties:
+ Name: Name
+ EncryptionConfiguration:
+ CloudWatchEncryption:
+ CloudWatchEncryptionMode: DISABLED
+ KmsKeyArn: KmsKeyArn
+ JobBookmarksEncryption:
+ JobBookmarksEncryptionMode: CSE-KMS
+ KmsKeyArn: KmsKeyArn
+ S3Encryptions:
+ KmsKeyArn: KmsKeyArn
+ S3EncryptionMode: SSE-KMS
+ Resource1:
+ Type: AWS::Glue::SecurityConfiguration
+ Properties:
+ Name: Name
+ EncryptionConfiguration:
+ CloudWatchEncryption:
+ CloudWatchEncryptionMode: DISABLED
+ KmsKeyArn: KmsKeyArn
+ JobBookmarksEncryption:
+ JobBookmarksEncryptionMode: DISABLED
+ KmsKeyArn: KmsKeyArn
+ S3Encryptions:
+ KmsKeyArn: KmsKeyArn
+ S3EncryptionMode: DISABLED
+ Resource2:
+ Type: AWS::Glue::SecurityConfiguration
+ Properties:
+ Name: Name
+ EncryptionConfiguration:
+ CloudWatchEncryption:
+ CloudWatchEncryptionMode: SSE-KMS
+ KmsKeyArn: KmsKeyArn
+ JobBookmarksEncryption:
+ JobBookmarksEncryptionMode: CSE-KMS
+ KmsKeyArn: KmsKeyArn
+ Resource3:
+ Type: AWS::Glue::SecurityConfiguration
+ Properties:
+ Name: Name
+ EncryptionConfiguration:
+ S3Encryptions:
+ KmsKeyArn: DISABLED
+ S3EncryptionMode: SSE-KMS
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-PASSED.yml
new file mode 100644
index 0000000000..943507c4ac
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfiguration/GlueSecurityConfiguration-PASSED.yml
@@ -0,0 +1,16 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ Resource0:
+ Type: AWS::Glue::SecurityConfiguration
+ Properties:
+ Name: Name
+ EncryptionConfiguration:
+ CloudWatchEncryption:
+ CloudWatchEncryptionMode: SSE-KMS
+ KmsKeyArn: KmsKeyArn
+ JobBookmarksEncryption:
+ JobBookmarksEncryptionMode: CSE-KMS
+ KmsKeyArn: KmsKeyArn
+ S3Encryptions:
+ KmsKeyArn: KmsKeyArn
+ S3EncryptionMode: SSE-KMS
diff --git a/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED-2.yml b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED-2.yml
new file mode 100644
index 0000000000..f31e158b47
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED-2.yml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Example
+Resources:
+ AWSStarPrincipal2:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ |
+ {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "*"
+ }
+ }
+ ]
+ }
+
+ AWSStarPrincipalInList2:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ |
+ {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": ["arn:aws:iam::123456789101:root", "*"]
+ }
+ }
+ ]
+ }
diff --git a/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED.yml b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED.yml
new file mode 100644
index 0000000000..7b8698a60d
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/FAILED.yml
@@ -0,0 +1,28 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Example
+Resources:
+ AWSStarPrincipal:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Allow"
+ Principal:
+ AWS:
+ - "*"
+ Action:
+ - "sts:AssumeRole"
+ AWSStarPrincipalInList:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Allow"
+ Principal:
+ AWS:
+ - "arn:aws:iam::123456789101:root"
+ - "*"
+ Action:
+ - "sts:AssumeRole"
diff --git a/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED-2.yml b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED-2.yml
new file mode 100644
index 0000000000..d0bb047991
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED-2.yml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Example
+Resources:
+ ServiceRole2:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument: |
+ {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "ec2.amazonaws.com"
+ }
+ }
+ ]
+ }
+ DenyIgnore2:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument: |
+ {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {
+ "AWS": "*"
+ }
+ }
+ ]
+ }
diff --git a/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED.yml b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED.yml
new file mode 100644
index 0000000000..be10748edd
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IAMRoleAllowsPublicAssume/PASSED.yml
@@ -0,0 +1,27 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Example
+Resources:
+ ServiceRole:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Allow"
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ DenyIgnore:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Deny"
+ Principal:
+ AWS:
+ - "*"
+ Action:
+ - "sts:AssumeRole"
diff --git a/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/FAILED.yml b/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/FAILED.yml
new file mode 100644
index 0000000000..c1cf011ca5
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/FAILED.yml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ MetadataOptionsNone:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateName: MetadataOptionsNone
+ LaunchTemplateData:
+ DisableApiTermination: true
+ ImageId: ami-04d5cc9b88example
+ InstanceType: t2.micro
+ KeyName: MyKeyPair
+ IMDSv1Enabled:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateName: IMDSv1Enabled
+ LaunchTemplateData:
+ DisableApiTermination: true
+ ImageId: ami-04d5cc9b88example
+ InstanceType: t2.micro
+ KeyName: MyKeyPair
+ MetadataOptions:
+ HttpEndpoint: enabled
+ IMDSv2Optional:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateName: IMDSv2Optional
+ LaunchTemplateData:
+ DisableApiTermination: true
+ ImageId: ami-04d5cc9b88example
+ InstanceType: t2.micro
+ KeyName: MyKeyPair
+ MetadataOptions:
+ HttpTokens: optional
diff --git a/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/PASSED.yml b/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/PASSED.yml
new file mode 100644
index 0000000000..5160acaefe
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_IMDSv1Disabled/PASSED.yml
@@ -0,0 +1,24 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+ IMDSv1Disabled:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateName: IMDSv1Disabled
+ LaunchTemplateData:
+ DisableApiTermination: true
+ ImageId: ami-04d5cc9b88example
+ InstanceType: t2.micro
+ KeyName: MyKeyPair
+ MetadataOptions:
+ HttpEndpoint: disabled
+ IMDSv2Enabled:
+ Type: AWS::EC2::LaunchTemplate
+ Properties:
+ LaunchTemplateName: IMDSv1Disabled
+ LaunchTemplateData:
+ DisableApiTermination: true
+ ImageId: ami-04d5cc9b88example
+ InstanceType: t2.micro
+ KeyName: MyKeyPair
+ MetadataOptions:
+ HttpTokens: required
diff --git a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-NoEncryption.yml b/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/FAILED.yml
similarity index 68%
rename from tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-NoEncryption.yml
rename to tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/FAILED.yml
index 671b9c36bf..1f6cc80074 100644
--- a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-NoEncryption.yml
+++ b/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/FAILED.yml
@@ -1,9 +1,8 @@
AWSTemplateFormatVersion: 2010-09-09
-Description: ElasticsearchDomain resource
Resources:
- MyStream:
+ NoEncryption:
Type: AWS::Kinesis::Stream
Properties:
Name: MyKinesisStream
RetentionPeriodHours: 168
- ShardCount: 3
\ No newline at end of file
+ ShardCount: 3
diff --git a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-Wrong-Key.yml b/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-Wrong-Key.yml
deleted file mode 100644
index 6620dcbed0..0000000000
--- a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-FAILED-Wrong-Key.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-AWSTemplateFormatVersion: 2010-09-09
-Description: ElasticsearchDomain resource
-Resources:
- MyStream:
- Type: AWS::Kinesis::Stream
- Properties:
- Name: MyKinesisStream
- RetentionPeriodHours: 168
- ShardCount: 3
- StreamEncryption:
- EncryptionType: NonKms
- KeyId: myKey
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/PASSED.yml
similarity index 75%
rename from tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-PASSED.yml
rename to tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/PASSED.yml
index 85859418be..a7a4488fd1 100644
--- a/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/KinesisStreamEncryptionType-PASSED.yml
+++ b/tests/cloudformation/checks/resource/aws/example_KinesisStreamEncryptionType/PASSED.yml
@@ -1,7 +1,6 @@
AWSTemplateFormatVersion: 2010-09-09
-Description: ElasticsearchDomain resource
Resources:
- MyStream:
+ KMSEncryption:
Type: AWS::Kinesis::Stream
Properties:
Name: MyKinesisStream
@@ -9,4 +8,4 @@ Resources:
ShardCount: 3
StreamEncryption:
EncryptionType: KMS
- KeyId: myKey
\ No newline at end of file
+ KeyId: myKey
diff --git a/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/LambdaEnvironmentCredentials.yaml b/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/LambdaEnvironmentCredentials.yaml
new file mode 100644
index 0000000000..c9bc7a453e
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/LambdaEnvironmentCredentials.yaml
@@ -0,0 +1,47 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ l1:
+ Type: AWS::Lambda::Function
+ Properties:
+ Runtime: nodejs12.x
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Handler: index.handler
+ Environment:
+ Variables:
+ key1: AKIAAAAAAAAAAAAAAAAA
+ key2: Val2
+ Code:
+ ZipFile: |
+ print('hi')
+ Description: Invoke a function during stack creation.
+ TracingConfig:
+ Mode: Active
+ l2:
+ Type: AWS::Lambda::Function
+ Properties:
+ Runtime: nodejs12.x
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Handler: index.handler
+ Environment:
+ Variables:
+ key1: notasecret
+ key2: Val2
+ Code:
+ ZipFile: |
+ print('hi')
+ Description: Invoke a function during stack creation.
+ TracingConfig:
+ Mode: Active
+ l3:
+ Type: AWS::Lambda::Function
+ Properties:
+ Runtime: nodejs12.x
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Handler: index.handler
+ Code:
+ ZipFile: |
+ print('hi')
+ Description: Invoke a function during stack creation.
+ TracingConfig:
+ Mode: Active
+
diff --git a/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-FAILED.yml b/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-FAILED.yml
new file mode 100644
index 0000000000..18de743c90
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-FAILED.yml
@@ -0,0 +1,11 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ NeptuneDBClusterDefault:
+ Type: "AWS::Neptune::DBCluster"
+ Properties:
+ DBClusterIdentifier: DBClusterIdentifier
+ NeptuneDBClusterEmpty:
+ Type: "AWS::Neptune::DBCluster"
+ Properties:
+ DBClusterIdentifier: DBClusterIdentifier
+ EnableCloudwatchLogsExports: []
diff --git a/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-PASSED.yml b/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-PASSED.yml
new file mode 100644
index 0000000000..8e35392f0c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_NeptuneClusterLogging/NeptuneClusterLogging-PASSED.yml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ NeptuneDBClusterEnabled:
+ Type: "AWS::Neptune::DBCluster"
+ Properties:
+ DBClusterIdentifier: DBClusterIdentifier
+ EnableCloudwatchLogsExports: ["audit"]
diff --git a/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/FAIL.yaml
new file mode 100644
index 0000000000..264d781473
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/FAIL.yaml
@@ -0,0 +1,8 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Disabled:
+ Type: "AWS::QLDB::Ledger"
+ Properties:
+ DeletionProtection: false
+ Name: "ledger"
+ PermissionsMode: "STANDARD"
diff --git a/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/PASS.yaml
new file mode 100644
index 0000000000..b3e683f6d3
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerDeletionProtection/PASS.yaml
@@ -0,0 +1,13 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Default:
+ Type: "AWS::QLDB::Ledger"
+ Properties:
+ Name: "ledger"
+ PermissionsMode: "STANDARD"
+ Enabled:
+ Type: "AWS::QLDB::Ledger"
+ Properties:
+ DeletionProtection: true
+ Name: "ledger"
+ PermissionsMode: "STANDARD"
diff --git a/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/FAIL.yaml
new file mode 100644
index 0000000000..2c44d0884f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/FAIL.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ AllowAll:
+ Type: "AWS::QLDB::Ledger"
+ Properties:
+ Name: "ledger"
+ PermissionsMode: "ALLOW_ALL"
diff --git a/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/PASS.yaml
new file mode 100644
index 0000000000..1a34190496
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_QLDBLedgerPermissionsMode/PASS.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Standard:
+ Type: "AWS::QLDB::Ledger"
+ Properties:
+ Name: "ledger"
+ PermissionsMode: "STANDARD"
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-FAIL.yaml
new file mode 100644
index 0000000000..8d237722cb
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-FAIL.yaml
@@ -0,0 +1,14 @@
+Resources:
+ Default:
+ Type: 'AWS::RDS::DBCluster'
+ Properties:
+ Engine: 'aurora'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ Disabled:
+ Type: 'AWS::RDS::DBCluster'
+ Properties:
+ Engine: 'aurora'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: false
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-PASSED.yaml
new file mode 100644
index 0000000000..3cbe29a538
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSClusterIAMAuthentication/RDSClusterIAMAuthentication-PASSED.yaml
@@ -0,0 +1,8 @@
+Resources:
+ Enabled:
+ Type: 'AWS::RDS::DBCluster'
+ Properties:
+ Engine: 'aurora'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: true
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-FAIL.yaml
new file mode 100644
index 0000000000..13706ab71c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-FAIL.yaml
@@ -0,0 +1,31 @@
+Resources:
+ DefaultMysql:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ DefaultPostgres:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'postgres'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ DisabledMysql:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: false
+ DisabledPostgres:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'postgres'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: false
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-PASSED.yaml
new file mode 100644
index 0000000000..a38ce4b69e
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-PASSED.yaml
@@ -0,0 +1,17 @@
+Resources:
+ EnabledMysql:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: true
+ EnabledPostgres:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'postgres'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
+ EnableIAMDatabaseAuthentication: true
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-UNKNOWN.yaml b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-UNKNOWN.yaml
new file mode 100644
index 0000000000..33993057f7
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSIAMAuthentication/RDSIAMAuthentication-UNKNOWN.yaml
@@ -0,0 +1,8 @@
+Resources:
+ Mariadb:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mariadb'
+ MasterUsername: 'username'
+ MasterUserPassword: 'password'
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED-2.yaml b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED-2.yaml
new file mode 100644
index 0000000000..d3dd6d0d08
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED-2.yaml
@@ -0,0 +1,9 @@
+Resources:
+ MyDBDefault:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBName: 'mydb-default'
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'master'
+ MasterUserPassword: 'password'
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED.yaml
new file mode 100644
index 0000000000..196632db8c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-FAILED.yaml
@@ -0,0 +1,10 @@
+Resources:
+ MyDBDisabled:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBName: 'mydb-disabled'
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'master'
+ MasterUserPassword: 'password'
+ MultiAZ: false
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-PASSED.yaml
new file mode 100644
index 0000000000..ee3cd74b62
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RDSMultiAZEnabled/RDSMultiAZEnabled-PASSED.yaml
@@ -0,0 +1,10 @@
+Resources:
+ MyDBEnabled:
+ Type: 'AWS::RDS::DBInstance'
+ Properties:
+ DBName: 'mydb-enabled'
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'master'
+ MasterUserPassword: 'password'
+ MultiAZ: true
diff --git a/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-FAILED.yaml
new file mode 100644
index 0000000000..7a2f989d38
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-FAILED.yaml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ RedshiftParameterGroupDefault:
+ Type: AWS::Redshift::ClusterParameterGroup
+ Properties:
+ Description: parameter group
+ ParameterGroupFamily: redshift-1.0
+ Parameters:
+ - ParameterName: "enable_user_activity_logging"
+ ParameterValue: "true"
+ RedshiftParameterGroupDisabled:
+ Type: AWS::Redshift::ClusterParameterGroup
+ Properties:
+ Description: parameter group
+ ParameterGroupFamily: redshift-1.0
+ Parameters:
+ - ParameterName: "require_ssl"
+ ParameterValue: "false"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-PASSED.yaml
new file mode 100644
index 0000000000..76c1bf5050
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedShiftSSL/RedShiftSSL-PASSED.yaml
@@ -0,0 +1,10 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ RedshiftParameterGroupEnabled:
+ Type: AWS::Redshift::ClusterParameterGroup
+ Properties:
+ Description: parameter group
+ ParameterGroupFamily: redshift-1.0
+ Parameters:
+ - ParameterName: "require_ssl"
+ ParameterValue: "true"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED.yaml
index bb7d3f5c58..0c95d8e8d6 100644
--- a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED.yaml
@@ -1,5 +1,5 @@
Resources:
- myCluster:
+ RedshiftClusterDisabled:
Type: "AWS::Redshift::Cluster"
Properties:
DBName: "mydb"
@@ -8,3 +8,11 @@ Resources:
NodeType: "ds2.xlarge"
ClusterType: "single-node"
Encrypted: false
+ RedshiftClusterDefault:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-PASSED.yaml
index 3dca949988..95053f3430 100644
--- a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-PASSED.yaml
@@ -1,5 +1,5 @@
Resources:
- myCluster:
+ RedshiftClusterEnabled:
Type: "AWS::Redshift::Cluster"
Properties:
DBName: "mydb"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED-2.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-FAILED.yaml
similarity index 89%
rename from tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED-2.yaml
rename to tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-FAILED.yaml
index c054aa80ff..08562fa00d 100644
--- a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterEncryption/RedshiftClusterEncryption-FAILED-2.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-FAILED.yaml
@@ -1,5 +1,5 @@
Resources:
- myCluster:
+ RedshiftClusterDefault:
Type: "AWS::Redshift::Cluster"
Properties:
DBName: "mydb"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-PASSED.yaml
new file mode 100644
index 0000000000..5ac75aac86
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterLogging/RedshiftClusterLogging-PASSED.yaml
@@ -0,0 +1,11 @@
+Resources:
+ RedshiftClusterEnabled:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
+ LoggingProperties:
+ BucketName: "bucket"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-FAILED.yaml
new file mode 100644
index 0000000000..3c723a5f46
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-FAILED.yaml
@@ -0,0 +1,10 @@
+Resources:
+ RedshiftClusterDisabled:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
+ PubliclyAccessible: true
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-PASSED.yaml
new file mode 100644
index 0000000000..a7aa7b8927
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftClusterPubliclyAccessible/RedshiftClusterPubliclyAccessible-PASSED.yaml
@@ -0,0 +1,18 @@
+Resources:
+ RedshiftClusterDefault:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
+ RedshiftClusterEnabled:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
+ PubliclyAccessible: false
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-FAILED.yaml
new file mode 100644
index 0000000000..08562fa00d
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-FAILED.yaml
@@ -0,0 +1,9 @@
+Resources:
+ RedshiftClusterDefault:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
diff --git a/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-PASSED.yaml
new file mode 100644
index 0000000000..ee5925f500
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_RedshiftInEc2ClassicMode/RedshiftInEc2ClassicMode-PASSED.yaml
@@ -0,0 +1,10 @@
+Resources:
+ RedshiftClusterEnabled:
+ Type: "AWS::Redshift::Cluster"
+ Properties:
+ DBName: "mydb"
+ MasterUsername: "master"
+ MasterUserPassword: "MasterUserPassword"
+ NodeType: "ds2.xlarge"
+ ClusterType: "single-node"
+ ClusterSubnetGroupName: "subnet-ebd9cead"
diff --git a/tests/cloudformation/checks/resource/aws/example_SecurityGroupUnrestrictedIngress22/SecurityGroupUnrestrictedIngress22-FAILED-3.yaml b/tests/cloudformation/checks/resource/aws/example_SecurityGroupUnrestrictedIngress22/SecurityGroupUnrestrictedIngress22-FAILED-3.yaml
new file mode 100644
index 0000000000..333d3c6f7f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_SecurityGroupUnrestrictedIngress22/SecurityGroupUnrestrictedIngress22-FAILED-3.yaml
@@ -0,0 +1,18 @@
+Description: Security Group Example
+Parameters:
+ SSHLocation:
+ Description: The IP address range that can be used to SSH to the EC2 instances
+ Type: String
+ Default: '0000:0000:0000:0000:0000:0000:0000:0000/0'
+Resources:
+ InstanceSecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: Enable SSH access via port 22
+ SecurityGroupIngress:
+ - Description: SSH Ingress
+ IpProtocol: tcp
+ FromPort: 22
+ ToPort: 22
+ CidrIpv6: !Ref 'SSHLocation'
+
diff --git a/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-FAILED.yaml
new file mode 100644
index 0000000000..31c4338b70
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-FAILED.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ TimestreamDatabaseDefault:
+ Type: AWS::Timestream::Database
+ Properties:
+ DatabaseName: timestream
diff --git a/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-PASSED.yaml
new file mode 100644
index 0000000000..3c25b511ce
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_TimestreamDatabaseKMSKey/TimestreamDatabaseKMSKey-PASSED.yaml
@@ -0,0 +1,7 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ TimestreamDatabaseEnabled:
+ Type: AWS::Timestream::Database
+ Properties:
+ DatabaseName: timestream
+ KmsKeyId: kms-key-id
diff --git a/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/FAIL.yaml
new file mode 100644
index 0000000000..149dfc9b77
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/FAIL.yaml
@@ -0,0 +1,8 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ PUBLIC:
+ Type: AWS::Transfer::Server
+ Properties:
+ EndpointType: "PUBLIC"
+ NONE:
+ Type: AWS::Transfer::Server
diff --git a/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/PASS.yaml
new file mode 100644
index 0000000000..49c3d1dd7b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_TransferServerIsPublic/PASS.yaml
@@ -0,0 +1,10 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ VPC:
+ Type: AWS::Transfer::Server
+ Properties:
+ EndpointType: "VPC"
+ VPCENDPOINT:
+ Type: AWS::Transfer::Server
+ Properties:
+ EndpointType: "VPC_ENDPOINT"
diff --git a/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/FAIL.yaml
new file mode 100644
index 0000000000..0d0e964494
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/FAIL.yaml
@@ -0,0 +1,8 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ FailDefault:
+ Type: AWS::EC2::VPCEndpointService
+ FailExplicit:
+ Type: AWS::EC2::VPCEndpointService
+ Properties:
+ AcceptanceRequired: false
diff --git a/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/PASS.yaml
new file mode 100644
index 0000000000..270ea83859
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_VPCEndpointAcceptanceConfigured/PASS.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::EC2::VPCEndpointService
+ Properties:
+ AcceptanceRequired: true
diff --git a/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/FAIL.yaml
new file mode 100644
index 0000000000..4ff123335b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/FAIL.yaml
@@ -0,0 +1,15 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ FailDefault:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ FailExplicit:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ RootVolumeEncryptionEnabled: false
diff --git a/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/PASS.yaml
new file mode 100644
index 0000000000..9f3504afe1
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_WorkspaceRootVolumeEncrypted/PASS.yaml
@@ -0,0 +1,9 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ RootVolumeEncryptionEnabled: true
diff --git a/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/FAIL.yaml
new file mode 100644
index 0000000000..c83e3d5a1c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/FAIL.yaml
@@ -0,0 +1,15 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ FailDefault:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ FailExplicit:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ UserVolumeEncryptionEnabled: false
diff --git a/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/PASS.yaml
new file mode 100644
index 0000000000..7ed5923082
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_WorkspaceUserVolumeEncrypted/PASS.yaml
@@ -0,0 +1,9 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ Pass:
+ Type: AWS::WorkSpaces::Workspace
+ Properties:
+ UserName: test
+ BundleId: wsb-abc123456
+ DirectoryId: d-abc123456
+ UserVolumeEncryptionEnabled: true
diff --git a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.json b/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.json
deleted file mode 100644
index 2878d1687c..0000000000
--- a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "AWSTemplateFormatVersion": "2010-09-09",
- "Description": "acme AWS CloudTrail and Config Security Audit Integration",
- "Resources": {
- "logGroup": {
- "Type" : "AWS::Logs::LogGroup",
- "Properties" : {
- "LogGroupName" : "String"
- }
- }
- }
- }
-
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.yaml b/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.yaml
deleted file mode 100644
index d889450673..0000000000
--- a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-FAILED.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
- AWSTemplateFormatVersion: "2010-09-09"
- Parameters:
- OperatorEmail:
- Description: "Email address to notify when new logs are published."
- Type: String
- Resources:
- logGroup:
- Type: AWS::Logs::LogGroup
- Properties:
- LogGroupName: String
diff --git a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.json b/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.json
deleted file mode 100644
index 14708ebb7c..0000000000
--- a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "AWSTemplateFormatVersion": "2010-09-09",
- "Description": "acme AWS CloudTrail and Config Security Audit Integration",
- "Resources": {
- "logGroup": {
- "Type" : "AWS::Logs::LogGroup",
- "Properties" : {
- "LogGroupName" : "String",
- "RetentionInDays" : 3
- }
- }
- }
- }
-
\ No newline at end of file
diff --git a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.yaml b/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.yaml
deleted file mode 100644
index 4e03f8d4a6..0000000000
--- a/tests/cloudformation/checks/resource/aws/example_cloudwatchLogGroupRetention/example_cloudwatchLogGroupRetention-SUCCESS.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
- AWSTemplateFormatVersion: "2010-09-09"
- Parameters:
- OperatorEmail:
- Description: "Email address to notify when new logs are published."
- Type: String
- Resources:
- logGroup:
- Type: AWS::Logs::LogGroup
- Properties:
- LogGroupName: String
- RetentionInDays: 3
diff --git a/tests/cloudformation/checks/resource/aws/test_ALBDropHttpHeaders.py b/tests/cloudformation/checks/resource/aws/test_ALBDropHttpHeaders.py
new file mode 100644
index 0000000000..48622d2c7d
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ALBDropHttpHeaders.py
@@ -0,0 +1,51 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ALBDropHttpHeaders import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestALBDropHttpHeaders(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ test_files_dir = current_dir + "/example_ALBDropHttpHeaders"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.PassDefaultType",
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.PassExplicitALB",
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.PassMultipleAttributes",
+ }
+
+ failing_resources = {
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.FailDefaultType",
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.FailExplicitALB",
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.FailExplicitFalse",
+ "AWS::ElasticLoadBalancingV2::LoadBalancer.FailKeyNotExist",
+ }
+
+ # 2 Unknown resources are tested which are properly silently ignored
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 3)
+ self.assertEqual(summary['failed'], 4)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_APIGatewayAuthorization.py b/tests/cloudformation/checks/resource/aws/test_APIGatewayAuthorization.py
index 5e80871a30..221fb77a58 100644
--- a/tests/cloudformation/checks/resource/aws/test_APIGatewayAuthorization.py
+++ b/tests/cloudformation/checks/resource/aws/test_APIGatewayAuthorization.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 3)
+ self.assertEqual(summary['passed'], 4)
self.assertEqual(summary['failed'], 2)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/cloudformation/checks/resource/aws/test_APIGatewayCacheEnable.py b/tests/cloudformation/checks/resource/aws/test_APIGatewayCacheEnable.py
new file mode 100644
index 0000000000..b6378d84ce
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_APIGatewayCacheEnable.py
@@ -0,0 +1,45 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.APIGatewayCacheEnable import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestAPIGatewayCacheEnable(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ test_files_dir = current_dir + "/example_APIGatewayCacheEnable"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::ApiGateway::Stage.CacheTrue",
+ }
+
+ failing_resources = {
+ "AWS::ApiGateway::Stage.CacheDefault",
+ "AWS::ApiGateway::Stage.CacheFalse",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_AmazonMQBrokerPublicAccess.py b/tests/cloudformation/checks/resource/aws/test_AmazonMQBrokerPublicAccess.py
new file mode 100644
index 0000000000..37710ba335
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_AmazonMQBrokerPublicAccess.py
@@ -0,0 +1,41 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.AmazonMQBrokerPublicAccess import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestAmazonMQBrokerPublicAccess(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_AmazonMQBrokerPublicAccess"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::AmazonMQ::Broker.PrivateBroker0",
+ "AWS::AmazonMQ::Broker.PrivateBroker1",
+ }
+ failing_resources = {
+ "AWS::AmazonMQ::Broker.PublicBroker0",
+ "AWS::AmazonMQ::Broker.PublicBroker1",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_BackupVaultEncrypted.py b/tests/cloudformation/checks/resource/aws/test_BackupVaultEncrypted.py
new file mode 100644
index 0000000000..e5967db132
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_BackupVaultEncrypted.py
@@ -0,0 +1,45 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.BackupVaultEncrypted import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestBackupVaultEncrypted(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ test_files_dir = current_dir + "/example_BackupVaultEncrypted"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::Backup::BackupVault.Pass",
+ }
+
+ failing_resources = {
+ "AWS::Backup::BackupVault.Fail",
+
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupKMSKey.py b/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupKMSKey.py
new file mode 100644
index 0000000000..0efa79b91c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupKMSKey.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.CloudWatchLogGroupKMSKey import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestCloudWatchLogGroupKMSKey(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_CloudWatchLogGroupKMSKey"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::Logs::LogGroup.Pass",
+ }
+
+ failing_resources = {
+ "AWS::Logs::LogGroup.Fail",
+
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupRetention.py b/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupRetention.py
new file mode 100644
index 0000000000..ee54c2fdda
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudWatchLogGroupRetention.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.CloudWatchLogGroupRetention import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestCloudWatchLogGroupRetention(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_CloudWatchLogGroupRetention"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::Logs::LogGroup.Pass",
+ }
+
+ failing_resources = {
+ "AWS::Logs::LogGroup.Fail",
+
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMGroup.py b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMGroup.py
new file mode 100644
index 0000000000..67071bf62f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMGroup.py
@@ -0,0 +1,29 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMPermissionsManagement import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+# This test is the same as for IAMPermissionsManagement but uses IAM Group test data
+# with multiple Policies to ensure that this resource type is tested but it would be
+# overkill to use all possible resources for each policy related check
+
+class TestCloudsplainingIAMGroup(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_IAMGroup"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMRole.py b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMRole.py
new file mode 100644
index 0000000000..7ca4d4db0b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMRole.py
@@ -0,0 +1,29 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMPermissionsManagement import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+# This test is the same as for IAMPermissionsManagement but uses IAM Role test data
+# with multiple Policies to ensure that this resource type is tested but it would be
+# overkill to use all possible resources for each policy related check
+
+class TestCloudsplainingIAMRole(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_IAMRole"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMUser.py b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMUser.py
new file mode 100644
index 0000000000..3dd208e961
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudsplainingIAMUser.py
@@ -0,0 +1,29 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMPermissionsManagement import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+# This test is the same as for IAMPermissionsManagement but uses IAM User test data
+# with multiple Policies to ensure that this resource type is tested but it would be
+# overkill to use all possible resources for each policy related check
+
+class TestCloudsplainingIAMUser(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_IAMUser"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudsplainingManagedPolicy.py b/tests/cloudformation/checks/resource/aws/test_CloudsplainingManagedPolicy.py
new file mode 100644
index 0000000000..17a71d0f59
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_CloudsplainingManagedPolicy.py
@@ -0,0 +1,29 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMPermissionsManagement import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+# This test is the same as for IAMPermissionsManagement but uses 'ManagedPolicy' test data
+# instead of 'Policy' data. This is to ensure some ManagedPolicy tests are excercised
+# because it would be overkill to duplicate every test with both.
+
+class TestCloudsplainingManagedPolicy(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_ManagedPolicy"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DMSReplicationInstancePubliclyAccessible.py b/tests/cloudformation/checks/resource/aws/test_DMSReplicationInstancePubliclyAccessible.py
new file mode 100644
index 0000000000..625ae5cfca
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_DMSReplicationInstancePubliclyAccessible.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.DMSReplicationInstancePubliclyAccessible import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestDMSReplicationInstancePubliclyAccessible(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_DMSReplicationInstancePubliclyAccessible"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DocDBAuditLogs.py b/tests/cloudformation/checks/resource/aws/test_DocDBAuditLogs.py
new file mode 100644
index 0000000000..9281c93d16
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_DocDBAuditLogs.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.DocDBAuditLogs import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestDocDBAuditLogs(unittest.TestCase):
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_DocDBAuditLogs"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupEnabled",
+ }
+ failing_resources = {
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupDefault",
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DocDBEncryption.py b/tests/cloudformation/checks/resource/aws/test_DocDBEncryption.py
index 98e0c81e66..ab575dc98d 100644
--- a/tests/cloudformation/checks/resource/aws/test_DocDBEncryption.py
+++ b/tests/cloudformation/checks/resource/aws/test_DocDBEncryption.py
@@ -7,20 +7,33 @@
class TestDocDBEncryption(unittest.TestCase):
-
def test_summary(self):
runner = Runner()
current_dir = os.path.dirname(os.path.realpath(__file__))
test_files_dir = current_dir + "/example_DocDBEncryption"
- report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 1)
- self.assertEqual(summary['skipped'], 0)
- self.assertEqual(summary['parsing_errors'], 0)
+ passing_resources = {
+ "AWS::DocDB::DBCluster.DocDBEnabled",
+ }
+ failing_resources = {
+ "AWS::DocDB::DBCluster.DocDBDefault",
+ "AWS::DocDB::DBCluster.DocDBDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DocDBLogging.py b/tests/cloudformation/checks/resource/aws/test_DocDBLogging.py
index 647c705aea..414540807b 100644
--- a/tests/cloudformation/checks/resource/aws/test_DocDBLogging.py
+++ b/tests/cloudformation/checks/resource/aws/test_DocDBLogging.py
@@ -7,20 +7,34 @@
class TestDocDBLogging(unittest.TestCase):
-
def test_summary(self):
runner = Runner()
current_dir = os.path.dirname(os.path.realpath(__file__))
test_files_dir = current_dir + "/example_DocDBLogging"
- report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 3)
- self.assertEqual(summary['skipped'], 0)
- self.assertEqual(summary['parsing_errors'], 0)
+ passing_resources = {
+ "AWS::DocDB::DBCluster.DocDBEnabled",
+ }
+ failing_resources = {
+ "AWS::DocDB::DBCluster.DocDBDefault",
+ "AWS::DocDB::DBCluster.DocDBAudit",
+ "AWS::DocDB::DBCluster.DocDBProfiler",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 3)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DocDBTLS.py b/tests/cloudformation/checks/resource/aws/test_DocDBTLS.py
index 037d0ff3b5..dfab0a7e5a 100644
--- a/tests/cloudformation/checks/resource/aws/test_DocDBTLS.py
+++ b/tests/cloudformation/checks/resource/aws/test_DocDBTLS.py
@@ -7,20 +7,33 @@
class TestDocDBTLS(unittest.TestCase):
-
def test_summary(self):
runner = Runner()
current_dir = os.path.dirname(os.path.realpath(__file__))
test_files_dir = current_dir + "/example_DocDBTLS"
- report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 2)
- self.assertEqual(summary['failed'], 1)
- self.assertEqual(summary['skipped'], 0)
- self.assertEqual(summary['parsing_errors'], 0)
+ passing_resources = {
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupEnabled",
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupDefault",
+ }
+ failing_resources = {
+ "AWS::DocDB::DBClusterParameterGroup.DocDBParameterGroupDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_DynamodbGlobalTableRecovery.py b/tests/cloudformation/checks/resource/aws/test_DynamodbGlobalTableRecovery.py
new file mode 100644
index 0000000000..4e8984b3d5
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_DynamodbGlobalTableRecovery.py
@@ -0,0 +1,40 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.DynamodbGlobalTableRecovery import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestDynamodbGlobalTableRecovery(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_DynamodbGlobalTableRecovery"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::DynamoDB::GlobalTable.MyGlobalTableRecoveryEnabled",
+ }
+ failing_resources = {
+ "AWS::DynamoDB::GlobalTable.MyGlobalTableRecoveryDisabled",
+ "AWS::DynamoDB::GlobalTable.MyGlobalTableRecoveryDefault",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_CloudwatchLogGroupRetention.py b/tests/cloudformation/checks/resource/aws/test_EC2Credentials.py
similarity index 68%
rename from tests/cloudformation/checks/resource/aws/test_CloudwatchLogGroupRetention.py
rename to tests/cloudformation/checks/resource/aws/test_EC2Credentials.py
index 3311a50c27..a7694eaef9 100644
--- a/tests/cloudformation/checks/resource/aws/test_CloudwatchLogGroupRetention.py
+++ b/tests/cloudformation/checks/resource/aws/test_EC2Credentials.py
@@ -1,23 +1,23 @@
import os
import unittest
-from checkov.cloudformation.checks.resource.aws.cloudwatchLogGroupRetention import check
+from checkov.cloudformation.checks.resource.aws.EC2Credentials import check
from checkov.cloudformation.runner import Runner
from checkov.runner_filter import RunnerFilter
-class TestcloudwatchLogGroupRetention(unittest.TestCase):
+class TestEC2Credentials(unittest.TestCase):
def test_summary(self):
runner = Runner()
current_dir = os.path.dirname(os.path.realpath(__file__))
- test_files_dir = current_dir + "/example_cloudwatchLogGroupRetention"
+ test_files_dir = current_dir + "/example_EC2Credentials"
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
self.assertEqual(summary['passed'], 2)
- self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/cloudformation/checks/resource/aws/test_EC2PublicIP.py b/tests/cloudformation/checks/resource/aws/test_EC2PublicIP.py
new file mode 100644
index 0000000000..fe7683f622
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_EC2PublicIP.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.EC2PublicIP import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEC2PublicIP(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EC2PublicIP"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ECRImageScanning.py b/tests/cloudformation/checks/resource/aws/test_ECRImageScanning.py
new file mode 100644
index 0000000000..dd32268273
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ECRImageScanning.py
@@ -0,0 +1,45 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ECRImageScanning import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestECRImageScanning(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ECRImageScanning"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::ECR::Repository.ImageScanTrue"
+ }
+
+ failing_resources = {
+ "AWS::ECR::Repository.ImageScanFalse",
+ "AWS::ECR::Repository.ImageScanNotSet"
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ECRPolicy.py b/tests/cloudformation/checks/resource/aws/test_ECRPolicy.py
index 7c616af6ea..505e852d00 100644
--- a/tests/cloudformation/checks/resource/aws/test_ECRPolicy.py
+++ b/tests/cloudformation/checks/resource/aws/test_ECRPolicy.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/cloudformation/checks/resource/aws/test_ECRRepositoryEncrypted.py b/tests/cloudformation/checks/resource/aws/test_ECRRepositoryEncrypted.py
new file mode 100644
index 0000000000..37353eb3de
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ECRRepositoryEncrypted.py
@@ -0,0 +1,45 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ECRRepositoryEncrypted import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestECRRepositoryEncrypted(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ECRRepositoryEncrypted"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::ECR::Repository.KMSEncryption"
+ }
+
+ failing_resources = {
+ "AWS::ECR::Repository.AES256Encryption",
+ "AWS::ECR::Repository.NoEncryption"
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ECSTaskDefinitionEFSVolumeEncryption.py b/tests/cloudformation/checks/resource/aws/test_ECSTaskDefinitionEFSVolumeEncryption.py
new file mode 100644
index 0000000000..751db3eb85
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ECSTaskDefinitionEFSVolumeEncryption.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ECSTaskDefinitionEFSVolumeEncryption import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestECSTaskDefinitionEFSVolumeEncryption(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ECSTaskDefinitionEFSVolumeEncryption"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 3)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_EKSNodeGroupRemoteAccess.py b/tests/cloudformation/checks/resource/aws/test_EKSNodeGroupRemoteAccess.py
new file mode 100644
index 0000000000..cd209c1d52
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_EKSNodeGroupRemoteAccess.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.EKSNodeGroupRemoteAccess import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEKSNodeGroupRemoteAccess(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EKSNodeGroupRemoteAccess"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_EKSSecretEncryption.py b/tests/cloudformation/checks/resource/aws/test_EKSSecretEncryption.py
new file mode 100644
index 0000000000..57b6aa231c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_EKSSecretEncryption.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.EKSSecretsEncryption import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEKSSecretEncryption(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EKSSecretEncryption"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ELBAccessLogs.py b/tests/cloudformation/checks/resource/aws/test_ELBAccessLogs.py
new file mode 100644
index 0000000000..190e3a0405
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ELBAccessLogs.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ELBAccessLogs import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestELBAccessLogs(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ELBAccessLogs"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ELBv2AccessLogs.py b/tests/cloudformation/checks/resource/aws/test_ELBv2AccessLogs.py
new file mode 100644
index 0000000000..cb108acc1f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ELBv2AccessLogs.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ELBv2AccessLogs import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestELBv2AccessLogs(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ELBv2AccessLogs"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainEnforceHTTPS.py b/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainEnforceHTTPS.py
new file mode 100644
index 0000000000..d74417ef56
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainEnforceHTTPS.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ElasticsearchDomainEnforceHTTPS import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestElasticsearchDomainEnforceHTTPS(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ElasticsearchDomainEnforceHTTPS"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainLogging.py b/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainLogging.py
new file mode 100644
index 0000000000..64c1415d47
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_ElasticsearchDomainLogging.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.ElasticsearchDomainLogging import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestElasticsearchDomainLogging(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ElasticsearchDomainLogging"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_GlueDataCatalogEncryption.py b/tests/cloudformation/checks/resource/aws/test_GlueDataCatalogEncryption.py
new file mode 100644
index 0000000000..c09ccc31b8
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_GlueDataCatalogEncryption.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.GlueDataCatalogEncryption import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestGlueDataCatalogEncryption(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_GlueDataCatalogEncryption"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfiguration.py b/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfiguration.py
new file mode 100644
index 0000000000..d48936889f
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfiguration.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.GlueSecurityConfiguration import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestGlueSecurityConfiguration(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_GlueSecurityConfiguration"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 4)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_IAMPermissionsManagement.py b/tests/cloudformation/checks/resource/aws/test_IAMPermissionsManagement.py
new file mode 100644
index 0000000000..ec1a5c8646
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_IAMPermissionsManagement.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMPermissionsManagement import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestIAMPermisionsManagement(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_IAMPermissionsManagement"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py b/tests/cloudformation/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py
new file mode 100644
index 0000000000..8a83113787
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py
@@ -0,0 +1,50 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMRoleAllowsPublicAssume import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestIAMRoleAllowsPublicAssume(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_IAMRoleAllowsPublicAssume"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::IAM::Role.ServiceRole",
+ "AWS::IAM::Role.DenyIgnore",
+ "AWS::IAM::Role.ServiceRole2",
+ "AWS::IAM::Role.DenyIgnore2",
+ }
+
+ failing_resources = {
+ "AWS::IAM::Role.AWSStarPrincipal",
+ "AWS::IAM::Role.AWSStarPrincipalInList",
+ "AWS::IAM::Role.AWSStarPrincipal2",
+ "AWS::IAM::Role.AWSStarPrincipalInList2",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 4)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_IAMWriteAccess.py b/tests/cloudformation/checks/resource/aws/test_IAMWriteAccess.py
new file mode 100644
index 0000000000..73e21782cb
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_IAMWriteAccess.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IAMWriteAccess import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestIAMWriteAccess(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/Cloudsplaining_IAMWriteAccess"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(report.failed_checks[0].check_id, check.id)
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_IMDSv1Disabled.py b/tests/cloudformation/checks/resource/aws/test_IMDSv1Disabled.py
new file mode 100644
index 0000000000..5699ae0e19
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_IMDSv1Disabled.py
@@ -0,0 +1,32 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.IMDSv1Disabled import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestIMDSv1Disabled(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_IMDSv1Disabled"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_KinesisStreamEncryptionType.py b/tests/cloudformation/checks/resource/aws/test_KinesisStreamEncryptionType.py
index b1e56d51e4..31d277d705 100644
--- a/tests/cloudformation/checks/resource/aws/test_KinesisStreamEncryptionType.py
+++ b/tests/cloudformation/checks/resource/aws/test_KinesisStreamEncryptionType.py
@@ -16,10 +16,29 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::Kinesis::Stream.KMSEncryption"
+ }
+
+ failing_resources = {
+ "AWS::Kinesis::Stream.NoEncryption"
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
if __name__ == '__main__':
diff --git a/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py b/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py
new file mode 100644
index 0000000000..97ce930bea
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.LambdaEnvironmentCredentials import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestLambdaEnvironmentCredentials(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_LambdaEnvironmentCredentials"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_NeptuneClusterLogging.py b/tests/cloudformation/checks/resource/aws/test_NeptuneClusterLogging.py
new file mode 100644
index 0000000000..bdc1255060
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_NeptuneClusterLogging.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.NeptuneClusterLogging import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestNeptuneClusterLogging(unittest.TestCase):
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_NeptuneClusterLogging"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Neptune::DBCluster.NeptuneDBClusterEnabled",
+ }
+ failing_resources = {
+ "AWS::Neptune::DBCluster.NeptuneDBClusterDefault",
+ "AWS::Neptune::DBCluster.NeptuneDBClusterEmpty",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_QLDBLedgerDeletionProtection.py b/tests/cloudformation/checks/resource/aws/test_QLDBLedgerDeletionProtection.py
new file mode 100644
index 0000000000..b3e2ebd6f0
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_QLDBLedgerDeletionProtection.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.QLDBLedgerDeletionProtection import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestQLDBLedgerDeletionProtection(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_QLDBLedgerDeletionProtection"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::QLDB::Ledger.Default",
+ "AWS::QLDB::Ledger.Enabled",
+ }
+ failing_resources = {
+ "AWS::QLDB::Ledger.Disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_QLDBLedgerPermissionsMode.py b/tests/cloudformation/checks/resource/aws/test_QLDBLedgerPermissionsMode.py
new file mode 100644
index 0000000000..f2986ad7a0
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_QLDBLedgerPermissionsMode.py
@@ -0,0 +1,36 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.QLDBLedgerPermissionsMode import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestQLDBLedgerPermissionsMode(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_QLDBLedgerPermissionsMode"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::QLDB::Ledger.Standard",
+ }
+ failing_resources = {
+ "AWS::QLDB::Ledger.AllowAll",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RDSClusterIAMAuthentication.py b/tests/cloudformation/checks/resource/aws/test_RDSClusterIAMAuthentication.py
new file mode 100644
index 0000000000..7cd37accae
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RDSClusterIAMAuthentication.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.RDSClusterIAMAuthentication import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRDSClusterIAMAuthentication(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_RDSClusterIAMAuthentication"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::RDS::DBCluster.Enabled",
+ }
+ failing_resources = {
+ "AWS::RDS::DBCluster.Default",
+ "AWS::RDS::DBCluster.Disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RDSIAMAuthentication.py b/tests/cloudformation/checks/resource/aws/test_RDSIAMAuthentication.py
new file mode 100644
index 0000000000..97ce47af0c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RDSIAMAuthentication.py
@@ -0,0 +1,40 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.RDSIAMAuthentication import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRDSIAMAuthentication(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_RDSIAMAuthentication"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::RDS::DBInstance.EnabledMysql",
+ "AWS::RDS::DBInstance.EnabledPostgres",
+ }
+ failing_resources = {
+ "AWS::RDS::DBInstance.DefaultMysql",
+ "AWS::RDS::DBInstance.DefaultPostgres",
+ "AWS::RDS::DBInstance.DisabledMysql",
+ "AWS::RDS::DBInstance.DisabledPostgres",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RDSMultiAZEnabled.py b/tests/cloudformation/checks/resource/aws/test_RDSMultiAZEnabled.py
new file mode 100644
index 0000000000..f735f1cc42
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RDSMultiAZEnabled.py
@@ -0,0 +1,40 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.RDSMultiAZEnabled import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRDSMultiAZEnabled(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RDSMultiAZEnabled"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::RDS::DBInstance.MyDBEnabled",
+ }
+ failing_resources = {
+ "AWS::RDS::DBInstance.MyDBDefault",
+ "AWS::RDS::DBInstance.MyDBDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RedShiftSSL.py b/tests/cloudformation/checks/resource/aws/test_RedShiftSSL.py
new file mode 100644
index 0000000000..fa5dbf45ef
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RedShiftSSL.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.RedShiftSSL import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRedShiftSSL(unittest.TestCase):
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RedShiftSSL"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Redshift::ClusterParameterGroup.RedshiftParameterGroupEnabled",
+ }
+ failing_resources = {
+ "AWS::Redshift::ClusterParameterGroup.RedshiftParameterGroupDefault",
+ "AWS::Redshift::ClusterParameterGroup.RedshiftParameterGroupDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RedshiftClusterEncryption.py b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterEncryption.py
index 6041578d2a..856ef26104 100644
--- a/tests/cloudformation/checks/resource/aws/test_RedshiftClusterEncryption.py
+++ b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterEncryption.py
@@ -7,20 +7,33 @@
class TestRedshiftClusterEncryption(unittest.TestCase):
-
def test_summary(self):
runner = Runner()
current_dir = os.path.dirname(os.path.realpath(__file__))
test_files_dir = current_dir + "/example_RedshiftClusterEncryption"
- report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 2)
- self.assertEqual(summary['skipped'], 0)
- self.assertEqual(summary['parsing_errors'], 0)
+ passing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterEnabled",
+ }
+ failing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterDefault",
+ "AWS::Redshift::Cluster.RedshiftClusterDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RedshiftClusterLogging.py b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterLogging.py
new file mode 100644
index 0000000000..82280c08da
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterLogging.py
@@ -0,0 +1,35 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.RedshiftClusterLogging import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRedshiftClusterLogging(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_RedshiftClusterLogging"
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterEnabled",
+ }
+ failing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterDefault",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py
new file mode 100644
index 0000000000..decc0e982c
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py
@@ -0,0 +1,36 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.RedshiftClusterPubliclyAccessible import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRedshiftClusterPubliclyAccessible(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_RedshiftClusterPubliclyAccessible"
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterDefault",
+ "AWS::Redshift::Cluster.RedshiftClusterEnabled",
+ }
+ failing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterDisabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_RedshiftInEc2ClassicMode.py b/tests/cloudformation/checks/resource/aws/test_RedshiftInEc2ClassicMode.py
new file mode 100644
index 0000000000..d1b211f87b
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_RedshiftInEc2ClassicMode.py
@@ -0,0 +1,35 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.RedshiftInEc2ClassicMode import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRedshiftInEc2ClassicMode(unittest.TestCase):
+ def test_summary(self):
+ test_files_dir = Path(__file__).parent / "example_RedshiftInEc2ClassicMode"
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterEnabled",
+ }
+ failing_resources = {
+ "AWS::Redshift::Cluster.RedshiftClusterDefault",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py b/tests/cloudformation/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
index ee3dc17532..d9d36f17cc 100644
--- a/tests/cloudformation/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
+++ b/tests/cloudformation/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
@@ -17,7 +17,7 @@ def test_summary(self):
summary = report.get_summary()
self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['failed'], 3)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/cloudformation/checks/resource/aws/test_TimestreamDatabaseKMSKey.py b/tests/cloudformation/checks/resource/aws/test_TimestreamDatabaseKMSKey.py
new file mode 100644
index 0000000000..78e3d61363
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_TimestreamDatabaseKMSKey.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.TimestreamDatabaseKMSKey import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRedShiftSSL(unittest.TestCase):
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_TimestreamDatabaseKMSKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Timestream::Database.TimestreamDatabaseEnabled",
+ }
+ failing_resources = {
+ "AWS::Timestream::Database.TimestreamDatabaseDefault",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_TransferServerIsPublic.py b/tests/cloudformation/checks/resource/aws/test_TransferServerIsPublic.py
new file mode 100644
index 0000000000..bdf348ec5e
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_TransferServerIsPublic.py
@@ -0,0 +1,40 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.TransferServerIsPublic import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestTransferServerIsPublic(unittest.TestCase):
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_TransferServerIsPublic"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "AWS::Transfer::Server.VPC",
+ "AWS::Transfer::Server.VPCENDPOINT",
+ }
+ failing_resources = {
+ "AWS::Transfer::Server.PUBLIC",
+ "AWS::Transfer::Server.NONE",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py b/tests/cloudformation/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py
new file mode 100644
index 0000000000..8de39db526
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.VPCEndpointAcceptanceConfigured import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestVPCEndpointAcceptanceConfigured(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_VPCEndpointAcceptanceConfigured"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::EC2::VPCEndpointService.Pass",
+ }
+
+ failing_resources = {
+ "AWS::EC2::VPCEndpointService.FailDefault",
+ "AWS::EC2::VPCEndpointService.FailExplicit",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py b/tests/cloudformation/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py
new file mode 100644
index 0000000000..c359746044
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.WorkspaceRootVolumeEncrypted import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestWorkspaceRootVolumeEncrypted(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_WorkspaceRootVolumeEncrypted"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::WorkSpaces::Workspace.Pass",
+ }
+
+ failing_resources = {
+ "AWS::WorkSpaces::Workspace.FailDefault",
+ "AWS::WorkSpaces::Workspace.FailExplicit",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py b/tests/cloudformation/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py
new file mode 100644
index 0000000000..e76927a528
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.cloudformation.checks.resource.aws.WorkspaceUserVolumeEncrypted import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestWorkspaceUserVolumeEncrypted(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_WorkspaceUserVolumeEncrypted"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ for record in report.failed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ for record in report.passed_checks:
+ self.assertEqual(record.check_id, check.id)
+
+ passing_resources = {
+ "AWS::WorkSpaces::Workspace.Pass",
+ }
+
+ failing_resources = {
+ "AWS::WorkSpaces::Workspace.FailDefault",
+ "AWS::WorkSpaces::Workspace.FailExplicit",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/file_formats/json_with_space/test_json_with_space.json b/tests/cloudformation/file_formats/json_with_space/test_json_with_space.json
new file mode 100644
index 0000000000..bdc19cf651
--- /dev/null
+++ b/tests/cloudformation/file_formats/json_with_space/test_json_with_space.json
@@ -0,0 +1,11 @@
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Resources": {
+ "MySourceQueue": {
+ "Type": "AWS::SQS::Queue",
+ "Properties": {
+ "KmsMasterKeyId": "kms_id"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/file_formats/json_with_tabs/test_json_with_tabs.json b/tests/cloudformation/file_formats/json_with_tabs/test_json_with_tabs.json
new file mode 100644
index 0000000000..747cf6d566
--- /dev/null
+++ b/tests/cloudformation/file_formats/json_with_tabs/test_json_with_tabs.json
@@ -0,0 +1,11 @@
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Resources": {
+ "MySourceQueue": {
+ "Type": "AWS::SQS::Queue",
+ "Properties": {
+ "KmsMasterKeyId": "kms_id"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/file_formats/test_json_with_space.py b/tests/cloudformation/file_formats/test_json_with_space.py
new file mode 100644
index 0000000000..efc25b6a35
--- /dev/null
+++ b/tests/cloudformation/file_formats/test_json_with_space.py
@@ -0,0 +1,25 @@
+import os
+import unittest
+
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestJsonFileFormat(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/json_with_space"
+ report = runner.run(root_folder=test_files_dir)
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 0)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/file_formats/test_json_with_tabs.py b/tests/cloudformation/file_formats/test_json_with_tabs.py
new file mode 100644
index 0000000000..2b904cfc34
--- /dev/null
+++ b/tests/cloudformation/file_formats/test_json_with_tabs.py
@@ -0,0 +1,25 @@
+import os
+import unittest
+
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestJsonFileFormat(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/json_with_tabs"
+ report = runner.run(root_folder=test_files_dir)
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 0)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/file_formats/test_yaml.py b/tests/cloudformation/file_formats/test_yaml.py
new file mode 100644
index 0000000000..76149f8621
--- /dev/null
+++ b/tests/cloudformation/file_formats/test_yaml.py
@@ -0,0 +1,25 @@
+import os
+import unittest
+
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestYamlFileFormat(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/yaml"
+ report = runner.run(root_folder=test_files_dir)
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 0)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/file_formats/yaml/test_yaml.yaml b/tests/cloudformation/file_formats/yaml/test_yaml.yaml
new file mode 100644
index 0000000000..cfbf3e4105
--- /dev/null
+++ b/tests/cloudformation/file_formats/yaml/test_yaml.yaml
@@ -0,0 +1,6 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+ MySourceQueue:
+ Type: AWS::SQS::Queue
+ Properties:
+ KmsMasterKeyId: "kms_id"
\ No newline at end of file
diff --git a/tests/cloudformation/graph/__init__.py b/tests/cloudformation/graph/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/cloudformation/graph/checks/__init__.py b/tests/cloudformation/graph/checks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/cloudformation/graph/checks/resources/LambdaFunction/expected.yaml b/tests/cloudformation/graph/checks/resources/LambdaFunction/expected.yaml
new file mode 100644
index 0000000000..30ab0b77e8
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/LambdaFunction/expected.yaml
@@ -0,0 +1,6 @@
+pass:
+ - "AWS::Lambda::Function.GoodLambdaFunction"
+fail:
+ - "AWS::Lambda::Function.WrongTracingConfigValueLambdaFunction"
+ - "AWS::Lambda::Function.WithoutTracingConfigLambdaFunction"
+
diff --git a/tests/cloudformation/graph/checks/resources/LambdaFunction/template.yaml b/tests/cloudformation/graph/checks/resources/LambdaFunction/template.yaml
new file mode 100644
index 0000000000..2a11b03560
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/LambdaFunction/template.yaml
@@ -0,0 +1,63 @@
+Description: X-ray tracing is enabled for Lambda.
+Resources:
+ GoodLambdaFunction:
+ Type: "AWS::Lambda::Function"
+ Properties:
+ FunctionName: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ Runtime: nodejs12.x
+ Role: !GetAtt IAM4Lambda.Arn
+ Handler: exports.test
+ Code:
+ ZipFile: |
+ console.log("Hello World");
+ Environment:
+ Variables:
+ access_key: "AKIAIOSFODNN7EXAMPLE"
+ secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ - Key: Environment
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}"
+ Tracing_config:
+ Mode: "Active"
+ WrongTracingConfigValueLambdaFunction:
+ Type: "AWS::Lambda::Function"
+ Properties:
+ FunctionName: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ Runtime: nodejs12.x
+ Role: !GetAtt IAM4Lambda.Arn
+ Handler: exports.test
+ Code:
+ ZipFile: |
+ console.log("Hello World");
+ Environment:
+ Variables:
+ access_key: "AKIAIOSFODNN7EXAMPLE"
+ secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ - Key: Environment
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}"
+ Tracing_config:
+ Mode: "Wrong value"
+ WithoutTracingConfigLambdaFunction:
+ Type: "AWS::Lambda::Function"
+ Properties:
+ FunctionName: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ Runtime: nodejs12.x
+ Role: !GetAtt IAM4Lambda.Arn
+ Handler: exports.test
+ Code:
+ ZipFile: |
+ console.log("Hello World");
+ Environment:
+ Variables:
+ access_key: "AKIAIOSFODNN7EXAMPLE"
+ secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ - Key: Environment
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}"
diff --git a/tests/cloudformation/graph/checks/resources/MSKClusterLogging/expected.yaml b/tests/cloudformation/graph/checks/resources/MSKClusterLogging/expected.yaml
new file mode 100644
index 0000000000..280eca79cd
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/MSKClusterLogging/expected.yaml
@@ -0,0 +1,9 @@
+pass:
+ - "AWS::MSK::Cluster.ClusterCloudWatchLogsGood"
+ - "AWS::MSK::Cluster.ClusterFirehoseGood"
+ - "AWS::MSK::Cluster.Clusters3Good"
+ - "AWS::MSK::Cluster.ClusterAllGood"
+fail:
+ - "AWS::MSK::Cluster.ClusterBadNoLoggingInfo"
+ - "AWS::MSK::Cluster.ClusterBadNoLoggingDisabled"
+
diff --git a/tests/cloudformation/graph/checks/resources/MSKClusterLogging/template.yaml b/tests/cloudformation/graph/checks/resources/MSKClusterLogging/template.yaml
new file mode 100644
index 0000000000..97fbce9486
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/MSKClusterLogging/template.yaml
@@ -0,0 +1,103 @@
+Description: MSK Cluster with required properties.
+Resources:
+ ClusterBadNoLoggingInfo:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ Tags:
+ - Key: yor_trace
+ Value: "mock_trace"
+ ClusterBadNoLoggingDisabled:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: false
+ ClusterCloudWatchLogsGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: true
+ ClusterFirehoseGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ Firehose:
+ Enabled: true
+ Clusters3Good:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ S3:
+ Enabled: true
+ ClusterAllGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: true
+ Firehose:
+ Enabled: true
+ S3:
+ Enabled: true
+
+
diff --git a/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/expected.yaml b/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/expected.yaml
new file mode 100644
index 0000000000..00c6e7067d
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "AWS::SageMaker::NotebookInstance.BasicNotebookInstanceGood"
+fail:
+ - "AWS::SageMaker::NotebookInstance.BasicNotebookInstanceBad"
diff --git a/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/template.yaml b/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/template.yaml
new file mode 100644
index 0000000000..aeb525c7a7
--- /dev/null
+++ b/tests/cloudformation/graph/checks/resources/SagemakerNotebookEncryption/template.yaml
@@ -0,0 +1,40 @@
+Description: "Basic NotebookInstance test update to a different instance type"
+Resources:
+ BasicNotebookInstanceBad:
+ Type: "AWS::SageMaker::NotebookInstance"
+ Properties:
+ InstanceType: "ml.t2.large"
+ RoleArn: !GetAtt ExecutionRole.Arn
+ BasicNotebookInstanceGood:
+ Type: "AWS::SageMaker::NotebookInstance"
+ Properties:
+ InstanceType: "ml.t2.large"
+ RoleArn: !GetAtt ExecutionRole.Arn
+ KmsKeyId: "test_kms_key"
+ ExecutionRole:
+ Type: "AWS::IAM::Role"
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Principal:
+ Service:
+ - "sagemaker.amazonaws.com"
+ Action:
+ - "sts:AssumeRole"
+ Path: "/"
+ Policies:
+ -
+ PolicyName: "root"
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Action: "*"
+ Resource: "*"
+Outputs:
+ BasicNotebookInstanceId:
+ Value: !Ref BasicNotebookInstance
\ No newline at end of file
diff --git a/tests/cloudformation/graph/checks/test_yaml_policies.py b/tests/cloudformation/graph/checks/test_yaml_policies.py
new file mode 100644
index 0000000000..f8dc35c525
--- /dev/null
+++ b/tests/cloudformation/graph/checks/test_yaml_policies.py
@@ -0,0 +1,138 @@
+import copy
+import json
+import os
+import unittest
+import warnings
+from pathlib import Path
+from typing import List
+
+import yaml
+
+from checkov.cloudformation import checks
+from checkov.cloudformation.graph_manager import CloudformationGraphManager
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.checks_infra.registry import Registry
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.common.graph.graph_builder import CustomAttributes
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.record import Record
+from checkov.common.output.report import Report
+from checkov.runner_filter import RunnerFilter
+
+
+class TestYamlPolicies(unittest.TestCase):
+ def setUp(self) -> None:
+ os.environ['UNIQUE_TAG'] = ''
+ warnings.filterwarnings("ignore", category=ResourceWarning)
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+ def test_SagemakerNotebookEncryption(self):
+ self.go("SagemakerNotebookEncryption")
+
+ def test_MSKClusterLogging(self):
+ self.go("MSKClusterLogging")
+
+ def test_LambdaFunction(self):
+ self.go("LambdaFunction")
+
+ def test_registry_load(self):
+ registry = get_checks_registry()
+ self.assertGreater(len(registry.checks), 0)
+
+ def go(self, dir_name, check_name=None):
+ dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+ f"resources/{dir_name}")
+ assert os.path.exists(dir_path)
+ policy_dir_path = os.path.dirname(checks.__file__)
+ assert os.path.exists(policy_dir_path)
+ found = False
+ for root, d_names, f_names in os.walk(policy_dir_path):
+ for f_name in f_names:
+ check_name = dir_name if check_name is None else check_name
+ if f_name == f"{check_name}.yaml":
+ found = True
+ policy = load_yaml_data(f_name, root)
+ assert policy is not None
+ expected = load_yaml_data("expected.yaml", dir_path)
+ assert expected is not None
+ report = get_policy_results(dir_path, policy)
+ expected = load_yaml_data("expected.yaml", dir_path)
+
+ expected_to_fail = expected.get('fail', [])
+ expected_to_pass = expected.get('pass', [])
+ expected_to_skip = expected.get('skip', [])
+ self.assert_entities(expected_to_pass, report.passed_checks, True)
+ self.assert_entities(expected_to_fail, report.failed_checks, False)
+ self.assert_entities(expected_to_skip, report.skipped_checks, True)
+
+ assert found
+
+ def assert_entities(self, expected_entities: List[str], results: List[Record], assertion: bool):
+ self.assertEqual(len(expected_entities), len(results),
+ f"mismatch in number of results in {'passed' if assertion else 'failed'}, "
+ f"expected: {len(expected_entities)}, got: {len(results)}")
+ for expected_entity in expected_entities:
+ found = False
+ for check_result in results:
+ entity_id = check_result.resource
+ if entity_id == expected_entity:
+ found = True
+ break
+ self.assertTrue(found, f"expected to find entity {expected_entity}, {'passed' if assertion else 'failed'}")
+
+
+def get_checks_registry():
+ registry = Registry(parser=NXGraphCheckParser(), checks_dir=str(
+ Path(
+ __file__).parent.parent.parent.parent.parent / "checkov" / "cloudformation" / "checks" / "graph_checks"))
+ registry.load_checks()
+ return registry
+
+
+def get_policy_results(root_folder, policy):
+ check_id = policy['metadata']['id']
+ graph_manager = CloudformationGraphManager(db_connector=NetworkxConnector())
+ local_graph, _ = graph_manager.build_graph_from_source_directory(root_folder)
+ nx_graph = graph_manager.save_graph(local_graph)
+ registry = get_checks_registry()
+ checks_results = registry.run_checks(nx_graph, RunnerFilter(checks=[check_id]))
+ return create_report_from_graph_checks_results(checks_results, policy['metadata'])
+
+
+def create_report_from_graph_checks_results(checks_results, check):
+ report = Report("cloudformation")
+ first_results_key = list(checks_results.keys())[0]
+ for check_result in checks_results[first_results_key]:
+ entity = check_result["entity"]
+ record = Record(check_id=check['id'],
+ check_name=check['name'],
+ check_result=copy.deepcopy(check_result),
+ code_block="",
+ file_path=entity.get(CustomAttributes.FILE_PATH),
+ file_line_range=[entity.get('__startline__'), entity.get('__endline__')],
+ resource=entity.get(CustomAttributes.BLOCK_NAME),
+ entity_tags=entity.get('tags', {}),
+ evaluations=None,
+ check_class=None,
+ file_abs_path=entity.get(CustomAttributes.FILE_PATH))
+ if check_result["result"] == CheckResult.PASSED:
+ report.passed_checks.append(record)
+ if check_result["result"] == CheckResult.FAILED:
+ report.failed_checks.append(record)
+ return report
+
+
+def wrap_policy(policy):
+ policy['query'] = policy['definition']
+ del policy['definition']
+
+
+def load_yaml_data(source_file_name, dir_path):
+ expected_path = os.path.join(dir_path, source_file_name)
+ if not os.path.exists(expected_path):
+ return None
+
+ with open(expected_path, "r") as f:
+ expected_data = yaml.safe_load(f)
+
+ return json.loads(json.dumps(expected_data))
diff --git a/tests/cloudformation/graph/graph_builder/__init__.py b/tests/cloudformation/graph/graph_builder/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/cloudformation/graph/graph_builder/resources/edges_json/test.json b/tests/cloudformation/graph/graph_builder/resources/edges_json/test.json
new file mode 100644
index 0000000000..1e45ec20ee
--- /dev/null
+++ b/tests/cloudformation/graph/graph_builder/resources/edges_json/test.json
@@ -0,0 +1,179 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Parameters": {
+ "EnvType": {
+ "Description": "Environment type.",
+ "Default": "test",
+ "Type": "String",
+ "AllowedValues": [
+ "prod",
+ "dev",
+ "test"
+ ],
+ "ConstraintDescription": "must specify prod, dev, or test."
+ },
+ "DataBucketName": {
+ "Description": "Bucket Name",
+ "Type": "String",
+ "Default": "bucket_name"
+ }
+ },
+ "Mappings": {
+ "RegionMap": {
+ "us-east-1": {
+ "AMI": "ami-0ff8a91507f77f867"
+ },
+ "us-west-1": {
+ "AMI": "ami-0bdb828fd58c52235"
+ },
+ "us-west-2": {
+ "AMI": "ami-a0cfeed8"
+ },
+ "eu-west-1": {
+ "AMI": "ami-047bb4163c506cd98"
+ },
+ "sa-east-1": {
+ "AMI": "ami-07b14488da8ea02a0"
+ },
+ "ap-southeast-1": {
+ "AMI": "ami-08569b978cc4dfa10"
+ },
+ "ap-southeast-2": {
+ "AMI": "ami-09b42976632b27e9b"
+ },
+ "ap-northeast-1": {
+ "AMI": "ami-06cd52961ce9f0d85"
+ }
+ }
+ },
+ "Conditions": {
+ "CreateProdResources": {
+ "Fn::Equals": [
+ {
+ "Ref": "EnvType"
+ },
+ "prod"
+ ]
+ },
+ "CreateDevResources": {
+ "Fn::Equals": [
+ {
+ "Ref": "EnvType"
+ },
+ "dev"
+ ]
+ }
+ },
+ "Resources": {
+ "EC2Instance": {
+ "Type": "AWS::EC2::Instance",
+ "Properties": {
+ "ImageId": {
+ "Fn::FindInMap": [
+ "RegionMap",
+ {
+ "Ref": "AWS::Region"
+ },
+ "AMI"
+ ]
+ },
+ "InstanceType": {
+ "Fn::If": [
+ "CreateProdResources",
+ "c1.xlarge",
+ {
+ "Fn::If": [
+ "CreateDevResources",
+ "m1.large",
+ "m1.small"
+ ]
+ }
+ ]
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": {
+ "Fn::Sub": [
+ "ec2-${Environment}",
+ {
+ "Environment": {
+ "Ref": "EnvType"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "MountPoint": {
+ "Type": "AWS::EC2::VolumeAttachment",
+ "Condition": "CreateProdResources",
+ "Properties": {
+ "InstanceId": {
+ "Ref": "EC2Instance"
+ },
+ "VolumeId": {
+ "Ref": "NewVolume"
+ },
+ "Device": "/dev/sdh"
+ }
+ },
+ "NewVolume": {
+ "Type": "AWS::EC2::Volume",
+ "Condition": "CreateProdResources",
+ "Properties": {
+ "Size": 100,
+ "AvailabilityZone": {
+ "Fn::GetAtt": [
+ "EC2Instance",
+ "AvailabilityZone"
+ ]
+ }
+ }
+ },
+ "DataBucket": {
+ "Type": "AWS::S3::Bucket",
+ "DeletionPolicy": "Delete",
+ "DependsOn": "EC2Instance",
+ "Properties": {
+ "BucketName": {
+ "Ref": "DataBucketName"
+ },
+ "AccessControl": "PublicRead",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": {
+ "Fn::Sub": "${AWS::AccountId}-${DataBucketName}-${EnvType}"
+ }
+ }
+ ]
+ }
+ }
+ },
+ "Outputs": {
+ "EC2InstanceId": {
+ "Description": "Web Host Public DNS Name",
+ "Value": {
+ "Ref": "EC2Instance"
+ }
+ },
+ "EC2PublicDNS": {
+ "Description": "Web Host Public DNS Name",
+ "Value": {
+ "Fn::GetAtt": [
+ "EC2Instance",
+ "PublicDnsName"
+ ]
+ }
+ },
+ "DataBucketUniqueId": {
+ "Description": "Data Bucket Name",
+ "Value": {
+ "Fn::Sub": "DataBucket-${DataBucket}-${DataBucketName}"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/graph/graph_builder/resources/edges_yaml/test.yaml b/tests/cloudformation/graph/graph_builder/resources/edges_yaml/test.yaml
new file mode 100644
index 0000000000..de346bea31
--- /dev/null
+++ b/tests/cloudformation/graph/graph_builder/resources/edges_yaml/test.yaml
@@ -0,0 +1,83 @@
+AWSTemplateFormatVersion: "2010-09-09"
+
+Parameters:
+ EnvType:
+ Description: Environment type.
+ Default: test
+ Type: String
+ AllowedValues: [prod, dev, test]
+ ConstraintDescription: must specify prod, dev, or test.
+ DataBucketName:
+ Description: Bucket Name
+ Type: String
+ Default: bucket_name
+
+Mappings:
+ RegionMap:
+ us-east-1:
+ AMI: "ami-0ff8a91507f77f867"
+ us-west-1:
+ AMI: "ami-0bdb828fd58c52235"
+ us-west-2:
+ AMI: "ami-a0cfeed8"
+ eu-west-1:
+ AMI: "ami-047bb4163c506cd98"
+ sa-east-1:
+ AMI: "ami-07b14488da8ea02a0"
+ ap-southeast-1:
+ AMI: "ami-08569b978cc4dfa10"
+ ap-southeast-2:
+ AMI: "ami-09b42976632b27e9b"
+ ap-northeast-1:
+ AMI: "ami-06cd52961ce9f0d85"
+
+Conditions:
+ CreateProdResources: !Equals [!Ref EnvType, prod]
+ CreateDevResources: !Equals [!Ref EnvType, "dev"]
+
+Resources:
+ EC2Instance:
+ Type: "AWS::EC2::Instance"
+ Properties:
+ ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
+ InstanceType: !If [CreateProdResources, c1.xlarge, !If [CreateDevResources, m1.large, m1.small]]
+ Tags:
+ - Key: Name
+ Value: !Sub
+ - ec2-${Environment}
+ - Environment: !Ref EnvType
+ MountPoint:
+ Type: "AWS::EC2::VolumeAttachment"
+ Condition: CreateProdResources
+ Properties:
+ InstanceId: !Ref EC2Instance
+ VolumeId: !Ref NewVolume
+ Device: /dev/sdh
+ NewVolume:
+ Type: "AWS::EC2::Volume"
+ Condition: CreateProdResources
+ Properties:
+ Size: 100
+ AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
+ DataBucket:
+ # Public, not encrypted, no access logs, no versioning
+ Type: AWS::S3::Bucket
+ DeletionPolicy: Delete
+ DependsOn: EC2Instance
+ Properties:
+ BucketName: !Ref DataBucketName
+ AccessControl: PublicRead
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${DataBucketName}-${EnvType}"
+
+Outputs:
+ EC2InstanceId:
+ Description: Web Host Public DNS Name
+ Value: !Ref EC2Instance
+ EC2PublicDNS:
+ Description: Web Host Public DNS Name
+ Value: !GetAtt [EC2Instance, PublicDnsName]
+ DataBucketUniqueId:
+ Description: Data Bucket Name
+ Value: !Sub "DataBucket-${DataBucket}-${DataBucketName}"
diff --git a/tests/cloudformation/graph/graph_builder/resources/vertices/test.json b/tests/cloudformation/graph/graph_builder/resources/vertices/test.json
new file mode 100644
index 0000000000..71bf7ab4c9
--- /dev/null
+++ b/tests/cloudformation/graph/graph_builder/resources/vertices/test.json
@@ -0,0 +1,40 @@
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description": "AWS CloudFormation Template to deploy insecure infrastructure",
+ "Parameters": {
+ "KmsMasterKeyId": {
+ "Description": "Company Name",
+ "Type": "String",
+ "Default": "kms_id"
+ },
+ "DBName": {
+ "Description": "Name of the Database",
+ "Type": "String",
+ "Default": "db"
+ }
+ },
+ "Resources": {
+ "MySourceQueue": {
+ "Type": "AWS::SQS::Queue",
+ "Properties": {
+ "KmsMasterKeyId": { "Ref": "KmsMasterKeyId" }
+ }
+ },
+ "MyDB": {
+ "Type": "AWS::RDS::DBInstance",
+ "Properties": {
+ "DBName": { "Ref": "DBName" },
+ "DBInstanceClass": "db.t3.micro",
+ "Engine": "mysql",
+ "MasterUsername": "master",
+ "MasterUserPassword": "password"
+ }
+ }
+ },
+ "Outputs": {
+ "DBAppPublicDNS": {
+ "Description": "DB App Public DNS Name",
+ "Value": { "Fn::GetAtt" : [ "MyDB", "PublicDnsName" ] }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/graph/graph_builder/resources/vertices/test.yaml b/tests/cloudformation/graph/graph_builder/resources/vertices/test.yaml
new file mode 100644
index 0000000000..8aa5594a62
--- /dev/null
+++ b/tests/cloudformation/graph/graph_builder/resources/vertices/test.yaml
@@ -0,0 +1,30 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: AWS CloudFormation Template to deploy insecure infrastructure
+Parameters:
+ KmsMasterKeyId:
+ Description: Company Name
+ Type: String
+ Default: kms_id
+ DBName:
+ Description: Name of the Database
+ Type: String
+ Default: db1
+Resources:
+ MySourceQueue:
+ Type: AWS::SQS::Queue
+ Properties:
+ KmsMasterKeyId: !Ref KmsMasterKeyId
+ MyDB:
+ Type: 'AWS::RDS::DBInstance'
+ # Test case for check skip via comment
+ # checkov:skip=CKV_AWS_16:Ensure all data stored in the RDS is securely encrypted at rest
+ Properties:
+ DBName: !Ref DBName
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'master'
+ MasterUserPassword: 'password'
+Outputs:
+ DBAppPublicDNS:
+ Description: DB App Public DNS Name
+ Value: !GetAtt [ MyDB, PublicDnsName ]
diff --git a/tests/cloudformation/graph/graph_builder/test_local_graph.py b/tests/cloudformation/graph/graph_builder/test_local_graph.py
new file mode 100644
index 0000000000..5278d37c94
--- /dev/null
+++ b/tests/cloudformation/graph/graph_builder/test_local_graph.py
@@ -0,0 +1,131 @@
+import os
+from unittest import TestCase
+
+from checkov.cloudformation.cfn_utils import create_definitions
+from checkov.cloudformation.graph_builder.graph_components.block_types import BlockType
+from checkov.cloudformation.graph_builder.graph_to_definitions import convert_graph_vertices_to_definitions
+from checkov.cloudformation.graph_builder.local_graph import CloudformationLocalGraph
+from checkov.cloudformation.parser import parse, TemplateSections
+from checkov.runner_filter import RunnerFilter
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestLocalGraph(TestCase):
+ def test_build_graph_with_single_resource(self):
+ relative_file_path = "../../checks/resource/aws/example_APIGatewayXray/APIGatewayXray-PASSED.yaml"
+ definitions = {}
+ file = os.path.realpath(os.path.join(TEST_DIRNAME, relative_file_path))
+ (definitions[relative_file_path], definitions_raw) = parse(file)
+ local_graph = CloudformationLocalGraph(definitions)
+ local_graph.build_graph(render_variables=False)
+ self.assertEqual(1, len(local_graph.vertices))
+ self.assertEqual(0, len(local_graph.edges))
+ resource_vertex = local_graph.vertices[0]
+ self.assertEqual("AWS::ApiGateway::Stage.MyStage", resource_vertex.name)
+ self.assertEqual("AWS::ApiGateway::Stage.MyStage", resource_vertex.id)
+ self.assertEqual(BlockType.RESOURCE, resource_vertex.block_type)
+ self.assertEqual("CloudFormation", resource_vertex.source)
+ self.assertDictEqual(definitions[relative_file_path]["Resources"]["MyStage"]["Properties"],
+ resource_vertex.attributes)
+
+ def test_build_graph_with_params_outputs(self):
+ relative_file_path = "../../checks/resource/aws/example_IAMRoleAllowAssumeFromAccount/example_IAMRoleAllowAssumeFromAccount-PASSED-2.yml"
+ definitions = {}
+ file = os.path.realpath(os.path.join(TEST_DIRNAME, relative_file_path))
+ (definitions[relative_file_path], definitions_raw) = parse(file)
+ local_graph = CloudformationLocalGraph(definitions)
+ local_graph.build_graph(render_variables=False)
+ self.assertEqual(len(local_graph.vertices), 57)
+ self.assertEqual(len([v for v in local_graph.vertices if v.block_type == BlockType.CONDITION]), 2)
+ self.assertEqual(len([v for v in local_graph.vertices if v.block_type == BlockType.RESOURCE]), 16)
+ self.assertEqual(len([v for v in local_graph.vertices if v.block_type == BlockType.PARAMETER]), 30)
+ self.assertEqual(len([v for v in local_graph.vertices if v.block_type == BlockType.OUTPUT]), 8)
+ self.assertEqual(len([v for v in local_graph.vertices if v.block_type == BlockType.MAPPING]), 1)
+
+ def test_vertices_from_local_graph(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, './resources/vertices'))
+ definitions, _ = create_definitions(root_folder=resources_dir, files=None, runner_filter=RunnerFilter())
+ local_graph = CloudformationLocalGraph(definitions)
+ local_graph.build_graph(render_variables=False)
+ definitions, breadcrumbs = convert_graph_vertices_to_definitions(local_graph.vertices, resources_dir)
+
+ self.assertIsNotNone(definitions)
+ self.assertEqual(len(definitions.items()), 2)
+
+ test_yaml_definitions = definitions[os.path.join(resources_dir, 'test.yaml')][TemplateSections.RESOURCES]
+ self.assertEqual(len(test_yaml_definitions.keys()), 2)
+ self.assertIn('MyDB', test_yaml_definitions.keys())
+ self.assertIn('MySourceQueue', test_yaml_definitions.keys())
+
+ test_json_definitions = definitions[os.path.join(resources_dir, 'test.json')][TemplateSections.RESOURCES]
+ self.assertEqual(len(test_json_definitions.keys()), 2)
+ self.assertIn('MyDB', test_json_definitions.keys())
+ self.assertIn('MySourceQueue', test_json_definitions.keys())
+
+ self.assertIsNotNone(breadcrumbs)
+ self.assertDictEqual(breadcrumbs, {}) # Will be changed when we add breadcrumbs to cfn vertices
+
+ def test_yaml_edges(self):
+ root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, 'resources/edges_yaml'))
+ self.validate_edges_count(root_dir)
+
+ def test_json_edges(self):
+ root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, 'resources/edges_json'))
+ self.validate_edges_count(root_dir)
+
+ def validate_edges_count(self, root_dir) -> None:
+ expected_out_edges_count = {
+ 'parameters.EnvType': 0,
+ 'parameters.DataBucketName': 0,
+ 'mappings.RegionMap': 0,
+ 'conditions.CreateProdResources': 1,
+ 'conditions.CreateDevResources': 1,
+ 'AWS::EC2::Instance.EC2Instance': 4,
+ 'AWS::EC2::VolumeAttachment.MountPoint': 3,
+ 'AWS::EC2::Volume.NewVolume': 2,
+ 'AWS::S3::Bucket.DataBucket': 4,
+ 'outputs.EC2InstanceId': 1,
+ 'outputs.EC2PublicDNS': 1,
+ 'outputs.DataBucketUniqueId': 2
+ }
+
+ expected_in_edges_count = {
+ 'parameters.EnvType': 4,
+ 'parameters.DataBucketName': 3,
+ 'mappings.RegionMap': 1,
+ 'conditions.CreateProdResources': 3,
+ 'conditions.CreateDevResources': 1,
+ 'AWS::EC2::Instance.EC2Instance': 5,
+ 'AWS::EC2::VolumeAttachment.MountPoint': 0,
+ 'AWS::EC2::Volume.NewVolume': 1,
+ 'AWS::S3::Bucket.DataBucket': 1,
+ 'outputs.EC2InstanceId': 0,
+ 'outputs.EC2PublicDNS': 0,
+ 'outputs.DataBucketUniqueId': 0
+ }
+
+ definitions, _ = create_definitions(root_folder=root_dir, files=None, runner_filter=RunnerFilter())
+ local_graph = CloudformationLocalGraph(definitions)
+ local_graph.build_graph(render_variables=False)
+ idx_to_vertex_id = {idx: vertex.id for idx, vertex in enumerate(local_graph.vertices)}
+
+ # we check that each entity in the template file has the right amount of out edges_yaml
+ out_edges_overall_count = 0
+ for vertex_index, actual_out_edges in local_graph.out_edges.items():
+ vertex_id = idx_to_vertex_id[vertex_index]
+ self.assertEqual(len(actual_out_edges), expected_out_edges_count[vertex_id], f'{vertex_id} actually has {len(actual_out_edges)} outgoing edges, not {expected_out_edges_count[vertex_id]}')
+ out_edges_overall_count += len(actual_out_edges)
+
+ # we check that each entity in the template file has the right amount of in edges_yaml
+ in_edges_overall_count = 0
+ for vertex_index, actual_in_edges in local_graph.in_edges.items():
+ vertex_id = idx_to_vertex_id[vertex_index]
+ self.assertEqual(len(actual_in_edges), expected_in_edges_count[vertex_id], f'{vertex_id} actually has {len(actual_in_edges)} outgoing edges, not {expected_in_edges_count[vertex_id]}')
+ in_edges_overall_count += len(actual_in_edges)
+
+ # we check that the overall amount of out edges_yaml equals the overall amount of in edges_yaml
+ # and the overall amount of edges_yaml
+ self.assertEqual(out_edges_overall_count, in_edges_overall_count)
+ self.assertEqual(out_edges_overall_count, len(local_graph.edges))
+
diff --git a/tests/cloudformation/graph/graph_runner/__init__.py b/tests/cloudformation/graph/graph_runner/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/cloudformation/graph/graph_runner/resources/LambdaFunction.json b/tests/cloudformation/graph/graph_runner/resources/LambdaFunction.json
new file mode 100644
index 0000000000..cc0a424447
--- /dev/null
+++ b/tests/cloudformation/graph/graph_runner/resources/LambdaFunction.json
@@ -0,0 +1,13 @@
+{
+ "Resources": {
+ "LambdaFunction": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "FunctionName": "${AWS::AccountId}-${CompanyName}-${Environment}-analysis",
+ "Runtime": "nodejs12.x",
+ "Role": "!GetAtt IAM4Lambda.Arn",
+ "Handler": "exports.test"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/graph/graph_runner/resources/MSKClusterLogging.yaml b/tests/cloudformation/graph/graph_runner/resources/MSKClusterLogging.yaml
new file mode 100644
index 0000000000..97fbce9486
--- /dev/null
+++ b/tests/cloudformation/graph/graph_runner/resources/MSKClusterLogging.yaml
@@ -0,0 +1,103 @@
+Description: MSK Cluster with required properties.
+Resources:
+ ClusterBadNoLoggingInfo:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ Tags:
+ - Key: yor_trace
+ Value: "mock_trace"
+ ClusterBadNoLoggingDisabled:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: false
+ ClusterCloudWatchLogsGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: true
+ ClusterFirehoseGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ Firehose:
+ Enabled: true
+ Clusters3Good:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ S3:
+ Enabled: true
+ ClusterAllGood:
+ Type: 'AWS::MSK::Cluster'
+ Properties:
+ ClusterName: ClusterWithRequiredProperties
+ KafkaVersion: 2.2.1
+ NumberOfBrokerNodes: 3
+ BrokerNodeGroupInfo:
+ InstanceType: kafka.m5.large
+ ClientSubnets:
+ - ReplaceWithSubnetId1
+ - ReplaceWithSubnetId2
+ - ReplaceWithSubnetId3
+ LoggingInfo:
+ BrokerLogs:
+ CloudWatchLogs:
+ Enabled: true
+ Firehose:
+ Enabled: true
+ S3:
+ Enabled: true
+
+
diff --git a/tests/cloudformation/graph/graph_runner/resources/SagemakerNotebookEncryption.yaml b/tests/cloudformation/graph/graph_runner/resources/SagemakerNotebookEncryption.yaml
new file mode 100644
index 0000000000..aeb525c7a7
--- /dev/null
+++ b/tests/cloudformation/graph/graph_runner/resources/SagemakerNotebookEncryption.yaml
@@ -0,0 +1,40 @@
+Description: "Basic NotebookInstance test update to a different instance type"
+Resources:
+ BasicNotebookInstanceBad:
+ Type: "AWS::SageMaker::NotebookInstance"
+ Properties:
+ InstanceType: "ml.t2.large"
+ RoleArn: !GetAtt ExecutionRole.Arn
+ BasicNotebookInstanceGood:
+ Type: "AWS::SageMaker::NotebookInstance"
+ Properties:
+ InstanceType: "ml.t2.large"
+ RoleArn: !GetAtt ExecutionRole.Arn
+ KmsKeyId: "test_kms_key"
+ ExecutionRole:
+ Type: "AWS::IAM::Role"
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Principal:
+ Service:
+ - "sagemaker.amazonaws.com"
+ Action:
+ - "sts:AssumeRole"
+ Path: "/"
+ Policies:
+ -
+ PolicyName: "root"
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Action: "*"
+ Resource: "*"
+Outputs:
+ BasicNotebookInstanceId:
+ Value: !Ref BasicNotebookInstance
\ No newline at end of file
diff --git a/tests/cloudformation/graph/graph_runner/test_running_graph_checks.py b/tests/cloudformation/graph/graph_runner/test_running_graph_checks.py
new file mode 100644
index 0000000000..b096ce213e
--- /dev/null
+++ b/tests/cloudformation/graph/graph_runner/test_running_graph_checks.py
@@ -0,0 +1,21 @@
+import itertools
+import os
+import unittest
+from checkov.cloudformation.runner import Runner
+
+
+class TestRunningGraphChecks(unittest.TestCase):
+
+ def test_runner(self):
+ dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), f"resources")
+ report = Runner().run(dir_path)
+ assert any(
+ check.check_id == "CKV2_AWS_24" for check in itertools.chain(report.failed_checks, report.passed_checks))
+ assert any(
+ check.check_id == "CKV2_AWS_25" for check in itertools.chain(report.failed_checks, report.passed_checks))
+ assert any(
+ check.check_id == "CKV2_AWS_26" for check in itertools.chain(report.failed_checks, report.passed_checks))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/cloudformation/parser/cfn_with_ref.yaml b/tests/cloudformation/parser/cfn_with_ref.yaml
new file mode 100644
index 0000000000..1bb8ea12d7
--- /dev/null
+++ b/tests/cloudformation/parser/cfn_with_ref.yaml
@@ -0,0 +1,20 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: "data-stage"
+
+Parameters:
+ ElasticsearchInstanceType:
+ Type: String
+ Default: r5.large.elasticsearch
+
+Resources:
+ ElasticsearchDomain:
+ Type: AWS::Elasticsearch::Domain
+ Properties:
+ DomainName: !Join
+ - "-"
+ - - !Ref AWS::StackName
+ - "20200325"
+ ElasticsearchVersion: !Ref ElasticsearchVersion
+ ElasticsearchClusterConfig:
+ InstanceCount: '1'
+ InstanceType: !Ref ElasticsearchInstanceType
diff --git a/tests/cloudformation/parser/test_cfn_yaml.py b/tests/cloudformation/parser/test_cfn_yaml.py
index f19bb0975b..b616de023a 100644
--- a/tests/cloudformation/parser/test_cfn_yaml.py
+++ b/tests/cloudformation/parser/test_cfn_yaml.py
@@ -17,7 +17,7 @@ def test_skip_parsing(self):
summary = report.get_summary()
self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 0)
+ self.assertEqual(summary['failed'], 2)
self.assertEqual(summary['skipped'], 1)
self.assertEqual(summary['parsing_errors'], 0)
@@ -118,6 +118,19 @@ def test_trim_lines(self):
self.assertEqual(ContextParser.trim_lines(test5), [])
+ def test_parameter_import_lines(self):
+ # check that when a parameter is imported into a resource, the line numbers of the resource are preserved
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file = f'{current_dir}/cfn_with_ref.yaml'
+ definitions, definitions_raw = parse(file)
+
+ cf_context_parser = ContextParser(file, definitions, definitions_raw)
+ resource = definitions['Resources']['ElasticsearchDomain']
+ entity_lines_range, entity_code_lines = cf_context_parser.extract_cf_resource_code_lines(resource)
+ self.assertEqual(entity_lines_range[0], 10)
+ self.assertEqual(entity_lines_range[1], 20)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/cloudformation/runner/resources/tags.yaml b/tests/cloudformation/runner/resources/tags.yaml
new file mode 100644
index 0000000000..5dfafb18b2
--- /dev/null
+++ b/tests/cloudformation/runner/resources/tags.yaml
@@ -0,0 +1,74 @@
+Resources:
+ DataBucket:
+ # Public, not encrypted, no access logs, no versioning
+ Type: AWS::S3::Bucket
+ DeletionPolicy: Delete
+ Properties:
+ BucketName: !Sub "${AWS::AccountId}-data"
+ Tags:
+ - Key: Simple
+ Value: Value
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-data"
+ - Key: Environment
+ Value: !Sub
+ - long-form-sub-${account}
+ - account: test
+ - Key: Account
+ Value: !Sub
+ - long-form-sub-${account}
+ - { account: !Ref AWS::AccountId }
+ NoTags:
+ # Public, not encrypted, no access logs, no versioning
+ Type: AWS::S3::Bucket
+ DeletionPolicy: Delete
+ Properties:
+ BucketName: !Sub "${AWS::AccountId}-notags"
+ EKSClusterNodegroup:
+ Type: AWS::EKS::Nodegroup
+ Properties:
+ ClusterName: !Ref ClusterName
+ NodegroupName: !Ref NodeGroupName
+ NodeRole: !GetAtt EKSNodegroupIAMRole.Arn
+ InstanceTypes: !Ref NodeGroupInstanceType
+ ScalingConfig:
+ MinSize: !Ref NodeGroupAmount
+ DesiredSize: !Ref NodeGroupAmount
+ MaxSize: !Ref NodeGroupAmount
+ Subnets: !Ref VPCSubnets
+ Tags:
+ Name: !Join ["-", [!Ref ClusterName, EKS, !Ref NodeGroupName]]
+ TerraformServerAutoScalingGroup:
+ Type: AWS::AutoScaling::AutoScalingGroup
+ Properties:
+ LaunchConfigurationName: !Ref TerraformServerAutoScalingLaunchConfig
+ MaxSize: 20
+ MinSize: 1
+ DesiredCapacity: !Ref WrapperServerCount
+ VPCZoneIdentifier:
+ - !If
+ - CreateVpc
+ - !If
+ - CreatePrivateSubnet
+ - !Ref TerraformPrivateSubnet
+ - !Ref TerraformPublicSubnet
+ - !Ref Subnet
+ Tags:
+ - Key: Name
+ Value: TF-FulfillmentServer
+ PropagateAtLaunch: true
+ - Key: terraform-server-tag-key
+ Value: terraform-server-tag-value
+ PropagateAtLaunch: true
+ - !If
+ - CreateVpc
+ - Key: PublicRouteDependency
+ Value: !Ref TerraformPublicGatewayRoute
+ PropagateAtLaunch: false
+ - !Ref AWS::NoValue
+ - !If
+ - CreatePrivateSubnet
+ - Key: PrivateRouteDependency
+ Value: !Ref TerraformNatGatewayRoute
+ PropagateAtLaunch: false
+ - !Ref AWS::NoValue
diff --git a/tests/cloudformation/runner/test_runner.py b/tests/cloudformation/runner/test_runner.py
index 9fc27746d3..a5cdf4bb01 100644
--- a/tests/cloudformation/runner/test_runner.py
+++ b/tests/cloudformation/runner/test_runner.py
@@ -1,8 +1,14 @@
+import dis
+import inspect
import os
import unittest
+from pathlib import Path
+from checkov.cloudformation import cfn_utils
+from checkov.cloudformation.parser import parse
from checkov.runner_filter import RunnerFilter
from checkov.cloudformation.runner import Runner
+from checkov.common.output.report import Report
class TestRunnerValid(unittest.TestCase):
@@ -24,7 +30,7 @@ def test_record_relative_path_with_relative_dir(self):
runner_filter=RunnerFilter(framework='cloudformation', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -47,7 +53,7 @@ def test_record_relative_path_with_abs_dir(self):
runner_filter=RunnerFilter(framework='cloudformation', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -69,7 +75,7 @@ def test_record_relative_path_with_relative_file(self):
runner_filter=RunnerFilter(framework='cloudformation', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
@@ -91,11 +97,134 @@ def test_record_relative_path_with_abs_file(self):
runner_filter=RunnerFilter(framework='cloudformation', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
+ def test_get_tags(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ scan_file_path = os.path.join(current_dir, "resources", "tags.yaml")
+
+ definitions, _ = parse(scan_file_path)
+
+ resource_name = 'DataBucket'
+ resource = definitions['Resources'][resource_name]
+ entity = {resource_name: resource}
+ entity_tags = cfn_utils.get_resource_tags(entity)
+
+ self.assertEqual(len(entity_tags), 4)
+ tags = {
+ 'Simple': 'Value',
+ 'Name': '${AWS::AccountId}-data',
+ 'Environment': 'long-form-sub-${account}',
+ 'Account': 'long-form-sub-${account}'
+ }
+
+ for name, value in tags.items():
+ self.assertEqual(entity_tags[name], value)
+
+ resource_name = 'NoTags'
+ resource = definitions['Resources'][resource_name]
+ entity = {resource_name: resource}
+ entity_tags = cfn_utils.get_resource_tags(entity)
+
+ self.assertIsNone(entity_tags)
+
+ 'TerraformServerAutoScalingGroup'
+ resource_name = 'TerraformServerAutoScalingGroup'
+ resource = definitions['Resources'][resource_name]
+ entity = {resource_name: resource}
+ entity_tags = cfn_utils.get_resource_tags(entity)
+
+ self.assertIsNone(entity_tags)
+
+ resource_name = 'EKSClusterNodegroup'
+ resource = definitions['Resources'][resource_name]
+ entity = {resource_name: resource}
+ entity_tags = cfn_utils.get_resource_tags(entity)
+
+ self.assertEqual(len(entity_tags), 1)
+ tags = {
+ 'Name': '{\'Ref\': \'ClusterName\'}-EKS-{\'Ref\': \'NodeGroupName\'}'
+ }
+
+ for name, value in tags.items():
+ self.assertEqual(entity_tags[name], value)
+
+ def test_wrong_check_imports(self):
+ wrong_imports = ["arm", "dockerfile", "helm", "kubernetes", "serverless", "terraform"]
+ ignore_files = ["BaseCloudsplainingIAMCheck.py"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ if file.name in ignore_files:
+ continue
+
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
+ def test_run_graph_checks(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ scan_dir_path = os.path.join(current_dir, "../graph/checks/resources/MSKClusterLogging")
+
+
+ dir_abs_path = os.path.abspath(scan_dir_path)
+
+ runner = Runner()
+ report = runner.run(root_folder=dir_abs_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='cloudformation', download_external_modules=False))
+
+ def test_external_data(self):
+ dir_abs_path = os.path.dirname(os.path.realpath(__file__))
+
+ definitions = {
+ f'{dir_abs_path}/s3.yaml': {
+ 'Resources': {
+ 'MySourceQueue': {
+ 'Type': 'AWS::SQS::Queue',
+ 'Properties': {
+ 'KmsMasterKeyId': 'kms_id',
+ '__startline__': 17,
+ '__endline__': 22,
+ 'resource_type': 'AWS::SQS::Queue'
+ }
+ },
+ 'MyDB': {
+ 'Type': 'AWS::RDS::DBInstance',
+ 'Properties': {
+ 'DBName': 'db',
+ 'DBInstanceClass': 'db.t3.micro',
+ 'Engine': 'mysql',
+ 'MasterUsername': 'master',
+ 'MasterUserPassword': 'password',
+ '__startline__': 23,
+ '__endline__': 32,
+ 'resource_type': 'AWS::RDS::DBInstance'
+ }
+ }
+ }
+ }
+ }
+ context = {f'{dir_abs_path}/s3.yaml': {'Parameters': {'KmsMasterKeyId': {'start_line': 5, 'end_line': 9, 'code_lines': [(5, ' "KmsMasterKeyId": {\n'), (6, ' "Description": "Company Name",\n'), (7, ' "Type": "String",\n'), (8, ' "Default": "kms_id"\n'), (9, ' },\n')]}, 'DBName': {'start_line': 10, 'end_line': 14, 'code_lines': [(10, ' "DBName": {\n'), (11, ' "Description": "Name of the Database",\n'), (12, ' "Type": "String",\n'), (13, ' "Default": "db"\n'), (14, ' }\n')]}}, 'Resources': {'MySourceQueue': {'start_line': 17, 'end_line': 22, 'code_lines': [(17, ' "MySourceQueue": {\n'), (18, ' "Type": "AWS::SQS::Queue",\n'), (19, ' "Properties": {\n'), (20, ' "KmsMasterKeyId": { "Ref": "KmsMasterKeyId" }\n'), (21, ' }\n'), (22, ' },\n')], 'skipped_checks': []}, 'MyDB': {'start_line': 23, 'end_line': 32, 'code_lines': [(23, ' "MyDB": {\n'), (24, ' "Type": "AWS::RDS::DBInstance",\n'), (25, ' "Properties": {\n'), (26, ' "DBName": { "Ref": "DBName" },\n'), (27, ' "DBInstanceClass": "db.t3.micro",\n'), (28, ' "Engine": "mysql",\n'), (29, ' "MasterUsername": "master",\n'), (30, ' "MasterUserPassword": "password"\n'), (31, ' }\n'), (32, ' }\n')], 'skipped_checks': []}}, 'Outputs': {'DBAppPublicDNS': {'start_line': 35, 'end_line': 38, 'code_lines': [(35, ' "DBAppPublicDNS": {\n'), (36, ' "Description": "DB App Public DNS Name",\n'), (37, ' "Value": { "Fn::GetAtt" : [ "MyDB", "PublicDnsName" ] }\n'), (38, ' }\n')]}}}}
+ breadcrumbs = {}
+ runner = Runner()
+ runner.set_external_data(definitions, context, breadcrumbs)
+ report = Report('cloudformation')
+ runner.check_definitions(root_folder=dir_abs_path, runner_filter=RunnerFilter(framework='cloudformation', download_external_modules=False), report=report)
+ self.assertEqual(len(report.passed_checks), 2)
+ self.assertEqual(len(report.failed_checks), 3)
+ pass
+
def tearDown(self):
pass
diff --git a/tests/cloudformation/test_graph_manager.py b/tests/cloudformation/test_graph_manager.py
new file mode 100644
index 0000000000..bd4810ee18
--- /dev/null
+++ b/tests/cloudformation/test_graph_manager.py
@@ -0,0 +1,87 @@
+import os
+from unittest import TestCase
+
+from checkov.cloudformation.graph_builder.graph_components.block_types import BlockType
+from checkov.cloudformation.graph_manager import CloudformationGraphManager
+from checkov.cloudformation.parser import parse
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestCloudformationGraphManager(TestCase):
+ def test_build_graph_from_source_directory_no_rendering(self):
+ root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, "./runner/resources"))
+ graph_manager = CloudformationGraphManager(db_connector=NetworkxConnector())
+ local_graph, definitions = graph_manager.build_graph_from_source_directory(root_dir, render_variables=False)
+
+ expected_resources_by_file = {
+ os.path.join(root_dir, "tags.yaml"): [
+ "AWS::S3::Bucket.DataBucket",
+ "AWS::S3::Bucket.NoTags",
+ "AWS::EKS::Nodegroup.EKSClusterNodegroup",
+ "AWS::AutoScaling::AutoScalingGroup.TerraformServerAutoScalingGroup"],
+ os.path.join(root_dir, "cfn_newline_at_end.yaml"): [
+ "AWS::RDS::DBInstance.MyDB",
+ "AWS::S3::Bucket.MyBucket"],
+ os.path.join(root_dir, "success.json"): [
+ "AWS::S3::Bucket.acmeCWSBucket",
+ "AWS::S3::Bucket.acmeCWSBucket2",
+ "AWS::S3::BucketPolicy.acmeCWSBucketPolicy",
+ "AWS::SNS::Topic.acmeCWSTopic",
+ "AWS::SNS::TopicPolicy.acmeCWSTopicPolicy",
+ "AWS::CloudTrail::Trail.acmeCWSTrail",
+ "AWS::KMS::Key.CloudtrailKMSKey",
+ "AWS::KMS::Alias.CloudtrailKMSKeyAlias",
+ "AWS::SQS::Queue.acmeCWSQueue",
+ "AWS::SQS::QueuePolicy.acmeCWSQueuePolicy",
+ "AWS::SNS::Subscription.acmeCWSSubscription",
+ "AWS::IAM::Role.acmeCWSSACrossAccountAccessRole",
+ "AWS::EKS::Cluster.eksCluster",
+ "Custom::acmeSnsCustomResource.acmeSnsCustomResource",
+ ],
+ }
+ self.assertEqual(40, len(local_graph.vertices))
+ self.assertEqual(20, len(local_graph.vertices_by_block_type[BlockType.RESOURCE]))
+ self.assertEqual(9, len(local_graph.vertices_by_block_type[BlockType.PARAMETER]))
+ self.assertEqual(6, len(local_graph.vertices_by_block_type[BlockType.OUTPUT]))
+ self.assertEqual(4, len(local_graph.vertices_by_block_type[BlockType.CONDITION]))
+ self.assertEqual(1, len(local_graph.vertices_by_block_type[BlockType.MAPPING]))
+
+ for v in local_graph.vertices:
+ if v.block_type == BlockType.RESOURCE:
+ self.assertIn(v.name, expected_resources_by_file[v.path])
+
+ sqs_queue_vertex = local_graph.vertices[local_graph.vertices_block_name_map[BlockType.RESOURCE]["AWS::SQS::Queue.acmeCWSQueue"][0]]
+ self.assertDictEqual({'Fn::Join': ['', [{'Ref': 'ResourceNamePrefix', '__startline__': 650, '__endline__': 652}, '-acmecws']], '__startline__': 646, '__endline__': 656}, sqs_queue_vertex.attributes["QueueName"])
+
+ def test_build_graph_from_source_directory_with_rendering(self):
+ root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, "./runner/resources"))
+ graph_manager = CloudformationGraphManager(db_connector=NetworkxConnector())
+ local_graph, definitions = graph_manager.build_graph_from_source_directory(root_dir, render_variables=True)
+
+ sqs_queue_vertex = local_graph.vertices[local_graph.vertices_block_name_map[BlockType.RESOURCE]["AWS::SQS::Queue.acmeCWSQueue"][0]]
+ expected_node = {'Fn::Join': ['', ['acme', '-acmecws']], '__startline__': 646, '__endline__': 656}
+ self.assertDictEqual(expected_node, sqs_queue_vertex.config["QueueName"])
+ found = False
+ for d in definitions:
+ if 'resources/success.json' in d:
+ found = True
+ node = definitions[d]['Resources']['acmeCWSQueue']['Properties']['QueueName']
+ self.assertDictEqual(expected_node, node)
+ self.assertTrue(found, 'Did not find the wanted node, for acmeCWSQueue')
+
+ def test_build_graph_from_definitions(self):
+ relative_file_path = "./checks/resource/aws/example_APIGatewayXray/APIGatewayXray-PASSED.yaml"
+ definitions = {}
+ file = os.path.realpath(os.path.join(TEST_DIRNAME, relative_file_path))
+ (definitions[relative_file_path], definitions_raw) = parse(file)
+ graph_manager = CloudformationGraphManager(db_connector=NetworkxConnector())
+ local_graph = graph_manager.build_graph_from_definitions(definitions)
+ self.assertEqual(1, len(local_graph.vertices))
+ resource_vertex = local_graph.vertices[0]
+ self.assertEqual("AWS::ApiGateway::Stage.MyStage", resource_vertex.name)
+ self.assertEqual("AWS::ApiGateway::Stage.MyStage", resource_vertex.id)
+ self.assertEqual(BlockType.RESOURCE, resource_vertex.block_type)
+ self.assertEqual("CloudFormation", resource_vertex.source)
+ self.assertDictEqual(definitions[relative_file_path]["Resources"]["MyStage"]["Properties"], resource_vertex.attributes)
diff --git a/tests/cloudformation/utils/__init__.py b/tests/cloudformation/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/cloudformation/utils/file_formats/test.json b/tests/cloudformation/utils/file_formats/test.json
new file mode 100644
index 0000000000..71bf7ab4c9
--- /dev/null
+++ b/tests/cloudformation/utils/file_formats/test.json
@@ -0,0 +1,40 @@
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description": "AWS CloudFormation Template to deploy insecure infrastructure",
+ "Parameters": {
+ "KmsMasterKeyId": {
+ "Description": "Company Name",
+ "Type": "String",
+ "Default": "kms_id"
+ },
+ "DBName": {
+ "Description": "Name of the Database",
+ "Type": "String",
+ "Default": "db"
+ }
+ },
+ "Resources": {
+ "MySourceQueue": {
+ "Type": "AWS::SQS::Queue",
+ "Properties": {
+ "KmsMasterKeyId": { "Ref": "KmsMasterKeyId" }
+ }
+ },
+ "MyDB": {
+ "Type": "AWS::RDS::DBInstance",
+ "Properties": {
+ "DBName": { "Ref": "DBName" },
+ "DBInstanceClass": "db.t3.micro",
+ "Engine": "mysql",
+ "MasterUsername": "master",
+ "MasterUserPassword": "password"
+ }
+ }
+ },
+ "Outputs": {
+ "DBAppPublicDNS": {
+ "Description": "DB App Public DNS Name",
+ "Value": { "Fn::GetAtt" : [ "MyDB", "PublicDnsName" ] }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/utils/file_formats/test.yaml b/tests/cloudformation/utils/file_formats/test.yaml
new file mode 100644
index 0000000000..8aa5594a62
--- /dev/null
+++ b/tests/cloudformation/utils/file_formats/test.yaml
@@ -0,0 +1,30 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: AWS CloudFormation Template to deploy insecure infrastructure
+Parameters:
+ KmsMasterKeyId:
+ Description: Company Name
+ Type: String
+ Default: kms_id
+ DBName:
+ Description: Name of the Database
+ Type: String
+ Default: db1
+Resources:
+ MySourceQueue:
+ Type: AWS::SQS::Queue
+ Properties:
+ KmsMasterKeyId: !Ref KmsMasterKeyId
+ MyDB:
+ Type: 'AWS::RDS::DBInstance'
+ # Test case for check skip via comment
+ # checkov:skip=CKV_AWS_16:Ensure all data stored in the RDS is securely encrypted at rest
+ Properties:
+ DBName: !Ref DBName
+ DBInstanceClass: 'db.t3.micro'
+ Engine: 'mysql'
+ MasterUsername: 'master'
+ MasterUserPassword: 'password'
+Outputs:
+ DBAppPublicDNS:
+ Description: DB App Public DNS Name
+ Value: !GetAtt [ MyDB, PublicDnsName ]
diff --git a/tests/cloudformation/utils/file_formats/test2.yaml b/tests/cloudformation/utils/file_formats/test2.yaml
new file mode 100644
index 0000000000..c99ebcaee8
--- /dev/null
+++ b/tests/cloudformation/utils/file_formats/test2.yaml
@@ -0,0 +1,206 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: AWS CloudFormation Template to deploy insecure infrastructure
+Parameters:
+ LatestAmiId:
+ Type: AWS::SSM::Parameter::Value
+ Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
+#test
+Resources:
+ ####################
+ ### EC2 in VPC ###
+ ####################
+ WebHostStorage:
+ # Unencrypted Volume
+ Type: AWS::EC2::Volume
+ Properties:
+ AvailabilityZone: !Select
+ - 0
+ - Fn::GetAZs: ""
+ #Encrypted: False
+ Size: 1
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-ebs"
+
+ #############
+ ### KMS ###
+ #############
+
+ LogsKey:
+ # Key does not have rotation enabled
+ Type: AWS::KMS::Key
+ Properties:
+ Description: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-logs bucket key"
+ PendingWindowInDays: 7
+ KeyPolicy:
+ Version: '2012-10-17'
+ Id: key-default-1
+ Statement:
+ - Sid: Enable IAM User Permissions
+ Effect: Allow
+ Principal:
+ AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
+ Action: kms:*
+ Resource: '*'
+
+ LogsKeyAlias:
+ Type: AWS::KMS::Alias
+ Properties:
+ AliasName: !Sub "alias/${AWS::AccountId}-${CompanyName}-${Environment}-logs-bucket-key"
+ TargetKeyId: !Ref LogsKey
+
+ DBAppInstance:
+ # EC2 have plain text secrets in user data
+ Type: AWS::EC2::Instance
+ Properties:
+ AvailabilityZone:
+ Fn::Select:
+ - 0
+ - Fn::GetAZs: ""
+ ImageId: !Ref LatestAmiId
+ InstanceType: t2.nano
+ IamInstanceProfile: !Ref EC2Profile
+ SecurityGroupIds:
+ - !Ref WebNodeSG
+ SubnetId: !Ref WebSubnet
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-dbapp"
+ UserData:
+ Fn::Base64: !Sub |
+ #!/bin/bash
+ ### Config from https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Tutorials.WebServerDB.CreateWebServer.html
+ sudo yum -y update
+ sudo yum -y install httpd php php-mysqlnd
+ sudo systemctl enable httpd
+ sudo systemctl start httpd
+ sudo mkdir /var/www/inc
+ cat << EnD > /tmp/dbinfo.inc
+
+ EnD
+ sudo mv /tmp/dbinfo.inc /var/www/inc
+ sudo chown root:root /var/www/inc/dbinfo.inc
+ cat << EnD > /tmp/index.php
+
+
+
+ Sample page
+
+
+
+
+
+
+ ID
+ NAME
+ ADDRESS
+
+ ";
+ echo "",$query_data[0], " ",
+ "",$query_data[1], " ",
+ "",$query_data[2], " ";
+ echo "";
+ }
+ ?>
+
+
+
+
+
+ Error adding employee data.
");
+ }
+ /* Check whether the table exists and, if not, create it. */
+ function VerifyEmployeesTable($connection, $dbName) {
+ if(!TableExists("EMPLOYEES", $connection, $dbName))
+ {
+ $query = "CREATE TABLE EMPLOYEES (
+ ID int(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(45),
+ ADDRESS VARCHAR(90)
+ )";
+ if(!mysqli_query($connection, $query)) echo("Error creating table.
");
+ }
+ }
+ /* Check for the existence of a table. */
+ function TableExists($tableName, $connection, $dbName) {
+ $t = mysqli_real_escape_string($connection, $tableName);
+ $d = mysqli_real_escape_string($connection, $dbName);
+ $checktable = mysqli_query($connection,
+ "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = '$t' AND TABLE_SCHEMA = '$d'");
+ if(mysqli_num_rows($checktable) > 0) return true;
+ return false;
+ }
+ ?>
+ EnD
+ sudo mv /tmp/index.php /var/www/html
+ sudo chown root:root /var/www/html/index.php
+
+Outputs:
+ EC2PublicDNS:
+ # test comment
+ # test comment2
+ Description: Web Host Public DNS Name
+ Value: !GetAtt [EC2Instance, PublicDnsName]
+ VpcId:
+ Description: The ID of the VPC
+ # test comment
+ Value: !Ref WebVPC
+ PublicSubnet:
+ Description: The ID of the Public Subnet
+ Value: !Ref WebSubnet
+ # test comment
+ PublicSubnet2:
+ Description: The ID of the Public Subnet
+ Value: !Ref WebSubnet2
+
+ UserName:
+ Description: The Name of the IAM User
+ Value: !Ref User
diff --git a/tests/cloudformation/utils/test_cfn_utils.py b/tests/cloudformation/utils/test_cfn_utils.py
new file mode 100644
index 0000000000..4504467d09
--- /dev/null
+++ b/tests/cloudformation/utils/test_cfn_utils.py
@@ -0,0 +1,105 @@
+import os
+import unittest
+
+from checkov.cloudformation.cfn_utils import get_folder_definitions, build_definitions_context
+from checkov.cloudformation.parser.node import dict_node
+from checkov.cloudformation.parser import TemplateSections
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+RELATIVE_PATH = 'file_formats'
+
+
+class TestCfnUtils(unittest.TestCase):
+
+ def setUp(self):
+ self.test_root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, RELATIVE_PATH))
+
+ definitions, definitions_raw = get_folder_definitions(self.test_root_dir, None)
+ self.definitions_context = build_definitions_context(definitions, definitions_raw, self.test_root_dir)
+
+ def validate_definition_lines(self, definition: dict_node, start_line, end_line, code_lines):
+ self.assertEqual(definition['start_line'], start_line)
+ self.assertEqual(definition['end_line'], end_line)
+ self.assertEqual(len(definition['code_lines']), code_lines)
+
+ def test_parameters_value(self):
+ # Asserting test.yaml file
+ yaml_parameters = self.definitions_context[os.path.join(self.test_root_dir, 'test.yaml')][
+ TemplateSections.PARAMETERS.value]
+ self.assertIsNotNone(yaml_parameters)
+ self.assertEqual(len(yaml_parameters), 2)
+ self.validate_definition_lines(yaml_parameters['KmsMasterKeyId'], 4, 7, 4)
+ self.validate_definition_lines(yaml_parameters['DBName'], 8, 11, 4)
+ # Asserting test2.yaml file
+ yaml2_parameters = self.definitions_context[os.path.join(self.test_root_dir, 'test2.yaml')][
+ TemplateSections.PARAMETERS.value]
+ self.assertIsNotNone(yaml2_parameters)
+ self.assertEqual(len(yaml2_parameters), 1)
+ self.validate_definition_lines(yaml2_parameters['LatestAmiId'], 4, 6, 3)
+ # Asserting json file
+ json_parameters = self.definitions_context[os.path.join(self.test_root_dir, 'test.json')][
+ TemplateSections.PARAMETERS.value]
+ self.assertIsNotNone(json_parameters)
+ self.assertEqual(len(json_parameters), 2)
+ self.validate_definition_lines(json_parameters['KmsMasterKeyId'], 5, 9, 5)
+ self.validate_definition_lines(json_parameters['DBName'], 10, 14, 5)
+
+ def test_resources_value(self):
+ # Asserting test.yaml file
+ yaml_resources = self.definitions_context[os.path.join(self.test_root_dir, 'test.yaml')][
+ TemplateSections.RESOURCES.value]
+ self.assertIsNotNone(yaml_resources)
+ self.assertEqual(len(yaml_resources), 2)
+ self.validate_definition_lines(yaml_resources['MySourceQueue'], 13, 16, 4)
+ self.validate_definition_lines(yaml_resources['MyDB'], 17, 26, 10)
+ # Asserting test2.yaml file
+ yaml2_resources = self.definitions_context[os.path.join(self.test_root_dir, 'test2.yaml')][
+ TemplateSections.RESOURCES.value]
+ self.assertIsNotNone(yaml2_resources)
+ self.assertEqual(len(yaml2_resources), 4)
+ self.validate_definition_lines(yaml2_resources['WebHostStorage'], 12, 23, 12)
+ self.validate_definition_lines(yaml2_resources['LogsKey'], 29, 44, 16)
+ self.validate_definition_lines(yaml2_resources['LogsKeyAlias'], 46, 50, 5)
+ self.validate_definition_lines(yaml2_resources['DBAppInstance'], 52, 184, 133)
+ # Asserting json file
+ json_resources = self.definitions_context[os.path.join(self.test_root_dir, 'test.json')][
+ TemplateSections.RESOURCES.value]
+ self.assertIsNotNone(json_resources)
+ self.assertEqual(len(json_resources), 2)
+ self.validate_definition_lines(json_resources['MySourceQueue'], 17, 22, 6)
+ self.validate_definition_lines(json_resources['MyDB'], 23, 32, 10)
+
+ def test_outputs_value(self):
+ # Asserting test.yaml file
+ yaml_outputs = self.definitions_context[os.path.join(self.test_root_dir, 'test.yaml')][
+ TemplateSections.OUTPUTS.value]
+ self.assertIsNotNone(yaml_outputs)
+ self.assertEqual(len(yaml_outputs), 1)
+ self.validate_definition_lines(yaml_outputs['DBAppPublicDNS'], 28, 30, 3)
+ # Asserting test2.yaml file
+ yaml2_outputs = self.definitions_context[os.path.join(self.test_root_dir, 'test2.yaml')][
+ TemplateSections.OUTPUTS.value]
+ self.assertIsNotNone(yaml2_outputs)
+ self.assertEqual(len(yaml2_outputs), 5)
+ self.validate_definition_lines(yaml2_outputs['EC2PublicDNS'], 187, 191, 5)
+ self.validate_definition_lines(yaml2_outputs['VpcId'], 192, 195, 4)
+ self.validate_definition_lines(yaml2_outputs['PublicSubnet'], 196, 198, 3)
+ self.validate_definition_lines(yaml2_outputs['PublicSubnet2'], 200, 202, 3)
+ self.validate_definition_lines(yaml2_outputs['UserName'], 204, 206, 3)
+ # Asserting json file
+ json_outputs = self.definitions_context[os.path.join(self.test_root_dir, 'test.json')][
+ TemplateSections.OUTPUTS.value]
+ self.assertIsNotNone(json_outputs)
+ self.assertEqual(len(json_outputs), 1)
+ self.validate_definition_lines(json_outputs['DBAppPublicDNS'], 35, 38, 4)
+
+ def test_skipped_check_exists(self):
+ skipped_checks = self.definitions_context[os.path.join(self.test_root_dir, 'test.yaml')][
+ TemplateSections.RESOURCES.value]['MyDB']['skipped_checks']
+ self.assertEqual(len(skipped_checks), 1)
+ self.assertEqual(skipped_checks[0]['id'], 'CKV_AWS_16')
+ self.assertEqual(skipped_checks[0]['suppress_comment'],
+ 'Ensure all data stored in the RDS is securely encrypted at rest\\n\')')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/checks/test_base_check.py b/tests/common/checks/test_base_check.py
index 965faf020b..e1290e4a2e 100644
--- a/tests/common/checks/test_base_check.py
+++ b/tests/common/checks/test_base_check.py
@@ -6,6 +6,8 @@
class TestCheckTypeNotInSignature(BaseCheck):
+ # for pytest not to collect this class as tests
+ __test__ = False
def __init__(self):
name = "Example check"
diff --git a/tests/common/checks/test_base_check_registry.py b/tests/common/checks/test_base_check_registry.py
index 898bbf833b..21ac1bcfa0 100644
--- a/tests/common/checks/test_base_check_registry.py
+++ b/tests/common/checks/test_base_check_registry.py
@@ -5,6 +5,8 @@
class TestCheck(BaseCheck):
+ # for pytest not to collect this class as tests
+ __test__ = False
def __init__(self, *supported_entities, id="CKV_T_1"):
name = "Example check"
diff --git a/tests/common/goget/test_goget_base.py b/tests/common/goget/test_goget_base.py
index 6fceebcf63..1801a94e1c 100644
--- a/tests/common/goget/test_goget_base.py
+++ b/tests/common/goget/test_goget_base.py
@@ -12,7 +12,7 @@ def test_directory_creation(self):
result_dir = getter.get()
print(current_dir)
print(result_dir)
- self.assertTrue(current_dir in result_dir)
+ self.assertIn(current_dir, result_dir)
# Cleanup
os.rmdir(getter.temp_dir)
diff --git a/tests/common/goget/test_goget_github.py b/tests/common/goget/test_goget_github.py
new file mode 100644
index 0000000000..186e4bf771
--- /dev/null
+++ b/tests/common/goget/test_goget_github.py
@@ -0,0 +1,73 @@
+import os
+import unittest
+
+from checkov.common.goget.github.get_git import GitGetter
+
+
+class TestGitGetter(unittest.TestCase):
+
+ def test_parse_source(self):
+ url = "https://my-git.com/repository-name/"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("https://my-git.com/repository-name/", git_url, "Parsed source url should contain hostname and path")
+ self.assertEqual("", subdir, "Parsed source subdirectory should be empty")
+
+ def test_parse_source_and_subdirectory(self):
+ url = "https://my-git.com/repository-name.git//sub/path"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("https://my-git.com/repository-name.git", git_url, "Parsed source url should contain hostname and path")
+ self.assertEqual("/sub/path", subdir, "Parsed source subdirectory should contain absolute (sub)path")
+
+ def test_parse_source_and_subdirectory_without_git(self):
+ url = "https://my-git.com/repository-name//sub/path"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("https://my-git.com/repository-name", git_url, "Parsed source url should contain hostname and path")
+ self.assertEqual("/sub/path", subdir, "Parsed source subdirectory should contain absolute (sub)path")
+
+ def test_parse_source_with_query(self):
+ url = "https://my-git.com/repository-name?key=value"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("https://my-git.com/repository-name?key=value", git_url, "Parsed source url should contain hostname, path and query")
+ self.assertEqual("", subdir, "Parsed source subdirectory should be empty")
+
+ def test_parse_source_and_subdirectory_with_query(self):
+ url = "https://my-git.com/repository-name//sub/path?key=value"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("https://my-git.com/repository-name?key=value", git_url, "Parsed source url should contain hostname, path and query")
+ self.assertEqual("/sub/path", subdir, "Parsed source subdirectory should contain absolute (sub)path")
+
+ def test_parse_source_without_scheme(self):
+ url = "my-git.com/repository-name"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("my-git.com/repository-name", git_url, "Parsed source url should contain hostname and path")
+ self.assertEqual("", subdir, "Parsed source subdirectory should be empty")
+
+ def test_parse_source_and_subdirectory_without_scheme(self):
+ url = "my-git.com/repository-name//sub/path"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("my-git.com/repository-name", git_url, "Parsed source url should contain hostname ane path")
+ self.assertEqual("/sub/path", subdir, "Parsed source subdirectory should contain absolute (sub)path")
+
+ def test_parse_source_with_query_without_scheme(self):
+ url = "my-git.com/repository-name?key=value"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("my-git.com/repository-name?key=value", git_url, "Parsed source url should contain hostname, path and query")
+ self.assertEqual("", subdir, "Parsed source subdirectory should be empty")
+
+ def test_parse_source_and_subdirectory_with_query_without_scheme(self):
+ url = "my-git.com/repository-name//sub/path?key=value"
+ getter = GitGetter(url)
+ git_url, subdir = getter._source_subdir()
+ self.assertEqual("my-git.com/repository-name?key=value", git_url, "Parsed source url should contain hostname, path and query")
+ self.assertEqual("/sub/path", subdir, "Parsed source subdirectory should contain absolute (sub)path")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/integration_features/__init__.py b/tests/common/integration_features/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/common/integration_features/example_custom_policy_dir/main.tf b/tests/common/integration_features/example_custom_policy_dir/main.tf
new file mode 100644
index 0000000000..9d453aa8b7
--- /dev/null
+++ b/tests/common/integration_features/example_custom_policy_dir/main.tf
@@ -0,0 +1,4 @@
+
+resource "aws_s3_bucket" "b1" {
+ bucket = "bucket1"
+}
diff --git a/tests/common/integration_features/test_custom_policies_integration.py b/tests/common/integration_features/test_custom_policies_integration.py
new file mode 100644
index 0000000000..7b45690bc1
--- /dev/null
+++ b/tests/common/integration_features/test_custom_policies_integration.py
@@ -0,0 +1,143 @@
+import os
+import unittest
+
+from checkov.common.bridgecrew.integration_features.features.custom_policies_integration import \
+ CustomPoliciesIntegration
+from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.checks_infra.registry import Registry
+from checkov.terraform.runner import Runner
+from checkov.runner_filter import RunnerFilter
+from pathlib import Path
+
+class TestCustomPoliciesIntegration(unittest.TestCase):
+
+ def test_integration_valid(self):
+ instance = BcPlatformIntegration()
+ instance.skip_policy_download = False
+ instance.platform_integration_configured = True
+
+ custom_policies_integration = CustomPoliciesIntegration(instance)
+
+ self.assertTrue(custom_policies_integration.is_valid())
+
+ instance.skip_policy_download = True
+ self.assertFalse(custom_policies_integration.is_valid())
+
+ instance.platform_integration_configured = False
+ self.assertFalse(custom_policies_integration.is_valid())
+
+ instance.skip_policy_download = False
+ self.assertFalse(custom_policies_integration.is_valid())
+
+ def test_policy_load(self):
+ # response from API
+ policies = [
+ {
+ "provider": "AWS",
+ "id": "mikepolicies_AWS_1625063607541",
+ "title": "yaml1",
+ "severity": "MEDIUM",
+ "category": "General",
+ "resourceTypes": [
+ "aws_s3_bucket"
+ ],
+ "accountsData": {
+ "mikeurbanski1/terragoat3": {
+ "amounts": {
+ "CLOSED": 0,
+ "DELETED": 0,
+ "OPEN": 1,
+ "REMEDIATED": 0,
+ "SUPPRESSED": 0
+ },
+ "lastUpdateDate": "2021-06-30T14:33:54.638Z"
+ }
+ },
+ "guideline": "yaml1",
+ "isCustom": True,
+ "conditionQuery": {
+ "or": [
+ {
+ "value": "xyz",
+ "operator": "equals",
+ "attribute": "xyz",
+ "cond_type": "attribute",
+ "resource_types": [
+ "aws_s3_bucket"
+ ]
+ }
+ ]
+ },
+ "benchmarks": {},
+ "createdBy": "mike+policies@bridgecrew.io",
+ "code": "---\nmetadata:\n name: \"yaml1\" #give your custom policy a unique name \n guidelines: \"yaml1\" #add text that explains the configuration the policy looks for, its implications, and how to fix it\n category: \"general\" #choose one: \"general\"/\"elaticsearch\"/\"iam\"/\"kubernetes\"/\"logging\"/\"monitoring\"/\"networking\"/\"public\"/\"secrets\"/\"serverless\"/\"storage\"\n severity: \"medium\" #choose one: \"critical\"/\"high\"/\"medium\"/\"low\"/\"info\"\nscope:\n provider: \"aws\" #choose one: \"aws\"/\"azure\"/\"gcp\"\ndefinition: #define the conditions the policy searches for.\n or:\n - cond_type: \"attribute\"\n resource_types:\n - \"aws_s3_bucket\"\n attribute: \"xyz\"\n operator: \"equals\"\n value: \"xyz\"\n# - cond_type: \"attribute\"\n# resource_types:\n# - \"aws_instance\"\n# attribute: \"instance_type\"\n# operator: \"equals\"\n# value: \"t3.nano\"\n"
+ },
+ {
+ "provider": "AWS",
+ "id": "mikepolicies_aws_1625063842021",
+ "title": "ui1",
+ "severity": "HIGH",
+ "category": "General",
+ "resourceTypes": [
+ "aws_s3_bucket"
+ ],
+ "accountsData": {
+ "mikeurbanski1/terragoat3": {
+ "amounts": {
+ "CLOSED": 0,
+ "DELETED": 0,
+ "OPEN": 1,
+ "REMEDIATED": 0,
+ "SUPPRESSED": 0
+ },
+ "lastUpdateDate": "2021-06-30T14:42:29.534Z"
+ }
+ },
+ "guideline": "ui1",
+ "isCustom": True,
+ "conditionQuery": {
+ "value": "abc",
+ "operator": "equals",
+ "attribute": "region",
+ "cond_type": "attribute",
+ "resource_types": [
+ "aws_s3_bucket"
+ ]
+ },
+ "benchmarks": {},
+ "createdBy": "mike+policies@bridgecrew.io",
+ "code": None
+ }
+ ]
+
+ # for this test, we simulate some of the check registry manipulation; otherwise the singleton
+ # instance will be modified and break other tests.
+
+ parser = NXGraphCheckParser()
+
+ registry = Registry(parser=NXGraphCheckParser(), checks_dir=str(
+ Path(__file__).parent.parent.parent.parent / "checkov" / "terraform" / "checks" / "graph_checks"))
+ checks = [parser.parse_raw_check(CustomPoliciesIntegration._convert_raw_check(p)) for p in policies]
+ registry.checks = checks # simulate that the policy downloader will do
+
+ runner = Runner(external_registries=[registry])
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_custom_policy_dir"
+
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter())
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_aws_1625063842021']), 1)
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_AWS_1625063607541']), 1)
+
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=['mikepolicies_aws_1625063842021']))
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_aws_1625063842021']), 1)
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_AWS_1625063607541']), 0)
+
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(skip_checks=['mikepolicies_aws_1625063842021']))
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_aws_1625063842021']), 0)
+ self.assertEqual(len([r for r in report.failed_checks if r.check_id == 'mikepolicies_AWS_1625063607541']), 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/integration_features/test_fixes_integration.py b/tests/common/integration_features/test_fixes_integration.py
new file mode 100644
index 0000000000..dc03ff94ac
--- /dev/null
+++ b/tests/common/integration_features/test_fixes_integration.py
@@ -0,0 +1,30 @@
+import os
+import unittest
+
+from checkov.common.bridgecrew.integration_features.features.fixes_integration import FixesIntegration
+from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
+
+
+class TestFixesIntegration(unittest.TestCase):
+
+ def test_integration_valid(self):
+ instance = BcPlatformIntegration()
+ instance.skip_fixes = False
+ instance.platform_integration_configured = True
+
+ fixes_integration = FixesIntegration(instance)
+
+ self.assertTrue(fixes_integration.is_valid())
+
+ instance.skip_fixes = True
+ self.assertFalse(fixes_integration.is_valid())
+
+ instance.platform_integration_configured = False
+ self.assertFalse(fixes_integration.is_valid())
+
+ instance.skip_fixes = False
+ self.assertFalse(fixes_integration.is_valid())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/integration_features/test_suppressions_integration.py b/tests/common/integration_features/test_suppressions_integration.py
new file mode 100644
index 0000000000..928fa9222b
--- /dev/null
+++ b/tests/common/integration_features/test_suppressions_integration.py
@@ -0,0 +1,407 @@
+import os
+import unittest
+from unittest import mock
+
+from checkov.common.bridgecrew.integration_features.features.suppressions_integration import SuppressionsIntegration
+from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
+from checkov.common.output.record import Record
+
+
+class TestSuppressionsIntegration(unittest.TestCase):
+
+ def test_integration_valid(self):
+ instance = BcPlatformIntegration()
+ instance.skip_suppressions = False
+ instance.platform_integration_configured = True
+
+ suppressions_integration = SuppressionsIntegration(instance)
+
+ self.assertTrue(suppressions_integration.is_valid())
+
+ instance.skip_suppressions = True
+ self.assertFalse(suppressions_integration.is_valid())
+
+ instance.platform_integration_configured = False
+ self.assertFalse(suppressions_integration.is_valid())
+
+ instance.skip_suppressions = False
+ self.assertFalse(suppressions_integration.is_valid())
+
+ def test_policy_id_regex(self):
+ suppressions_integration = SuppressionsIntegration(BcPlatformIntegration())
+
+ matching_ids = [
+ 'bcorg_aws_1234567891011',
+ 'bcORrg_aws_1234567891011',
+ 'bcORrg_AWS_1234567891011',
+ 'bcorg12_aws_1234567891011',
+ 'bcorgabcdefgh_azure_1234567891011'
+ ]
+
+ non_matching_ids = [
+ 'bcorg_aws_123456789101',
+ 'bcorg_aws123_1234567891011',
+ 'bcorg_1234567891011',
+ 'bcorgabcdefghazure_1234567891011',
+ '_bcorg_aws_1234567891011',
+ ]
+
+ for id in matching_ids:
+ self.assertIsNotNone(suppressions_integration.custom_policy_id_regex.match(id))
+
+ for id in non_matching_ids:
+ self.assertIsNone(suppressions_integration.custom_policy_id_regex.match(id))
+
+ def test_suppression_valid(self):
+ instance = BcPlatformIntegration()
+ instance.repo_id = 'org/repo'
+ instance.bc_id_mapping = {
+ 'BC_AWS_1': 'CKV_AWS_20'
+ }
+
+ suppressions_integration = SuppressionsIntegration(instance)
+
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1608816140086,
+ "comment": "No justification comment provided.",
+ "accountIds": [
+ "org/repo"
+ ]
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1608816140086,
+ "comment": "No justification comment provided.",
+ "accountIds": [
+ "bcorg_org/repo"
+ ]
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Resources",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1608816140086,
+ "comment": "No justification comment provided.",
+ "resources": {
+ "accountId": "org/repo",
+ "resourceId": "/s3.tf"
+ }
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Tags",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1610035761349,
+ "comment": "No justification comment provided.",
+ "tags": [
+ {
+ "value": "test_1",
+ "key": "test_num"
+ }
+ ]
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Policy",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1602670330384,
+ "comment": "No justification comment provided."
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1608816140086,
+ "comment": "No justification comment provided.",
+ "accountIds": [
+ "other/repo"
+ ]
+ }
+
+ self.assertFalse(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_1",
+ "creationDate": 1608816140086,
+ "comment": "No justification comment provided.",
+ "accountIds": [
+ "bcorg_other/repo"
+ ]
+ }
+
+ self.assertFalse(suppressions_integration._suppression_valid_for_run(suppression))
+
+ suppression = {
+ "suppressionType": "Tags",
+ "policyId": "NOT_A_POLICY",
+ "creationDate": 1610035761349,
+ "comment": "No justification comment provided.",
+ "tags": [
+ {
+ "value": "test_1",
+ "key": "test_num"
+ }
+ ]
+ }
+
+ self.assertFalse(suppressions_integration._suppression_valid_for_run(suppression))
+
+ # custom policy
+ suppression = {
+ "suppressionType": "Tags",
+ "policyId": "bcorg_aws_1234567891011",
+ "creationDate": 1610035761349,
+ "comment": "No justification comment provided.",
+ "tags": [
+ {
+ "value": "test_1",
+ "key": "test_num"
+ }
+ ]
+ }
+
+ self.assertTrue(suppressions_integration._suppression_valid_for_run(suppression))
+
+ def test_policy_suppression(self):
+ instance = BcPlatformIntegration()
+
+ suppressions_integration = SuppressionsIntegration(instance)
+
+ suppression = {
+ "suppressionType": "Policy",
+ "id": "7caab873-7400-47f9-8b3f-82b33d0463ed",
+ "policyId": "BC_AWS_GENERAL_31",
+ "comment": "No justification comment provided.",
+ "checkovPolicyId": "CKV_AWS_79",
+ }
+
+ record1 = Record(check_id='CKV_AWS_79', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record2 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+
+ def test_account_suppression(self):
+ instance = BcPlatformIntegration()
+ instance.repo_id = 'org/repo'
+ suppressions_integration = SuppressionsIntegration(instance)
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_S3_13",
+ "comment": "testing checkov",
+ "accountIds": ["org/repo", "not/valid"],
+ "checkovPolicyId": "CKV_AWS_18",
+ }
+
+ record1 = Record(check_id='CKV_AWS_18', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record2 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+
+ def test_account_suppression_cli_repo(self):
+ instance = BcPlatformIntegration()
+ instance.repo_id = 'org/repo'
+ suppressions_integration = SuppressionsIntegration(instance)
+ suppression = {
+ "suppressionType": "Accounts",
+ "policyId": "BC_AWS_S3_13",
+ "comment": "testing checkov",
+ "accountIds": ["bcorg_org/repo", "bcorg_not/valid"],
+ "checkovPolicyId": "CKV_AWS_18",
+ }
+
+ record1 = Record(check_id='CKV_AWS_18', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record2 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource=None, evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+
+ def test_resource_suppression(self):
+ instance = BcPlatformIntegration()
+ instance.repo_id = 'org/repo'
+ suppressions_integration = SuppressionsIntegration(instance)
+ suppression = {
+ "suppressionType": "Resources",
+ "policyId": "BC_AWS_S3_13",
+ "comment": "No justification comment provided.",
+ "resources": [
+ {
+ "accountId": "org/repo",
+ "resourceId": "/terraform/aws/s3.tf:aws_s3_bucket.operations",
+ }
+ ],
+ "checkovPolicyId": "CKV_AWS_18",
+ }
+
+ record1 = Record(check_id='CKV_AWS_18', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path=',.', entity_tags=None)
+ record1.repo_file_path = '/terraform/aws/s3.tf'
+ record2 = Record(check_id='CKV_AWS_13', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.no', evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record2.repo_file_path = '/terraform/aws/s3.tf'
+ record3 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record3.repo_file_path = '/terraform/aws/s3.tf'
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record3, suppression))
+
+ def test_resource_suppression_cli_repo(self):
+ instance = BcPlatformIntegration()
+ instance.repo_id = 'org/repo'
+ suppressions_integration = SuppressionsIntegration(instance)
+ suppression = {
+ "suppressionType": "Resources",
+ "policyId": "BC_AWS_S3_13",
+ "comment": "No justification comment provided.",
+ "resources": [
+ {
+ "accountId": "bcorg_org/repo",
+ "resourceId": "/terraform/aws/s3.tf:aws_s3_bucket.operations",
+ }
+ ],
+ "checkovPolicyId": "CKV_AWS_18",
+ }
+
+ record1 = Record(check_id='CKV_AWS_18', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path=',.', entity_tags=None)
+ record1.repo_file_path = '/terraform/aws/s3.tf'
+ record2 = Record(check_id='CKV_AWS_13', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.no', evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record2.repo_file_path = '/terraform/aws/s3.tf'
+ record3 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+ record3.repo_file_path = '/terraform/aws/s3.tf'
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record3, suppression))
+
+ def test_tag_suppression(self):
+ instance = BcPlatformIntegration()
+ suppressions_integration = SuppressionsIntegration(instance)
+ suppression = {
+ "suppressionType": "Tags",
+ "policyId": "BC_AWS_S3_16",
+ "comment": "No justification comment provided.",
+ "tags": [
+ {
+ "value": "value1",
+ "key": "tag1"
+ },
+ {
+ "value": "value2",
+ "key": "tag2"
+ }
+ ],
+ "checkovPolicyId": "CKV_AWS_21",
+ }
+
+ record1 = Record(check_id='CKV_AWS_21', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+ record2 = Record(check_id='CKV_AWS_1', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.no', evaluations=None,
+ check_class=None, file_abs_path='.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+ record3 = Record(check_id='CKV_AWS_21', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path='.',
+ entity_tags={
+ 'tag1': 'value2222',
+ 'tag2': 'value2'
+ })
+ record4 = Record(check_id='CKV_AWS_21', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path='.',
+ entity_tags={
+ 'tag1': 'value2222',
+ 'tag2': 'value1111'
+ })
+ record5 = Record(check_id='CKV_AWS_21', check_name=None, check_result=None,
+ code_block=None, file_path=None,
+ file_line_range=None,
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path='.', entity_tags=None)
+
+ self.assertTrue(suppressions_integration._check_suppression(record1, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record2, suppression))
+ self.assertTrue(suppressions_integration._check_suppression(record3, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record4, suppression))
+ self.assertFalse(suppressions_integration._check_suppression(record5, suppression))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/output/test_get_exit_code.py b/tests/common/output/test_get_exit_code.py
new file mode 100644
index 0000000000..1c228e2d64
--- /dev/null
+++ b/tests/common/output/test_get_exit_code.py
@@ -0,0 +1,97 @@
+import unittest
+
+
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.report import Report
+from checkov.common.output.record import Record
+
+
+class TestGetExitCode(unittest.TestCase):
+
+ def test_get_exit_code(self):
+ record1 = Record(check_id='CKV_AWS_157',
+ bc_check_id='BC_AWS_157',
+ check_name="Some RDS check", check_result={"result": CheckResult.FAILED},
+ code_block=None, file_path="./rds.tf",
+ file_line_range='1:3',
+ resource='aws_db_instance.sample', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+ record2 = Record(check_id='CKV_AWS_16',
+ bc_check_id='BC_AWS_16',
+ check_name="Another RDS check",
+ check_result={"result": CheckResult.FAILED},
+ code_block=None, file_path="./rds.tf",
+ file_line_range='1:3',
+ resource='aws_db_instance.sample', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+
+ record3 = Record(check_id='CKV_AWS_161',
+ bc_check_id='BC_AWS_161',
+ check_name="Another RDS check",
+ check_result={"result": CheckResult.PASSED},
+ code_block=None, file_path="./rds.tf",
+ file_line_range='1:3',
+ resource='aws_db_instance.sample', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+ record4 = Record(check_id='CKV_AWS_118',
+ bc_check_id='BC_AWS_118',
+ check_name="Another RDS check",
+ check_result={"result": CheckResult.PASSED},
+ code_block=None, file_path="./rds.tf",
+ file_line_range='1:3',
+ resource='aws_db_instance.sample', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+
+ r = Report("terraform")
+ r.add_record(record1)
+ r.add_record(record2)
+ r.add_record(record3)
+ r.add_record(record4)
+
+ # When soft_fail=True, the exit code should always be 0.
+ test_soft_fail = r.get_exit_code(soft_fail=True, soft_fail_on=None, hard_fail_on=None)
+
+ # When soft_fail_on=['check1', 'check2'], exit code should be 0 if the only failing checks are in the
+ # soft_fail_on list
+ positive_test_soft_fail_on_code = r.get_exit_code(None, soft_fail_on=['CKV_AWS_157', 'CKV_AWS_16'],
+ hard_fail_on=None)
+ positive_test_soft_fail_on_code_bc_id = r.get_exit_code(None, soft_fail_on=['BC_AWS_157', 'BC_AWS_16'],
+ hard_fail_on=None)
+
+ negative_test_soft_fail_on_code = r.get_exit_code(None, soft_fail_on=['CKV_AWS_157'], hard_fail_on=None)
+ negative_test_soft_fail_on_code_bc_id = r.get_exit_code(None, soft_fail_on=['BC_AWS_157'], hard_fail_on=None)
+
+ # When hard_fail_on=['check1', 'check2'], exit code should be 1 if any checks in the hard_fail_on list fail
+ positive_test_hard_fail_on_code = r.get_exit_code(None, soft_fail_on=None, hard_fail_on=['CKV_AWS_157'])
+ positive_test_hard_fail_on_code_bc_id = r.get_exit_code(None, soft_fail_on=None, hard_fail_on=['BC_AWS_157'])
+
+ negative_test_hard_fail_on_code = r.get_exit_code(None, soft_fail_on=None,
+ hard_fail_on=['CKV_AWS_161', 'CKV_AWS_118'])
+ negative_test_hard_fail_on_code_bc_id = r.get_exit_code(None, soft_fail_on=None,
+ hard_fail_on=['BC_AWS_161', 'BC_AWS_118'])
+
+ self.assertEqual(test_soft_fail, 0)
+ self.assertEqual(positive_test_soft_fail_on_code, 0)
+ self.assertEqual(positive_test_soft_fail_on_code_bc_id, 0)
+ self.assertEqual(negative_test_soft_fail_on_code, 1)
+ self.assertEqual(negative_test_soft_fail_on_code_bc_id, 1)
+ self.assertEqual(positive_test_hard_fail_on_code, 1)
+ self.assertEqual(positive_test_hard_fail_on_code_bc_id, 1)
+ self.assertEqual(negative_test_hard_fail_on_code, 0)
+ self.assertEqual(negative_test_hard_fail_on_code_bc_id, 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/output/test_junit_report.py b/tests/common/output/test_junit_report.py
new file mode 100644
index 0000000000..3a99e9da37
--- /dev/null
+++ b/tests/common/output/test_junit_report.py
@@ -0,0 +1,44 @@
+import unittest
+import os
+import xml.etree.ElementTree as ET
+
+
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.report import Report
+from checkov.common.output.record import Record
+
+
+class TestJunitReport(unittest.TestCase):
+
+ def test_valid_passing_valid_testcases(self):
+ record1 = Record(check_id='CKV_AWS_21',
+ check_name="Some Check", check_result={"result": CheckResult.FAILED},
+ code_block=None, file_path="./s3.tf",
+ file_line_range='1:3',
+ resource='aws_s3_bucket.operations', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+ record2 = Record(check_id='CKV_AWS_3',
+ check_name="Ensure all data stored in the EBS is securely encrypted",
+ check_result={"result": CheckResult.FAILED},
+ code_block=None, file_path="./ec2.tf",
+ file_line_range='1:3',
+ resource='aws_ebs_volume.web_host_storage', evaluations=None,
+ check_class=None, file_abs_path=',.',
+ entity_tags={
+ 'tag1': 'value1'
+ })
+
+ r = Report("terraform")
+ r.add_record(record=record1)
+ r.add_record(record=record2)
+ ts = r.get_test_suites()
+ xml_string = r.get_junit_xml_string(ts)
+ root = ET.fromstring(xml_string)
+ self.assertEqual(root.attrib['errors'], '0')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/common/output/test_sarif_report.py b/tests/common/output/test_sarif_report.py
new file mode 100644
index 0000000000..cd331ce184
--- /dev/null
+++ b/tests/common/output/test_sarif_report.py
@@ -0,0 +1,64 @@
+import unittest
+import os
+import json
+import jsonschema
+import urllib.request
+
+from checkov.common.models.enums import CheckResult
+from checkov.common.output.report import Report
+from checkov.common.output.record import Record
+
+
+class TestSarifReport(unittest.TestCase):
+ def test_valid_passing_valid_testcases(self):
+ record1 = Record(
+ check_id="CKV_AWS_21",
+ check_name="Some Check",
+ check_result={"result": CheckResult.FAILED},
+ code_block=None,
+ file_path="./s3.tf",
+ file_line_range=[1, 3],
+ resource="aws_s3_bucket.operations",
+ evaluations=None,
+ check_class=None,
+ file_abs_path=",.",
+ entity_tags={"tag1": "value1"},
+ )
+
+ record2 = Record(
+ check_id="CKV_AWS_3",
+ check_name="Ensure all data stored in the EBS is securely encrypted",
+ check_result={"result": CheckResult.FAILED},
+ code_block=None,
+ file_path="./ec2.tf",
+ file_line_range=[1, 3],
+ resource="aws_ebs_volume.web_host_storage",
+ evaluations=None,
+ check_class=None,
+ file_abs_path=",.",
+ entity_tags={"tag1": "value1"},
+ )
+
+ r = Report("terraform")
+ r.add_record(record=record1)
+ r.add_record(record=record2)
+ ts = r.get_test_suites()
+ json_structure = r.get_sarif_json()
+ print(json.dumps(json_structure))
+ self.assertEqual(
+ None,
+ jsonschema.validate(instance=json_structure, schema=get_sarif_schema()),
+ )
+
+
+def get_sarif_schema():
+ file_name, headers = urllib.request.urlretrieve(
+ "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
+ )
+ with open(file_name, "r") as file:
+ schema = json.load(file)
+ return schema
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/common/runner_registry/example_multi_iac/tf/terraform.tfvars b/tests/common/runner_registry/example_multi_iac/tf/terraform.tfvars
new file mode 100644
index 0000000000..54f8d98fac
--- /dev/null
+++ b/tests/common/runner_registry/example_multi_iac/tf/terraform.tfvars
@@ -0,0 +1 @@
+VERSIONING = true
\ No newline at end of file
diff --git a/tests/common/runner_registry/plan_with_hcl_for_enrichment/dynamodb.tf b/tests/common/runner_registry/plan_with_hcl_for_enrichment/dynamodb.tf
new file mode 100644
index 0000000000..bde9415da5
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_hcl_for_enrichment/dynamodb.tf
@@ -0,0 +1,12 @@
+resource "aws_dynamodb_table" "cross-environment-violations" {
+ # checkov:skip=CKV_AWS_28: ignoring backups for now
+ name = "CrossEnvironmentViolations"
+ read_capacity = 20
+ write_capacity = 20
+ hash_key = "id"
+ attribute {
+ name = "id"
+ type = "S"
+ }
+ provider = aws.current_region
+}
\ No newline at end of file
diff --git a/tests/common/runner_registry/plan_with_hcl_for_enrichment/iam.tf b/tests/common/runner_registry/plan_with_hcl_for_enrichment/iam.tf
new file mode 100644
index 0000000000..1cbc86b6ce
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_hcl_for_enrichment/iam.tf
@@ -0,0 +1,19 @@
+resource "aws_iam_policy" "policy" {
+ name = "my_policy-123456789101"
+ path = "/"
+ description = "My test policy"
+ policy = <Deployed via Terraform" | sudo tee /var/www/html/index.html
+EOF
+ tags = {
+ Name = "${local.resource_prefix.value}-ec2"
+ }
+}
diff --git a/tests/common/test_platform_integration.py b/tests/common/test_platform_integration.py
index 8a78cc99c2..c3db0b7161 100644
--- a/tests/common/test_platform_integration.py
+++ b/tests/common/test_platform_integration.py
@@ -1,25 +1,38 @@
import os
import unittest
from unittest import mock
+
+from checkov.common.bridgecrew.bc_source import get_source_type
from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
class TestBCApiUrl(unittest.TestCase):
- @mock.patch.dict(os.environ,{'BC_API_URL':'foo'})
+ @mock.patch.dict(os.environ, {'BC_API_URL': 'foo'})
def test_overriding_bc_api_url(self):
instance = BcPlatformIntegration()
- self.assertEqual(instance.bc_api_url,"foo")
+ self.assertEqual(instance.bc_api_url, "foo")
- @mock.patch.dict(os.environ,{'BC_SOURCE':'foo'})
- def test_overriding_bc_source(self):
+ @mock.patch.dict(os.environ, {'BC_SKIP_MAPPING': 'TRUE'})
+ def test_skip_mapping(self):
instance = BcPlatformIntegration()
- self.assertEqual(instance.bc_source,"foo")
+ instance.setup_http_manager()
+ instance.get_id_mapping()
+ self.assertDictEqual({}, instance.ckv_to_bc_id_mapping)
- def test_default_bc_source(self):
+ @mock.patch.dict(os.environ, {'BC_SKIP_MAPPING': 'FALSE'})
+ def test_skip_mapping_false(self):
instance = BcPlatformIntegration()
- self.assertEqual(instance.bc_source,"cli")
+ instance.setup_http_manager()
+ instance.get_id_mapping()
+ self.assertIsNotNone(instance.ckv_to_bc_id_mapping)
+
+ def test_should_upload(self):
+ self.assertFalse(get_source_type('vscode').upload_results)
+ self.assertTrue(get_source_type('cli').upload_results)
+ self.assertFalse(get_source_type('xyz').upload_results)
+ self.assertFalse(get_source_type(None).upload_results)
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/common/test_runner_filter.py b/tests/common/test_runner_filter.py
index 2fa40bb5fe..c25f92d181 100644
--- a/tests/common/test_runner_filter.py
+++ b/tests/common/test_runner_filter.py
@@ -25,18 +25,34 @@ def test_should_run_specific_enable(self):
instance = RunnerFilter(checks=["CHECK_1"])
self.assertTrue(instance.should_run_check("CHECK_1"))
+ def test_should_run_specific_enable_bc(self):
+ instance = RunnerFilter(checks=["BC_CHECK_1"])
+ self.assertTrue(instance.should_run_check("CHECK_1", "BC_CHECK_1"))
+
def test_should_run_omitted_specific_enable(self):
instance = RunnerFilter(checks=["CHECK_1"])
self.assertFalse(instance.should_run_check("CHECK_999"))
+ def test_should_run_omitted_specific_enable_bc_id(self):
+ instance = RunnerFilter(checks=["BC_CHECK_1"])
+ self.assertFalse(instance.should_run_check("CHECK_999", "BC_CHECK_999"))
+
def test_should_run_specific_disable(self):
instance = RunnerFilter(skip_checks=["CHECK_1"])
self.assertFalse(instance.should_run_check("CHECK_1"))
+ def test_should_run_specific_disable_bc_id(self):
+ instance = RunnerFilter(skip_checks=["BC_CHECK_1"])
+ self.assertFalse(instance.should_run_check("CHECK_1", "BC_CHECK_1"))
+
def test_should_run_omitted_specific_disable(self):
instance = RunnerFilter(skip_checks=["CHECK_1"])
self.assertTrue(instance.should_run_check("CHECK_999"))
+ def test_should_run_omitted_specific_disable_bc_id(self):
+ instance = RunnerFilter(skip_checks=["BC_CHECK_1"])
+ self.assertTrue(instance.should_run_check("CHECK_999", "BC_CHECK_999"))
+
def test_should_run_external(self):
instance = RunnerFilter(skip_checks=["CHECK_1"])
instance.notify_external_check("EXT_CHECK_999")
@@ -45,18 +61,28 @@ def test_should_run_external(self):
def test_should_run_external2(self):
instance = RunnerFilter(checks=["CHECK_1"], skip_checks=["CHECK_2"])
instance.notify_external_check("EXT_CHECK_999")
- self.assertTrue(instance.should_run_check("EXT_CHECK_999"))
+ self.assertFalse(instance.should_run_check("EXT_CHECK_999"))
def test_should_run_external3(self):
instance = RunnerFilter(checks=["EXT_CHECK_999"])
instance.notify_external_check("EXT_CHECK_999")
self.assertTrue(instance.should_run_check("EXT_CHECK_999"))
+ def test_should_run_external4(self):
+ instance = RunnerFilter(checks=["CHECK_1"], skip_checks=["CHECK_2"], all_external=True)
+ instance.notify_external_check("EXT_CHECK_999")
+ self.assertTrue(instance.should_run_check("EXT_CHECK_999"))
+
def test_should_run_external_disabled(self):
instance = RunnerFilter(skip_checks=["CHECK_1", "EXT_CHECK_999"])
instance.notify_external_check("EXT_CHECK_999")
self.assertFalse(instance.should_run_check("EXT_CHECK_999"))
+ def test_should_run_external_disabled2(self):
+ instance = RunnerFilter(skip_checks=["CHECK_1", "EXT_CHECK_999"], all_external=True)
+ instance.notify_external_check("EXT_CHECK_999")
+ self.assertFalse(instance.should_run_check("EXT_CHECK_999"))
+
def test_should_run_specific_disable_AND_enable(self):
instance = RunnerFilter(checks=["CHECK_1"], skip_checks=["CHECK_1"])
self.assertTrue(instance.should_run_check("CHECK_1"))
@@ -64,10 +90,18 @@ def test_should_run_specific_disable_AND_enable(self):
def test_should_run_omitted_wildcard(self):
instance = RunnerFilter(skip_checks=["CHECK_AWS*"])
self.assertTrue(instance.should_run_check("CHECK_999"))
+
+ def test_should_run_omitted_wildcard_bc_id(self):
+ instance = RunnerFilter(skip_checks=["BC_CHECK_AWS*"])
+ self.assertTrue(instance.should_run_check("CHECK_999", "BC_CHECK_999"))
def test_should_run_omitted_wildcard2(self):
instance = RunnerFilter(skip_checks=["CHECK_AWS*"])
- self.assertFalse(instance.should_run_check("CHECK_AWS_909"))
+ self.assertFalse(instance.should_run_check("CHECK_AWS_909"))
+
+ def test_should_run_omitted_wildcard2_bc_id(self):
+ instance = RunnerFilter(skip_checks=["BC_CHECK_AWS*"])
+ self.assertFalse(instance.should_run_check("CHECK_AWS_909", "BC_CHECK_AWS_909"))
def test_should_run_omitted_wildcard3(self):
instance = RunnerFilter(skip_checks=["CHECK_AWS*","CHECK_AZURE*"])
@@ -78,6 +112,5 @@ def test_should_run_omitted_wildcard4(self):
self.assertFalse(instance.should_run_check("CHECK_AZURE_01"))
-
if __name__ == '__main__':
unittest.main()
diff --git a/tests/common/utils/test_docs_generator.py b/tests/common/utils/test_docs_generator.py
index d2a0b1fbd6..9fd0841fcf 100644
--- a/tests/common/utils/test_docs_generator.py
+++ b/tests/common/utils/test_docs_generator.py
@@ -1,7 +1,5 @@
-import os
import unittest
-from unittest import mock
-from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration
+
from checkov.common.util.docs_generator import get_compare_key
@@ -15,10 +13,12 @@ def test_id_sorting_for_ckv_pattern(self):
['CKV_K8S_15', '', '', '', ''],
['CKV_K8S_9', '', '', '', ''],
['CKV_K8S_2', '', '', '', ''],
+ ['CKV2_K8S_2', '', '', '', ''],
['CKV_AZURE_11', '', '', '', ''],
['CKV_AZURE_32', '', '', '', ''],
['CKV_GIT_1', '', '', '', ''],
['CKV_AZURE_10', '', '', '', ''],
+ ['CKV2_AWS_1', '', '', '', ''],
['CKV_AZURE_22', '', '', '', ''],
['CKV_K8S_20', '', '', '', ''],
['CKV_GCP_1', '', '', '', ''],
@@ -32,6 +32,7 @@ def test_id_sorting_for_ckv_pattern(self):
self.assertEqual(sorted_check_ids, [
['CKV_AWS_1', '', '', '', ''],
['CKV_AWS_20', '', '', '', ''],
+ ['CKV2_AWS_1', '', '', '', ''],
['CKV_AZURE_10', '', '', '', ''],
['CKV_AZURE_11', '', '', '', ''],
['CKV_AZURE_12', '', '', '', ''],
@@ -47,130 +48,7 @@ def test_id_sorting_for_ckv_pattern(self):
['CKV_K8S_11', '', '', '', ''],
['CKV_K8S_15', '', '', '', ''],
['CKV_K8S_20', '', '', '', ''],
- ])
-
- def test_id_sorting_for_other_patterns(self):
- # keep all other things the same so sorting is based on the id
- check_ids = [
- ['A_03_C_02', '', '', '', ''],
- ['A_03_C_1', '', '', '', ''],
- ['A_01', '', '', '', ''],
- ['A_2', '', '', '', ''],
- ['A_03_C_2', '', '', '', ''],
- ['A_1_B', '', '', '', ''],
- ['A_1_A', '', '', '', ''],
- ['A_02_A', '', '', '', ''],
- ['B_2_0', '', '', '', ''],
- ['B_1_1', '', '', '', ''],
- ['C_0_1', '', '', '', ''],
- ['B_1_2', '', '', '', ''],
- ['A_1', '', '', '', ''],
- ['C_1', '', '', '', ''],
- ['C_2', '', '', '', ''],
- ['A_03_B', '', '', '', ''],
- ['B_1', '', '', '', ''],
- ]
- sorted_check_ids = sorted(check_ids, key=get_compare_key)
- self.assertEqual(sorted_check_ids, [
- ['A_1', '', '', '', ''],
- ['A_1_A', '', '', '', ''],
- ['A_1_B', '', '', '', ''],
- ['A_01', '', '', '', ''],
- ['A_2', '', '', '', ''],
- ['A_02_A', '', '', '', ''],
- ['A_03_B', '', '', '', ''],
- ['A_03_C_1', '', '', '', ''],
- ['A_03_C_2', '', '', '', ''],
- ['A_03_C_02', '', '', '', ''],
- ['B_1', '', '', '', ''],
- ['B_1_1', '', '', '', ''],
- ['B_1_2', '', '', '', ''],
- ['B_2_0', '', '', '', ''],
- ['C_0_1', '', '', '', ''],
- ['C_1', '', '', '', ''],
- ['C_2', '', '', '', ''],
- ])
-
- def test_id_sorting_for_arbitrary_string(self):
- # keep all other things the same so sorting is based on the id
- check_ids = [
- ['Xa1246', '', '', '', ''],
- ['X124', '', '', '', ''],
- ['so_a2_3', '', '', '', ''],
- ['so_aa2_3', '', '', '', ''],
- ['aDa(8)', '', '', '', ''],
- ['X1246', '', '', '', ''],
- ['X123', '', '', '', ''],
- ['1a', '', '', '', ''],
- ['Xa12a1', '', '', '', ''],
- ['Ada', '', '', '', ''],
- ['so_b1', '', '', '', ''],
- ['2a', '', '', '', ''],
- ['X1245', '', '', '', ''],
- ['Xa12', '', '', '', ''],
- ['aDa(9)', '', '', '', ''],
- ['2b', '', '', '', ''],
- ['aDa(7)asd', '', '', '', ''],
- ['Xa2', '', '', '', ''],
- ['Xa12ab', '', '', '', ''],
- ]
- sorted_check_ids = sorted(check_ids, key=get_compare_key)
- self.assertEqual(sorted_check_ids, [
- ['1a', '', '', '', ''],
- ['2a', '', '', '', ''],
- ['2b', '', '', '', ''],
- ['Ada', '', '', '', ''],
- ['X123', '', '', '', ''],
- ['X124', '', '', '', ''],
- ['X1245', '', '', '', ''],
- ['X1246', '', '', '', ''],
- ['Xa2', '', '', '', ''],
- ['Xa12', '', '', '', ''],
- ['Xa12a1', '', '', '', ''],
- ['Xa12ab', '', '', '', ''],
- ['Xa1246', '', '', '', ''],
- ['aDa(7)asd', '', '', '', ''],
- ['aDa(8)', '', '', '', ''],
- ['aDa(9)', '', '', '', ''],
- ['so_a2_3', '', '', '', ''],
- ['so_aa2_3', '', '', '', ''],
- ['so_b1', '', '', '', ''],
- ])
-
- def test_id_sorting_leading_zeros(self):
- # keep all other things the same so sorting is based on the id
- check_ids = [
- ['0542', '', '', '', ''],
- ['002', '', '', '', ''],
- ['00542', '', '', '', ''],
- ['001_03', '', '', '', ''],
- ['2', '', '', '', ''],
- ['01', '', '', '', ''],
- ['542', '', '', '', ''],
- ['4', '', '', '', ''],
- ['03', '', '', '', ''],
- ['1', '', '', '', ''],
- ['001', '', '', '', ''],
- ['001_003', '', '', '', ''],
- ['001_0002', '', '', '', ''],
- ['0002', '', '', '', ''],
- ]
- sorted_check_ids = sorted(check_ids, key=get_compare_key)
- self.assertEqual(sorted_check_ids, [
- ['1', '', '', '', ''],
- ['01', '', '', '', ''],
- ['001', '', '', '', ''],
- ['001_0002', '', '', '', ''],
- ['001_03', '', '', '', ''],
- ['001_003', '', '', '', ''],
- ['2', '', '', '', ''],
- ['002', '', '', '', ''],
- ['0002', '', '', '', ''],
- ['03', '', '', '', ''],
- ['4', '', '', '', ''],
- ['542', '', '', '', ''],
- ['0542', '', '', '', ''],
- ['00542', '', '', '', ''],
+ ['CKV2_K8S_2', '', '', '', ''],
])
diff --git a/tests/common/utils/test_utils.py b/tests/common/utils/test_utils.py
new file mode 100644
index 0000000000..856de00ee6
--- /dev/null
+++ b/tests/common/utils/test_utils.py
@@ -0,0 +1,39 @@
+import unittest
+
+from checkov.common.util.data_structures_utils import merge_dicts
+
+
+class TestUtils(unittest.TestCase):
+
+ def test_merge_dicts(self):
+ dict1 = {'a': '1', 'b': '2'}
+ dict2 = {'a': '4', 'c': '3'}
+ dict3 = {'x': 'x', 'y': 'y', 'a': 'q'}
+
+ res = merge_dicts(dict1, dict2)
+ self.assertEqual(len(res), 3)
+ self.assertEqual(res['a'], '4')
+ self.assertEqual(res['b'], '2')
+ self.assertEqual(res['c'], '3')
+
+ res = merge_dicts(dict1, dict2, dict3)
+ self.assertEqual(len(res), 5)
+ self.assertEqual(res['a'], 'q')
+ self.assertEqual(res['b'], '2')
+ self.assertEqual(res['c'], '3')
+ self.assertEqual(res['x'], 'x')
+ self.assertEqual(res['y'], 'y')
+
+ res = merge_dicts(dict1, None)
+ self.assertEqual(len(res), 2)
+ self.assertEqual(res['a'], '1')
+ self.assertEqual(res['b'], '2')
+
+ res = merge_dicts(dict1, 7)
+ self.assertEqual(len(res), 2)
+ self.assertEqual(res['a'], '1')
+ self.assertEqual(res['b'], '2')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/config/TestConfigFile.py b/tests/config/TestConfigFile.py
new file mode 100644
index 0000000000..b209e39aaa
--- /dev/null
+++ b/tests/config/TestConfigFile.py
@@ -0,0 +1,29 @@
+import unittest
+import configargparse
+
+from checkov.main import add_parser_args
+
+
+class TestConfigFile(unittest.TestCase):
+
+ def test_pass(self):
+ argv = ['--ca-certificate', '----- BEGIN CERTIFICATE ----- ----- END CERTIFICATE -----',
+ '--compact', '--directory', 'test-dir', '--docker-image', 'sample-image', '--dockerfile-path',
+ 'Dockerfile', '--download-external-modules', 'True', '--evaluate-variables', 'False',
+ '--external-checks-dir', 'sample-dir', '--external-checks-git', 'sample-github-url', '--file',
+ 'sample.tf', '--framework', 'all', '--no-guide', '--output', 'cli', '--quiet', '--repo-id',
+ 'bridgecrew/sample-repo', '--skip-check', 'CKV_DOCKER_3,CKV_DOCKER_2', '--skip-fixes',
+ '--skip-framework', 'dockerfile', '--skip-suppressions', '--soft-fail', '--branch', 'master',
+ '--check', 'CKV_DOCKER_1']
+ argv_parser = configargparse.ArgParser(config_file_parser_class=configargparse.YAMLConfigFileParser)
+ config_parser = configargparse.ArgParser(config_file_parser_class=configargparse.YAMLConfigFileParser,
+ default_config_files=['example_TestConfigFile/config.yml'])
+ add_parser_args(argv_parser)
+ add_parser_args(config_parser)
+ config_from_argv = argv_parser.parse_args(argv)
+ config_from_file = config_parser.parse_args([])
+ self.assertEqual(config_from_argv, config_from_file)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/config/__init__.py b/tests/config/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/config/example_TestConfigFile/config.yml b/tests/config/example_TestConfigFile/config.yml
new file mode 100644
index 0000000000..827c6eae69
--- /dev/null
+++ b/tests/config/example_TestConfigFile/config.yml
@@ -0,0 +1,29 @@
+---
+ca-certificate: "----- BEGIN CERTIFICATE ----- ----- END CERTIFICATE -----"
+compact: True
+directory:
+ - 'test-dir'
+docker-image: 'sample-image'
+dockerfile-path: 'Dockerfile'
+download-external-modules: True
+evaluate-variables: False
+external-checks-dir: 'sample-dir'
+external-checks-git:
+ - 'sample-github-url'
+external-modules-download-path: '.external_modules'
+file: 'sample.tf'
+framework: 'all'
+no-guide: True
+output: 'cli'
+quiet: True
+repo-id: 'bridgecrew/sample-repo'
+skip-check:
+ - "CKV_DOCKER_3,CKV_DOCKER_2"
+skip-fixes: True
+skip-framework: 'dockerfile'
+skip-suppressions: True
+soft-fail: True
+branch: "master"
+check:
+ - "CKV_DOCKER_1"
+
diff --git a/tests/dockerfile/__init__.py b/tests/dockerfile/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/dockerfile/checks/__init__.py b/tests/dockerfile/checks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/dockerfile/checks/example_AddExists/failure/Dockerfile b/tests/dockerfile/checks/example_AddExists/failure/Dockerfile
new file mode 100644
index 0000000000..35e47423a8
--- /dev/null
+++ b/tests/dockerfile/checks/example_AddExists/failure/Dockerfile
@@ -0,0 +1,5 @@
+FROM base
+
+LABEL foo="bar baz"
+ADD http://example.com/package.zip /temp
+USER me
diff --git a/tests/dockerfile/checks/example_HealthcheckExists/failure/Dockerfile b/tests/dockerfile/checks/example_HealthcheckExists/failure/Dockerfile
new file mode 100644
index 0000000000..4d77c1d294
--- /dev/null
+++ b/tests/dockerfile/checks/example_HealthcheckExists/failure/Dockerfile
@@ -0,0 +1,4 @@
+FROM base
+
+LABEL foo="bar baz
+USER me
diff --git a/tests/dockerfile/checks/example_HealthcheckExists/success/Dockerfile b/tests/dockerfile/checks/example_HealthcheckExists/success/Dockerfile
new file mode 100644
index 0000000000..f1686f0a8b
--- /dev/null
+++ b/tests/dockerfile/checks/example_HealthcheckExists/success/Dockerfile
@@ -0,0 +1,6 @@
+FROM base
+
+LABEL foo="bar baz
+USER me
+
+HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1
diff --git a/tests/dockerfile/checks/example_MaintainerExists/failure/Dockerfile b/tests/dockerfile/checks/example_MaintainerExists/failure/Dockerfile
new file mode 100644
index 0000000000..9a5e6e6dbf
--- /dev/null
+++ b/tests/dockerfile/checks/example_MaintainerExists/failure/Dockerfile
@@ -0,0 +1,3 @@
+FROM base
+
+MAINTAINER checkov
diff --git a/tests/dockerfile/checks/example_ReferenceLatestTag/failure_default_version_tag/Dockerfile b/tests/dockerfile/checks/example_ReferenceLatestTag/failure_default_version_tag/Dockerfile
new file mode 100644
index 0000000000..67fd379018
--- /dev/null
+++ b/tests/dockerfile/checks/example_ReferenceLatestTag/failure_default_version_tag/Dockerfile
@@ -0,0 +1 @@
+FROM alpine
diff --git a/tests/dockerfile/checks/example_ReferenceLatestTag/failure_latest_version_tag/Dockerfile b/tests/dockerfile/checks/example_ReferenceLatestTag/failure_latest_version_tag/Dockerfile
new file mode 100644
index 0000000000..b09b037ca2
--- /dev/null
+++ b/tests/dockerfile/checks/example_ReferenceLatestTag/failure_latest_version_tag/Dockerfile
@@ -0,0 +1 @@
+FROM alpine:latest
diff --git a/tests/dockerfile/checks/example_ReferenceLatestTag/success/Dockerfile b/tests/dockerfile/checks/example_ReferenceLatestTag/success/Dockerfile
new file mode 100644
index 0000000000..6f1f6eb348
--- /dev/null
+++ b/tests/dockerfile/checks/example_ReferenceLatestTag/success/Dockerfile
@@ -0,0 +1 @@
+FROM alpine:3
diff --git a/tests/dockerfile/checks/example_ReferenceLatestTag/success_multi_stage/Dockerfile b/tests/dockerfile/checks/example_ReferenceLatestTag/success_multi_stage/Dockerfile
new file mode 100644
index 0000000000..64fe5f6763
--- /dev/null
+++ b/tests/dockerfile/checks/example_ReferenceLatestTag/success_multi_stage/Dockerfile
@@ -0,0 +1,5 @@
+FROM alpine:3 as base
+COPY test.sh /test.sh
+
+FROM base
+LABEL maintainer=checkov
diff --git a/tests/dockerfile/checks/example_RootUser/failure/Dockerfile b/tests/dockerfile/checks/example_RootUser/failure/Dockerfile
new file mode 100644
index 0000000000..eb6d6cdb9b
--- /dev/null
+++ b/tests/dockerfile/checks/example_RootUser/failure/Dockerfile
@@ -0,0 +1,3 @@
+FROM base
+
+USER root
diff --git a/tests/dockerfile/checks/example_RootUser/success/Dockerfile b/tests/dockerfile/checks/example_RootUser/success/Dockerfile
new file mode 100644
index 0000000000..50f4fdbe51
--- /dev/null
+++ b/tests/dockerfile/checks/example_RootUser/success/Dockerfile
@@ -0,0 +1,6 @@
+FROM base
+
+USER root
+COPY test.sh /test.sh
+
+USER checkov
diff --git a/tests/dockerfile/checks/example_UpdateNotAlone/failure/Dockerfile b/tests/dockerfile/checks/example_UpdateNotAlone/failure/Dockerfile
new file mode 100644
index 0000000000..5aa278661f
--- /dev/null
+++ b/tests/dockerfile/checks/example_UpdateNotAlone/failure/Dockerfile
@@ -0,0 +1,3 @@
+FROM base
+
+RUN apk update
diff --git a/tests/dockerfile/checks/example_UpdateNotAlone/success/Dockerfile b/tests/dockerfile/checks/example_UpdateNotAlone/success/Dockerfile
new file mode 100644
index 0000000000..520b1583f2
--- /dev/null
+++ b/tests/dockerfile/checks/example_UpdateNotAlone/success/Dockerfile
@@ -0,0 +1,10 @@
+FROM base
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends foo \
+ && echo gooo
+
+RUN apk update \
+ && apk add --no-cache suuu looo
+
+RUN apk --update add moo
diff --git a/tests/dockerfile/checks/example_UserExists/failure/Dockerfile b/tests/dockerfile/checks/example_UserExists/failure/Dockerfile
new file mode 100644
index 0000000000..0394173ac0
--- /dev/null
+++ b/tests/dockerfile/checks/example_UserExists/failure/Dockerfile
@@ -0,0 +1,3 @@
+FROM base
+
+LABEL foo="bar baz
diff --git a/tests/dockerfile/checks/example_UserExists/success/Dockerfile b/tests/dockerfile/checks/example_UserExists/success/Dockerfile
new file mode 100644
index 0000000000..4d77c1d294
--- /dev/null
+++ b/tests/dockerfile/checks/example_UserExists/success/Dockerfile
@@ -0,0 +1,4 @@
+FROM base
+
+LABEL foo="bar baz
+USER me
diff --git a/tests/dockerfile/checks/test_AddExists.py b/tests/dockerfile/checks/test_AddExists.py
new file mode 100644
index 0000000000..4a40e746fd
--- /dev/null
+++ b/tests/dockerfile/checks/test_AddExists.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.AddExists import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestAddExists(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_AddExists"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ failing_resources = {"/failure/Dockerfile.ADD"}
+
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 0)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_HealthcheckExists.py b/tests/dockerfile/checks/test_HealthcheckExists.py
new file mode 100644
index 0000000000..31c159b62b
--- /dev/null
+++ b/tests/dockerfile/checks/test_HealthcheckExists.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.HealthcheckExists import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestHealthcheckExists(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_HealthcheckExists"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"/success/Dockerfile.HEALTHCHECK"}
+ failing_resources = {"/failure/Dockerfile."}
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_MaintainerExists.py b/tests/dockerfile/checks/test_MaintainerExists.py
new file mode 100644
index 0000000000..7541aa2d66
--- /dev/null
+++ b/tests/dockerfile/checks/test_MaintainerExists.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.MaintainerExists import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestMaintainerExists(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_MaintainerExists"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ failing_resources = {"/failure/Dockerfile.MAINTAINER"}
+
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 0)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_ReferenceLatestTag.py b/tests/dockerfile/checks/test_ReferenceLatestTag.py
new file mode 100644
index 0000000000..e91c996f72
--- /dev/null
+++ b/tests/dockerfile/checks/test_ReferenceLatestTag.py
@@ -0,0 +1,37 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.ReferenceLatestTag import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestReferenceLatestTag(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ReferenceLatestTag"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"/success/Dockerfile.", "/success_multi_stage/Dockerfile."}
+ failing_resources = {
+ "/failure_default_version_tag/Dockerfile.FROM",
+ "/failure_latest_version_tag/Dockerfile.FROM",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_RootUser.py b/tests/dockerfile/checks/test_RootUser.py
new file mode 100644
index 0000000000..430439fb3a
--- /dev/null
+++ b/tests/dockerfile/checks/test_RootUser.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.RootUser import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRootUser(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RootUser"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"/success/Dockerfile.USER"}
+ failing_resources = {"/failure/Dockerfile.USER"}
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_UpdateNotAlone.py b/tests/dockerfile/checks/test_UpdateNotAlone.py
new file mode 100644
index 0000000000..3ec2a036d4
--- /dev/null
+++ b/tests/dockerfile/checks/test_UpdateNotAlone.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.UpdateNotAlone import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestUpdateNotAlone(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_UpdateNotAlone"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"/success/Dockerfile."}
+ failing_resources = {"/failure/Dockerfile.RUN"}
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/checks/test_UserExists.py b/tests/dockerfile/checks/test_UserExists.py
new file mode 100644
index 0000000000..2df98a4e64
--- /dev/null
+++ b/tests/dockerfile/checks/test_UserExists.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.dockerfile.checks.UserExists import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestUserExists(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_UserExists"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"/success/Dockerfile.USER"}
+ failing_resources = {"/failure/Dockerfile."}
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/dockerfile/resources/__init__.py b/tests/dockerfile/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/dockerfile/resources/empty_dockerfile/Dockerfile b/tests/dockerfile/resources/empty_dockerfile/Dockerfile
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/dockerfile/resources/expose_port/fail/Dockerfile b/tests/dockerfile/resources/expose_port/fail/Dockerfile
new file mode 100644
index 0000000000..032b4b22fb
--- /dev/null
+++ b/tests/dockerfile/resources/expose_port/fail/Dockerfile
@@ -0,0 +1,8 @@
+FROM node:alpine
+WORKDIR /usr/src/app
+COPY package*.json ./
+RUN npm install
+COPY . .
+EXPOSE 3000 22
+HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1
+CMD ["node","app.js"]
\ No newline at end of file
diff --git a/tests/dockerfile/resources/expose_port/pass/Dockerfile b/tests/dockerfile/resources/expose_port/pass/Dockerfile
new file mode 100644
index 0000000000..cf02ed54d7
--- /dev/null
+++ b/tests/dockerfile/resources/expose_port/pass/Dockerfile
@@ -0,0 +1,4 @@
+FROM gliderlabs/alpine:3.3
+RUN apk --no-cache add nginx
+EXPOSE 3000 80 443
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/tests/dockerfile/resources/expose_port/skip/Dockerfile b/tests/dockerfile/resources/expose_port/skip/Dockerfile
new file mode 100644
index 0000000000..745fa653fe
--- /dev/null
+++ b/tests/dockerfile/resources/expose_port/skip/Dockerfile
@@ -0,0 +1,5 @@
+FROM gliderlabs/alpine:3.3
+RUN apk --no-cache add nginx
+EXPOSE 3000 80 443 22
+#checkov:skip=CKV_DOCKER_1: required
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/tests/dockerfile/test_runner.py b/tests/dockerfile/test_runner.py
new file mode 100644
index 0000000000..ffcb021cd2
--- /dev/null
+++ b/tests/dockerfile/test_runner.py
@@ -0,0 +1,97 @@
+import dis
+import inspect
+import unittest
+
+import os
+from pathlib import Path
+
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRunnerValid(unittest.TestCase):
+
+ def test_runner_empty_dockerfile(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/empty_dockerfile"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='all'))
+ self.assertEqual(report.failed_checks, [])
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_failing_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/expose_port/fail"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='all',checks=['CKV_DOCKER_1']))
+ self.assertEqual(len(report.failed_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_failing_check_with_file_path(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_file_path = current_dir + "/resources/expose_port/fail/Dockerfile"
+ runner = Runner()
+ report = runner.run(
+ files=[valid_file_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all", checks=["CKV_DOCKER_1"]),
+ )
+ self.assertEqual(len(report.failed_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_passing_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/expose_port/pass"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='all',checks=['CKV_DOCKER_1']))
+ self.assertEqual(len(report.passed_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.failed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_skip_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/expose_port/skip"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='all',checks=['CKV_DOCKER_1']))
+ self.assertEqual(len(report.skipped_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.failed_checks, [])
+ self.assertEqual(report.passed_checks, [])
+ report.print_console()
+
+ def test_wrong_check_imports(self):
+ wrong_imports = ["arm", "cloudformation", "helm", "kubernetes", "serverless", "terraform"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/example_AllowedCapabilities/cassandra-FAILED.yaml b/tests/kubernetes/checks/example_AllowedCapabilities/cassandra-FAILED.yaml
index da5eb6946e..d22b2d443a 100644
--- a/tests/kubernetes/checks/example_AllowedCapabilities/cassandra-FAILED.yaml
+++ b/tests/kubernetes/checks/example_AllowedCapabilities/cassandra-FAILED.yaml
@@ -94,6 +94,65 @@ spec:
volumeMounts:
- name: cassandra-data
mountPath: /cassandra_data
+ - name: cassandra
+ image: gcr.io/google-samples/cassandra:v13
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 7000
+ name: intra-node
+ - containerPort: 7001
+ name: tls-intra-node
+ - containerPort: 7199
+ name: jmx
+ - containerPort: 9042
+ name: cql
+ resources:
+ limits:
+ cpu: "500m"
+ memory: 1Gi
+ requests:
+ cpu: "500m"
+ memory: 1Gi
+ securityContext:
+ capabilities:
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - nodetool drain
+ env:
+ - name: MAX_HEAP_SIZE
+ value: 512M
+ - name: HEAP_NEWSIZE
+ value: 100M
+ - name: CASSANDRA_SEEDS
+ value: "cassandra-0.cassandra.default.svc.cluster.local"
+ - name: CASSANDRA_CLUSTER_NAME
+ value: "K8Demo"
+ - name: CASSANDRA_DC
+ value: "DC1-K8Demo"
+ - name: CASSANDRA_RACK
+ value: "Rack1-K8Demo"
+ - name: POD_IP
+ valueFrom:
+ fieldRef:
+ fieldPath: status.podIP
+ readinessProbe:
+ exec:
+ command:
+ - /bin/bash
+ - -c
+ - /ready-probe.sh
+ initialDelaySeconds: 15
+ timeoutSeconds: 5
+ # These volume mounts are persistent. They are like inline claims,
+ # but not exactly because the names need to match exactly one of
+ # the stateful pod volumes.
+ volumeMounts:
+ - name: cassandra-data
+ mountPath: /cassandra_data
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
# do not use these in production until ssd GCEPersistentDisk or other ssd pd
diff --git a/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-FAILED.yaml
new file mode 100644
index 0000000000..7348a09656
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver-failed
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=AlwaysAdmit
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-PASSED.yaml
new file mode 100644
index 0000000000..921af49d02
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAdmissionControlAlwaysAdmit/ApiServerAdmissionControlAlwaysAdmit-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver-passed
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-FAILED.yaml
new file mode 100644
index 0000000000..bfcdeb77f0
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-FAILED.yaml
@@ -0,0 +1,15 @@
+apiVersion: apiserver.config.k8s.io/v1
+kind: AdmissionConfiguration
+metadata:
+ name: "admission-configuration-failed"
+plugins:
+ - name: ValidatingAdmissionWebhook
+ configuration:
+ apiVersion: apiserver.config.k8s.io/v1
+ kind: WebhookAdmissionConfiguration
+ kubeConfigFile: ""
+ - name: MutatingAdmissionWebhook
+ configuration:
+ apiVersion: apiserver.config.k8s.io/v1
+ kind: WebhookAdmissionConfiguration
+ kubeConfigFile: ""
diff --git a/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-PASSED.yaml
new file mode 100644
index 0000000000..0bbef4bc8a
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAdmissionControlEventRateLimit/ApiServerAdmissionControlEventRateLimit-PASSED.yaml
@@ -0,0 +1,17 @@
+apiVersion: apiserver.config.k8s.io/v1
+kind: AdmissionConfiguration
+metadata:
+ name: "admission-configuration-passed"
+plugins:
+ - name: ValidatingAdmissionWebhook
+ configuration:
+ apiVersion: apiserver.config.k8s.io/v1
+ kind: WebhookAdmissionConfiguration
+ kubeConfigFile: ""
+ - name: EventRateLimit
+ path: eventconfig.yaml
+ - name: MutatingAdmissionWebhook
+ configuration:
+ apiVersion: apiserver.config.k8s.io/v1
+ kind: WebhookAdmissionConfiguration
+ kubeConfigFile: ""
diff --git a/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-PASSED.yaml
new file mode 100644
index 0000000000..0b18d680d3
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAlwaysPullImagesPlugin/ApiServerAlwaysPullImagesPlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=AlwaysPullImages
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-False-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-False-PASSED.yaml
new file mode 100644
index 0000000000..42f55e3d92
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-False-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --anonymous-auth=false
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-Missing-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-Missing-FAILED.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-Missing-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-True-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-True-FAILED.yaml
new file mode 100644
index 0000000000..5af8b7d5f0
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAnonymousAuth/ApiServer-AnonymousAuth-True-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --anonymous-auth=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..05e86ccd93
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..4235f113d6
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLog/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-path=/path/to/log
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..41b7691934
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxage=10
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..9e785e1477
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxAge/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxage=40
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..d8d4bdc642
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxbackup=5
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..960e4f924a
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --profiling=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..c685926521
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxBackup/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxbackup=15
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..2d4a3dc174
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxsize=10
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..05e86ccd93
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..c21d07fa04
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuditLogMaxSize/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --audit-log-maxsize=150
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..6de669a856
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..3ff7c9b37b
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..e2a14ef76d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNode/ApiServer-PASSED.yaml
@@ -0,0 +1,48 @@
+
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC,Node
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..d1fb5dce4f
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC,AlwaysAllow
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..ec749a0aa9
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=AlwaysAllow
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED-2.yaml
new file mode 100644
index 0000000000..eb3c73cd4b
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..15c82cfd15
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeNotAlwaysAllow/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC,node
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..6de669a856
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..7edf784300
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=Node
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED-2.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED-2.yaml
new file mode 100644
index 0000000000..b19c029399
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED-2.yaml
@@ -0,0 +1,48 @@
+
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..e2a14ef76d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerAuthorizationModeRBAC/ApiServer-PASSED.yaml
@@ -0,0 +1,48 @@
+
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --authorization-mode=RBAC,Node
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-FAILED.yaml
new file mode 100644
index 0000000000..2b095a697e
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --basic-auth-file=some_file
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-PASSED.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerBasicAuthFile/ApiServerBasicAuthFile-PASSED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-FAILED.yaml
new file mode 100644
index 0000000000..05e86ccd93
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-PASSED.yaml
new file mode 100644
index 0000000000..9c40142882
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEncryptionProviders/ApiServerEncryptionProviders-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --encryption-provider-config=config.file
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-FAILED.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-PASSED.yaml
new file mode 100644
index 0000000000..3214178127
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEtcdCaFile/example_ApiServerEtcdCaFile-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --etcd-ca-file=ca.file
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..99fe61a7d5
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --etcd-certfile=/path/to/cert
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..05e86ccd93
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..684dc32645
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerEtcdCertAndKey/ApiServer-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --etcd-certfile=/path/to/cert
+ - --etcd-keyfile=/path/to/key
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-insecure-bind-address-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-insecure-bind-address-FAILED.yaml
new file mode 100644
index 0000000000..64a4ed5e72
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-insecure-bind-address-FAILED.yaml
@@ -0,0 +1,31 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: kube-apiserver
+ labels:
+ k8s-app: kube-apiserver
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ k8s-app: kube-apiserver
+ component: apiserver
+ provider: kubernetes
+ template:
+ metadata:
+ labels:
+ k8s-app: kube-apiserver
+ component: apiserver
+ provider: kubernetes
+ serviceAccountName: kube-apiserver
+ tolerations:
+ - key: node-role.kubernetes.io/master
+ operator: Exists
+ effect: NoSchedule
+ containers:
+ - name: kube-apiserver
+ image: k8s.gcr.io/kube-apiserver
+ command:
+ - kube-apiserver
+ - --insecure-bind-address=192.168.1.1
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-secure-bind-address-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-secure-bind-address-PASSED.yaml
new file mode 100644
index 0000000000..0f7887624b
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerInsecureBindAddress/api-server-secure-bind-address-PASSED.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --bind-address=192.168.1.1
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
diff --git a/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-FAILED.yaml
new file mode 100644
index 0000000000..2baa64f4a6
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-FAILED.yaml
@@ -0,0 +1,31 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: kube-apiserver
+ labels:
+ k8s-app: kube-apiserver
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ k8s-app: kube-apiserver
+ component: apiserver
+ provider: kubernetes
+ template:
+ metadata:
+ labels:
+ k8s-app: kube-apiserver
+ component: apiserver
+ provider: kubernetes
+ serviceAccountName: kube-apiserver
+ tolerations:
+ - key: node-role.kubernetes.io/master
+ operator: Exists
+ effect: NoSchedule
+ containers:
+ - name: kube-apiserver
+ image: k8s.gcr.io/kube-apiserver
+ command:
+ - kube-apiserver
+ - --insecure-port=80
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-PASSED.yaml
new file mode 100644
index 0000000000..23a6bd1a27
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerInsecurePort/api-server-insecure-port-PASSED.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --insecure-port=0
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..05e86ccd93
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..482ad589d2
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --kubelet-client-certificate=/path/to/cert
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..53212cdd32
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletClientCertAndKey/ApiServer-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --kubelet-client-certificate=/path/to/cert
+ - --kubelet-client-key=/path/to/key
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..581d6658b5
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --kubelet-https=false
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED-2.yaml b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED-2.yaml
new file mode 100644
index 0000000000..eb3c73cd4b
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..b21dcb552c
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerKubeletHttps/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --kubelet-https=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-PASSED.yaml
new file mode 100644
index 0000000000..95e3615c15
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerNamespaceLifecyclePlugin/ApiServerNamespaceLifecyclePlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=NamespaceLifecycle
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-PASSED.yaml
new file mode 100644
index 0000000000..5174e39d5a
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerNodeRestrictionPlugin/ApiServerNodeRestrictionPlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=NodeRestriction
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-PASSED.yaml
new file mode 100644
index 0000000000..1d61293267
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerPodSecurityPolicyPlugin/ApiServerPodSecurityPolicyPlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=PodSecurityPolicy
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..960e4f924a
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --profiling=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..0e6dbe7d4a
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerProfiling/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --profiling=false
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-FAILED.yaml
new file mode 100644
index 0000000000..b605143584
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --request-timeout=1s9m3h
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-PASSED.yaml
new file mode 100644
index 0000000000..1c04a46be6
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerRequestTimeout/api-server-request-timeout-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --request-timeout=2m3s
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-FAILED.yaml
new file mode 100644
index 0000000000..8c4f310559
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-FAILED.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --secure-port=0
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
diff --git a/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-PASSED.yaml
new file mode 100644
index 0000000000..3710e68d0c
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerSecurePort/api-server-secure-port-PASSED.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --secure-port=80
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
diff --git a/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-PASSED.yaml
new file mode 100644
index 0000000000..4b48c56bf0
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerSecurityContextDenyPlugin/ApiServerSecurityContextDenyPlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=SecurityContextDeny
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-FAILED.yaml
new file mode 100644
index 0000000000..6d19218bca
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --service-account-key-file=sdfsdf\dsadsapem
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-PASSED.yaml
new file mode 100644
index 0000000000..7cc7241894
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountKeyFile/ApiServerServiceAccountKeyFile-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --service-account-key-file=/keys/key.pem
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..d63c8e8535
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --service-account-lookup=false
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..a44816ae16
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountLookup/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --service-account-lookup=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-FAILED.yaml
new file mode 100644
index 0000000000..2063f88d9d
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=other
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-PASSED.yaml
new file mode 100644
index 0000000000..fdb4f00a22
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerServiceAccountPlugin/ApiServerServiceAccountPlugin-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --enable-admission-plugins=ServiceAccount
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-FAILED.yaml
new file mode 100644
index 0000000000..0c03cb2e73
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_64_GCM_SHA256
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-PASSED.yaml
new file mode 100644
index 0000000000..7e41e5fb38
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerStrongCryptographicCiphers/ApiServerStrongCryptographicCiphers-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..b6e2d3b7e8
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --tls-cert-file=/path/to/cert
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..f25776261b
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..798ba24791
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerTlsCertAndKey/ApiServer-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --tls-cert-file=/path/to/cert
+ - --tls-private-key-file=/path/to/key
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-FAILED.yaml
new file mode 100644
index 0000000000..8af9610706
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --token-auth-file=some_file
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-PASSED.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerTokenAuthFile/ApiServerTokenAuthFile-PASSED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-FAILED.yaml b/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-FAILED.yaml
new file mode 100644
index 0000000000..ba9284faf7
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-PASSED.yaml b/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-PASSED.yaml
new file mode 100644
index 0000000000..c5bc8802ad
--- /dev/null
+++ b/tests/kubernetes/checks/example_ApiServerkubeletCertificateAuthority/ApiServerkubeletCertificateAuthority-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ - --kubelet-certificate-authority=ca.file
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED-2.yaml b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED-2.yaml
new file mode 100644
index 0000000000..dbc7389995
--- /dev/null
+++ b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED.yaml b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED.yaml
new file mode 100644
index 0000000000..a7cfc2fbee
--- /dev/null
+++ b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --bind-address=0.0.0.0
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-PASSED.yaml b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-PASSED.yaml
new file mode 100644
index 0000000000..53847b105c
--- /dev/null
+++ b/tests/kubernetes/checks/example_ControllerManagerBindAddress/ControllerManagerBindAddress-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --bind-address=127.0.0.1
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_DropCapabilities/pod-drop-NET_RAW-capabilities-FAILED-MISSING.yaml b/tests/kubernetes/checks/example_DropCapabilities/pod-drop-NET_RAW-capabilities-FAILED-MISSING.yaml
new file mode 100644
index 0000000000..09b5931f21
--- /dev/null
+++ b/tests/kubernetes/checks/example_DropCapabilities/pod-drop-NET_RAW-capabilities-FAILED-MISSING.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: pod-drop-net-raw-capability
+spec:
+ containers:
+ - name: main
+ image: fedora
+ command: ["/bin/sleep", "999999"]
+ securityContext:
+ capabilities:
+# kubectl exec -it pod-drop-net-raw-capability -- bash
+# dnf -y install libcap-ng-utils
+# pscap
+### Notice no net_raw capability
diff --git a/tests/kubernetes/checks/example_DropCapabilities/pod-drop-all-capabilities-PASSED2.yaml b/tests/kubernetes/checks/example_DropCapabilities/pod-drop-all-capabilities-PASSED2.yaml
new file mode 100644
index 0000000000..dfc4893db8
--- /dev/null
+++ b/tests/kubernetes/checks/example_DropCapabilities/pod-drop-all-capabilities-PASSED2.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: pod-drop-all-capability
+spec:
+ containers:
+ - name: main
+ image: fedora
+ command: ["/bin/sleep", "999999"]
+ securityContext:
+ capabilities:
+ drop:
+ - all
+
+# kubectl exec -it pod-drop-all-capability -- bash
+# dnf -y install libcap-ng-utils
+ ### You'll see with drop all you can't do this!
diff --git a/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-FAILED.yaml b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-FAILED.yaml
new file mode 100644
index 0000000000..2dccaf1d0e
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-FAILED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --auto-tls=true
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-fail
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED-2.yaml b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED-2.yaml
new file mode 100644
index 0000000000..a774f71865
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-pass
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED.yaml b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED.yaml
new file mode 100644
index 0000000000..081b496cdd
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdAutoTls/Etcd-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --auto-tls=false
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-pass
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED-2.yaml b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED-2.yaml
new file mode 100644
index 0000000000..f9d3bd8adb
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-fail
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED.yaml b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED.yaml
new file mode 100644
index 0000000000..ae11641bef
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-FAILED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --cert-file=/etc/kubernetes/pki/etcd/server.crt
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-fail
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-PASSED.yaml b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-PASSED.yaml
new file mode 100644
index 0000000000..1511962ca1
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdCertAndKey/Etcd-PASSED.yaml
@@ -0,0 +1,49 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --cert-file=/etc/kubernetes/pki/etcd/server.crt
+ - --key-file=/etc/kubernetes/pki/etcd/server.key
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-pass
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED-2.yaml b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED-2.yaml
new file mode 100644
index 0000000000..f9d3bd8adb
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-fail
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED.yaml b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED.yaml
new file mode 100644
index 0000000000..826a165064
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-FAILED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --client-cert-auth=false
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-fail
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-PASSED.yaml b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-PASSED.yaml
new file mode 100644
index 0000000000..ab0d3a6a9e
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdClientCertAuth/Etcd-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ annotations:
+ scheduler.alpha.kubernetes.io/critical-pod: ""
+ creationTimestamp: null
+ labels:
+ component: etcd
+ tier: control-plane
+ name: etcd
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --client-cert-auth=true
+ image: k8s.gcr.io/etcd-amd64:3.2.18
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -ec
+ - ETCDCTL_API=3 etcdctl --endpoints=https://[192.168.22.9]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
+ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
+ get foo
+ failureThreshold: 8
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: etcd-should-pass
+ resources: {}
+ volumeMounts:
+ - mountPath: /var/lib/etcd
+ name: etcd-data
+ - mountPath: /etc/kubernetes/pki/etcd
+ name: etcd-certs
+ hostNetwork: true
+ priorityClassName: system-cluster-critical
+ volumes:
+ - hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ name: etcd-data
+ - hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
+ name: etcd-certs
+status: {}
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-FAILED.yaml b/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-FAILED.yaml
new file mode 100644
index 0000000000..5facb864ac
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --peer-cert-file=file.pem
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-PASSED.yaml b/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-PASSED.yaml
new file mode 100644
index 0000000000..7b1ba81fe5
--- /dev/null
+++ b/tests/kubernetes/checks/example_EtcdPeerFiles/EtcdPeerFiles-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - etcd
+ - --peer-cert-file=file.pem
+ - --peer-key-file=file.key
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..dfa5277748
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --profiling=true
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED_2.yaml b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED_2.yaml
new file mode 100644
index 0000000000..8b3f2b2545
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-FAILED_2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..970378bf5f
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerBlockProfiles/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --profiling=false
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..e7834d2442
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --root-ca-file=file.txt
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..c05a00cb81
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --root-ca-file=private.pem
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED_2.yaml b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED_2.yaml
new file mode 100644
index 0000000000..3c511fe196
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerRootCAFile/ApiServer-PASSED_2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-FAILED.yaml
new file mode 100644
index 0000000000..af01ea41dc
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --feature-gates=RotateKubeletServerCertificate=false
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-PASSED.yaml
new file mode 100644
index 0000000000..0e3139610b
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerRotateKubeletServerCertificate/KubeControllerManagerRotateKubeletServerCertificate-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --feature-gates=RotateKubeletServerCertificate=true
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..1da9308ffb
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --use-service-account-credentials=false
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..9f3a952c7e
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --use-service-account-credentials=true
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-UNKNOWN.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-UNKNOWN.yaml
new file mode 100644
index 0000000000..39c8ec36b5
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountCredentials/ApiServer-UNKNOWN.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-unknwown
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..9dc2f10070
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --service-account-private-key-file=public.txt
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED-2.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED-2.yaml
new file mode 100644
index 0000000000..3c511fe196
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..08353d5d0f
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerServiceAccountPrivateKeyFile/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --service-account-private-key-file=public.pem
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED-2.yaml b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED-2.yaml
new file mode 100644
index 0000000000..4a9841e7e7
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED-2.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --terminated-pod-gc-threshold=0
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED.yaml
new file mode 100644
index 0000000000..8b3f2b2545
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-FAILED.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-fail
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-PASSED.yaml b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-PASSED.yaml
new file mode 100644
index 0000000000..f25938bb8d
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeControllerManagerTerminatedPods/ApiServer-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-controller-manager
+ tier: control-plane
+ name: kube-controller-manager
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-controller-manager
+ - --terminated-pod-gc-threshold=55555
+ image: gcr.io/google_containers/kube-controller-manager-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-controller-manager-should-pass
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-FAILED.yaml b/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-FAILED.yaml
new file mode 100644
index 0000000000..4cdb44a4d9
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --anonymous-auth=true
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-PASSED.yaml b/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-PASSED.yaml
new file mode 100644
index 0000000000..e82309b39b
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletAnonymousAuth/KubeletAnonymousAuth-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --anonymous-auth=false
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-FAILED.yaml b/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-FAILED.yaml
new file mode 100644
index 0000000000..476b09ef77
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --authorization-mode=AlwaysAllow
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-PASSED.yaml b/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-PASSED.yaml
new file mode 100644
index 0000000000..4328f3441d
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletAuthorizationModeNotAlwaysAllow/KubeletAuthorizationModeNotAlwaysAllow-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --authorization-mode=RBAC,node
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-FAILED.yaml b/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-FAILED.yaml
new file mode 100644
index 0000000000..e37284ec5c
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --root-ca-file=test.key
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-PASSED.yaml b/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-PASSED.yaml
new file mode 100644
index 0000000000..c8dbcc7bb2
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletClientCa/KubeletClientCa-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --root-ca-file=test.pem
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-FAILED.yaml b/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-FAILED.yaml
new file mode 100644
index 0000000000..a612987c81
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_64_GCM_SHA256
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-PASSED.yaml b/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-PASSED.yaml
new file mode 100644
index 0000000000..e3c1d9bab1
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletCryptographicCiphers/KubeletCryptographicCiphers-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-scheduler
+ tier: control-plane
+ name: kube-scheduler
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ image: gcr.io/google_containers/kube-scheduler-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-scheduler
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-FAILED.yaml b/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-FAILED.yaml
new file mode 100644
index 0000000000..d4ef4eafa4
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --hostname-override=check.io
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-PASSED.yaml b/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-PASSED.yaml
new file mode 100644
index 0000000000..967c200c2d
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletHostnameOverride/KubeletHostnameOverride-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --read-only-port=80
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-FAILED.yaml b/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-FAILED.yaml
new file mode 100644
index 0000000000..7bbc6e8b62
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --tls-cert-file=test.pem
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-PASSED.yaml b/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-PASSED.yaml
new file mode 100644
index 0000000000..3c2d82a0b5
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletKeyFilesSetAppropriate/KubeletKeyFilesSetAppropriate-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --tls-cert-file=test.pem
+ - --tls-private-key-file=test.key
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-FAILED.yaml b/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-FAILED.yaml
new file mode 100644
index 0000000000..0b75d2efd4
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --make-iptables-util-chains=false
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-PASSED.yaml b/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-PASSED.yaml
new file mode 100644
index 0000000000..278d750b5d
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletMakeIptablesUtilChains/KubeletMakeIptablesUtilChains-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --make-iptables-util-chains=true
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-FAILED.yaml b/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-FAILED.yaml
new file mode 100644
index 0000000000..92e35d422d
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --protect-kernel-defaults=false
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-PASSED.yaml b/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-PASSED.yaml
new file mode 100644
index 0000000000..159103c437
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletProtectKernelDefaults/KubeletProtectKernelDefaults-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --protect-kernel-defaults=true
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-FAILED.yaml b/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-FAILED.yaml
new file mode 100644
index 0000000000..4f3de75a94
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --read-only-port=1
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-PASSED.yaml b/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-PASSED.yaml
new file mode 100644
index 0000000000..a77bd84ff4
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletReadOnlyPort/KubeletReadOnlyPort-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --read-only-port=0
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-FAILED.yaml b/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-FAILED.yaml
new file mode 100644
index 0000000000..9c6112a0c2
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --streaming-connection-idle-timeout=0
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-PASSED.yaml b/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-PASSED.yaml
new file mode 100644
index 0000000000..2603641dca
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubeletStreamingConnectionIdleTimeout/KubeletStreamingConnectionIdleTimeout-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --streaming-connection-idle-timeout=1
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-FAILED.yaml b/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-FAILED.yaml
new file mode 100644
index 0000000000..d10149ff11
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --event-qps=6
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-PASSED.yaml b/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-PASSED.yaml
new file mode 100644
index 0000000000..c076a72d8e
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletEventCapture/KubletEventCapture-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --event-qps=2
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-FAILED.yaml b/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-FAILED.yaml
new file mode 100644
index 0000000000..060127ffaf
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --rotate-certificates=false
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-PASSED.yaml b/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-PASSED.yaml
new file mode 100644
index 0000000000..3bc5b34703
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletRotateCertificates/KubletRotateCertificates-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-scheduler
+ tier: control-plane
+ name: kube-scheduler
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --rotate-certificates=true
+ image: gcr.io/google_containers/kube-scheduler-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-scheduler
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-FAILED.yaml b/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-FAILED.yaml
new file mode 100644
index 0000000000..9bc5d0cbbe
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --feature-gates=RotateKubeletServerCertificate=false
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-PASSED.yaml b/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-PASSED.yaml
new file mode 100644
index 0000000000..adfced79b1
--- /dev/null
+++ b/tests/kubernetes/checks/example_KubletRotateKubeletServerCertificate/KubletRotateKubeletServerCertificate-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kubelet
+ tier: control-plane
+ name: kubelet
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kubelet
+ - --feature-gates=RotateKubeletServerCertificate=true
+ image: gcr.io/google_containers/kubelet-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kubelet
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_PSP/psp-no-annotations-FAILED.yml b/tests/kubernetes/checks/example_PSP/psp-no-annotations-FAILED.yml
new file mode 100644
index 0000000000..13a03d5217
--- /dev/null
+++ b/tests/kubernetes/checks/example_PSP/psp-no-annotations-FAILED.yml
@@ -0,0 +1,37 @@
+apiVersion: policy/v1beta1
+kind: PodSecurityPolicy
+metadata:
+ name: restricted
+ annotations:
+spec:
+ privileged: false
+ allowPrivilegeEscalation: false
+ requiredDropCapabilities:
+ - ALL
+ volumes:
+ - 'configMap'
+ - 'emptyDir'
+ - 'projected'
+ - 'secret'
+ - 'downwardAPI'
+ - 'persistentVolumeClaim'
+ hostNetwork: false
+ hostIPC: false
+ hostPID: false
+ runAsUser:
+ rule: 'MustRunAsNonRoot'
+ seLinux:
+ rule: 'RunAsAny'
+ supplementalGroups:
+ rule: 'MustRunAs'
+ ranges:
+ - min: 1
+ max: 65535
+ fsGroup:
+ rule: 'MustRunAs'
+ ranges:
+ # Forbid adding the root group.
+ - min: 1
+ max: 65535
+ readOnlyRootFilesystem: false
+
diff --git a/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED.yaml b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED.yaml
new file mode 100644
index 0000000000..95147c83be
--- /dev/null
+++ b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED.yaml
@@ -0,0 +1,52 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: etcd
+ namespace: should-fail
+spec:
+ hostNetwork: true
+ containers:
+ - name: "kuku1"
+ image: "b.gcr.io/kuar/etcd:2.2.0"
+ args:
+ - "--name=etcd0"
+ - "--advertise-client-urls=http://10.0.0.1:2379"
+ - "--listen-client-urls=http://0.0.0.0:2379"
+ - "--listen-peer-urls=http://0.0.0.0:2380"
+ - "--data-dir=/var/lib/etcd/data"
+ - "--wal-dir=/var/lib/etcd/wal"
+ - "--election-timeout=1000"
+ - "--heartbeat-interval=100"
+ - "--snapshot-count=10000"
+ - "--max-snapshots=5"
+ - "--max-wals=5"
+ - "--initial-advertise-peer-urls=http://10.0.0.1:2380"
+ - "--initial-cluster=etcd0=http://10.0.0.1:2380,etcd1=http://10.0.0.2:2380,etcd2=http://10.0.0.2:2380"
+ - "--initial-cluster-state=new"
+ - "--initial-cluster-token=cluster0"
+ - "--peer-client-cert-auth=false"
+ ports:
+ - name: client
+ containerPort: 2379
+ protocol: "TCP"
+ - name: peer
+ containerPort: 2380
+ protocol: "TCP"
+ resources:
+ limits:
+ cpu: "1000m"
+ memory: "256Mi"
+ volumeMounts:
+ - name: "etcd-data"
+ mountPath: /var/lib/etcd/data
+ - name: "etcd-wal"
+ mountPath: /var/lib/etcd/wal
+ volumes:
+ - name: "etcd-wal"
+ awsElasticBlockStore:
+ volumeID: vol-1234wal0
+ fsType: ext4
+ - name: "etcd-data"
+ awsElasticBlockStore:
+ volumeID: vol-1234data0
+ fsType: ext4
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED2.yaml b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED2.yaml
new file mode 100644
index 0000000000..a041182ba4
--- /dev/null
+++ b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-FAILED2.yaml
@@ -0,0 +1,51 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: etcd
+ namespace: should-fail
+spec:
+ hostNetwork: true
+ containers:
+ - name: "kuku3"
+ image: "b.gcr.io/kuar/etcd:2.2.0"
+ args:
+ - "--name=etcd0"
+ - "--advertise-client-urls=http://10.0.0.1:2379"
+ - "--listen-client-urls=http://0.0.0.0:2379"
+ - "--listen-peer-urls=http://0.0.0.0:2380"
+ - "--data-dir=/var/lib/etcd/data"
+ - "--wal-dir=/var/lib/etcd/wal"
+ - "--election-timeout=1000"
+ - "--heartbeat-interval=100"
+ - "--snapshot-count=10000"
+ - "--max-snapshots=5"
+ - "--max-wals=5"
+ - "--initial-advertise-peer-urls=http://10.0.0.1:2380"
+ - "--initial-cluster=etcd0=http://10.0.0.1:2380,etcd1=http://10.0.0.2:2380,etcd2=http://10.0.0.2:2380"
+ - "--initial-cluster-state=new"
+ - "--initial-cluster-token=cluster0"
+ ports:
+ - name: client
+ containerPort: 2379
+ protocol: "TCP"
+ - name: peer
+ containerPort: 2380
+ protocol: "TCP"
+ resources:
+ limits:
+ cpu: "1000m"
+ memory: "256Mi"
+ volumeMounts:
+ - name: "etcd-data"
+ mountPath: /var/lib/etcd/data
+ - name: "etcd-wal"
+ mountPath: /var/lib/etcd/wal
+ volumes:
+ - name: "etcd-wal"
+ awsElasticBlockStore:
+ volumeID: vol-1234wal0
+ fsType: ext4
+ - name: "etcd-data"
+ awsElasticBlockStore:
+ volumeID: vol-1234data0
+ fsType: ext4
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-PASSED.yaml b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-PASSED.yaml
new file mode 100644
index 0000000000..fb90b52566
--- /dev/null
+++ b/tests/kubernetes/checks/example_PeerClientCertAuthTrue/PeerClientCertAuthTrue-PASSED.yaml
@@ -0,0 +1,52 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: etcd
+ namespace: should-pass
+spec:
+ hostNetwork: true
+ containers:
+ - name: "kuku2"
+ image: "b.gcr.io/kuar/etcd:2.2.0"
+ args:
+ - "--name=etcd0"
+ - "--advertise-client-urls=http://10.0.0.1:2379"
+ - "--listen-client-urls=http://0.0.0.0:2379"
+ - "--listen-peer-urls=http://0.0.0.0:2380"
+ - "--data-dir=/var/lib/etcd/data"
+ - "--wal-dir=/var/lib/etcd/wal"
+ - "--election-timeout=1000"
+ - "--heartbeat-interval=100"
+ - "--snapshot-count=10000"
+ - "--max-snapshots=5"
+ - "--max-wals=5"
+ - "--initial-advertise-peer-urls=http://10.0.0.1:2380"
+ - "--initial-cluster=etcd0=http://10.0.0.1:2380,etcd1=http://10.0.0.2:2380,etcd2=http://10.0.0.2:2380"
+ - "--initial-cluster-state=new"
+ - "--initial-cluster-token=cluster0"
+ - "--peer-client-cert-auth=true"
+ ports:
+ - name: client
+ containerPort: 2379
+ protocol: "TCP"
+ - name: peer
+ containerPort: 2380
+ protocol: "TCP"
+ resources:
+ limits:
+ cpu: "1000m"
+ memory: "256Mi"
+ volumeMounts:
+ - name: "etcd-data"
+ mountPath: /var/lib/etcd/data
+ - name: "etcd-wal"
+ mountPath: /var/lib/etcd/wal
+ volumes:
+ - name: "etcd-wal"
+ awsElasticBlockStore:
+ volumeID: vol-1234wal0
+ fsType: ext4
+ - name: "etcd-data"
+ awsElasticBlockStore:
+ volumeID: vol-1234data0
+ fsType: ext4
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED-2.yaml b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED-2.yaml
new file mode 100644
index 0000000000..fc321e29d2
--- /dev/null
+++ b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED-2.yaml
@@ -0,0 +1,46 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-scheduler
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED.yaml b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED.yaml
new file mode 100644
index 0000000000..21c75ba88d
--- /dev/null
+++ b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-scheduler
+ - --bind-address=0.0.0.0
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-PASSED.yaml b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-PASSED.yaml
new file mode 100644
index 0000000000..9c8e29c125
--- /dev/null
+++ b/tests/kubernetes/checks/example_SchedulerBindAddress/SchedulerBindAddress-PASSED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-scheduler
+ - --bind-address=127.0.0.1
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
diff --git a/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-FAILED.yaml b/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-FAILED.yaml
new file mode 100644
index 0000000000..c5d6fb0452
--- /dev/null
+++ b/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-FAILED.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-apiserver
+ tier: control-plane
+ name: kube-apiserver
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-scheduler
+ - --profiling=true
+ image: gcr.io/google_containers/kube-apiserver-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-apiserver
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-PASSED.yaml b/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-PASSED.yaml
new file mode 100644
index 0000000000..df1a3a3b6c
--- /dev/null
+++ b/tests/kubernetes/checks/example_SchedulerProfiling/SchedulerProfiling-PASSED.yaml
@@ -0,0 +1,48 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ component: kube-scheduler
+ tier: control-plane
+ name: kube-scheduler
+ namespace: kube-system
+spec:
+ containers:
+ - command:
+ - kube-scheduler
+ - --profiling=false
+ image: gcr.io/google_containers/kube-scheduler-amd64:v1.6.0
+ livenessProbe:
+ failureThreshold: 8
+ httpGet:
+ host: 127.0.0.1
+ path: /healthz
+ port: 6443
+ scheme: HTTPS
+ initialDelaySeconds: 15
+ timeoutSeconds: 15
+ name: kube-scheduler
+ resources:
+ requests:
+ cpu: 250m
+ volumeMounts:
+ - mountPath: /etc/kubernetes/
+ name: k8s
+ readOnly: true
+ - mountPath: /etc/ssl/certs
+ name: certs
+ - mountPath: /etc/pki
+ name: pki
+ hostNetwork: true
+ volumes:
+ - hostPath:
+ path: /etc/kubernetes
+ name: k8s
+ - hostPath:
+ path: /etc/ssl/certs
+ name: certs
+ - hostPath:
+ path: /etc/pki
+ name: pki
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_Seccomp/cronjob-seccomp-PASSED.yaml b/tests/kubernetes/checks/example_Seccomp/cronjob-seccomp-PASSED.yaml
new file mode 100644
index 0000000000..193ae6e552
--- /dev/null
+++ b/tests/kubernetes/checks/example_Seccomp/cronjob-seccomp-PASSED.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+ name: cronjob-passed
+spec:
+ schedule: "0 * * * *"
+ jobTemplate:
+ spec:
+ template:
+ metadata:
+ annotations:
+ seccomp.security.alpha.kubernetes.io/pod: runtime/default
+ spec:
+ securityContext:
+ runAsUser: 1000
+ runAsGroup: 3000
+ fsGroup: 2000
+ volumes:
+ - name: sec-ctx-vol
+ emptyDir: {}
+ containers:
+ - name: sec-ctx-demo
+ image: busybox
+ command: ["sh", "-c", "sleep 1h"]
+ volumeMounts:
+ - name: sec-ctx-vol
+ mountPath: /data/demo
+ securityContext:
+ allowPrivilegeEscalation: false
diff --git a/tests/kubernetes/checks/example_Seccomp/pod-seccomp-FAILED2.yaml b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-FAILED2.yaml
new file mode 100644
index 0000000000..701cbee767
--- /dev/null
+++ b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-FAILED2.yaml
@@ -0,0 +1,60 @@
+apiVersion: "apps/v1"
+kind: "Deployment"
+metadata:
+ name: "app-cert-manager"
+ namespace: "infra"
+ labels:
+ app: "cert-manager"
+ app.kubernetes.io/name: "cert-manager"
+ app.kubernetes.io/instance: "app-cert-manager"
+ helm.sh/chart: "cert-manager-v0.11.0"
+spec:
+ replicas: "2"
+ selector:
+ matchLabels:
+ app: "cert-manager"
+ app.kubernetes.io/name: "cert-manager"
+ app.kubernetes.io/instance: "jetstack-cert-manager"
+ template:
+ metadata:
+ labels:
+ app: "cert-manager"
+ app.kubernetes.io/name: "cert-manager"
+ app.kubernetes.io/instance: "app-cert-manager"
+ helm.sh/chart: "cert-manager-v0.11.0"
+ annotations:
+ spec:
+ serviceAccountName: "app-cert-manager"
+ containers:
+ - name: "cert-manager"
+ image: "quay.io/app/cert-manager-controller:v0.11.0"
+ imagePullPolicy: "IfNotPresent"
+ args:
+ - "--v=2"
+ - "--cluster-resource-namespace=infra"
+ - "--leader-election-namespace=infra"
+ - "--default-issuer-name=letsencrypt-prod"
+ - "--default-issuer-kind=ClusterIssuer"
+ - "--webhook-namespace=$(POD_NAMESPACE)"
+ - "--webhook-ca-secret=app-cert-manager-webhook-ca"
+ - "--webhook-serving-secret=app-cert-manager-webhook-tls"
+ - "--webhook-dns-names=app-cert-manager-webhook,app-cert-manager-webhook.infra,app-cert-manager-webhook.infra.svc"
+ ports:
+ - containerPort: "9402"
+ env:
+ - name: "POD_NAMESPACE"
+ valueFrom:
+ fieldRef:
+ fieldPath: "metadata.namespace"
+ resources:
+ apiVersion: "apps/v1"
+ kind: "containers"
+ parent: "Deployment.app-cert-manager.infra (container 0)"
+ parent_metadata:
+ name: "jetstack-cert-manager"
+ namespace: "infra"
+ labels:
+ app: "cert-manager"
+ app.kubernetes.io/name: "cert-manager"
+ app.kubernetes.io/instance: "app-cert-manager"
+ helm.sh/chart: "cert-manager-v0.11.0"
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED4.yaml b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED4.yaml
new file mode 100644
index 0000000000..f594400be5
--- /dev/null
+++ b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED4.yaml
@@ -0,0 +1,23 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: seccomp-passed-deployment
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: test
+ template:
+ metadata:
+ labels:
+ app: test
+ spec:
+ containers:
+ - name: test
+ image: quay.io/test:0.1
+ ports:
+ - containerPort: 1234
+ securityContext:
+ allowPrivilegeEscalation: false
+ seccompProfile:
+ type: RuntimeDefault
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED5.yaml b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED5.yaml
new file mode 100644
index 0000000000..3b8bb62632
--- /dev/null
+++ b/tests/kubernetes/checks/example_Seccomp/pod-seccomp-PASSED5.yaml
@@ -0,0 +1,38 @@
+
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: RELEASE-NAME
+ labels:
+ app.kubernetes.io/instance: RELEASE-NAME
+ annotations:
+ namespace: default
+spec:
+ serviceName: RELEASE-NAME
+ replicas: 1
+ updateStrategy:
+ type: RollingUpdate
+ selector:
+ matchLabels:
+ app.kubernetes.io/instance: RELEASE-NAME
+ template:
+ metadata:
+ name: RELEASE-NAME
+ labels:
+ app.kubernetes.io/instance: RELEASE-NAME
+ annotations:
+ spec:
+ affinity:
+ podAffinity:
+ podAntiAffinity:
+ nodeAffinity:
+ automountServiceAccountToken: false
+ securityContext:
+ allowPrivilegeEscalation: false
+ fsGroup: 10001
+ seccompProfile:
+ type: RuntimeDefault
+ containers:
+ - name: RELEASE-NAME
+ image: docker.io/test:1
+ imagePullPolicy: "Always"
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_WildcardRoles/role-failed-1.yaml b/tests/kubernetes/checks/example_WildcardRoles/role-failed-1.yaml
new file mode 100644
index 0000000000..9f17d433a0
--- /dev/null
+++ b/tests/kubernetes/checks/example_WildcardRoles/role-failed-1.yaml
@@ -0,0 +1,10 @@
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: test-should-fail-1
+ namespace: test
+rules:
+- apiGroups: ["", "extensions", "apps"]
+ resources: ["*"]
+ verbs: [""]
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_WildcardRoles/role-failed-2.yaml b/tests/kubernetes/checks/example_WildcardRoles/role-failed-2.yaml
new file mode 100644
index 0000000000..06eae8c883
--- /dev/null
+++ b/tests/kubernetes/checks/example_WildcardRoles/role-failed-2.yaml
@@ -0,0 +1,10 @@
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: test-should-fail-2
+ namespace: test
+rules:
+- apiGroups: ["", "extensions", "apps"]
+ resources: ["example"]
+ verbs: ["example", "*"]
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_WildcardRoles/role-failed-3.yaml b/tests/kubernetes/checks/example_WildcardRoles/role-failed-3.yaml
new file mode 100644
index 0000000000..f84dae0524
--- /dev/null
+++ b/tests/kubernetes/checks/example_WildcardRoles/role-failed-3.yaml
@@ -0,0 +1,10 @@
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: test-should-fail-3
+ namespace: test
+rules:
+- apiGroups: ["*", "extensions", "apps"]
+ resources: ["example"]
+ verbs: ["example"]
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_WildcardRoles/role-passed-1.yaml b/tests/kubernetes/checks/example_WildcardRoles/role-passed-1.yaml
new file mode 100644
index 0000000000..557902185e
--- /dev/null
+++ b/tests/kubernetes/checks/example_WildcardRoles/role-passed-1.yaml
@@ -0,0 +1,10 @@
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: test-should-pass-3
+ namespace: test
+rules:
+- apiGroups: ["extensions", "apps"]
+ resources: ["example"]
+ verbs: ["example"]
\ No newline at end of file
diff --git a/tests/kubernetes/checks/example_WildcardRoles/role-passed-2.yaml b/tests/kubernetes/checks/example_WildcardRoles/role-passed-2.yaml
new file mode 100644
index 0000000000..b621b53e51
--- /dev/null
+++ b/tests/kubernetes/checks/example_WildcardRoles/role-passed-2.yaml
@@ -0,0 +1,7 @@
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: test-should-pass-2
+ namespace: test
+rules:
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_AllowPrivilegeEscalationPSP.py b/tests/kubernetes/checks/test_AllowPrivilegeEscalationPSP.py
index 2f497253b9..11b386854d 100644
--- a/tests/kubernetes/checks/test_AllowPrivilegeEscalationPSP.py
+++ b/tests/kubernetes/checks/test_AllowPrivilegeEscalationPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_AllowedCapabilities.py b/tests/kubernetes/checks/test_AllowedCapabilities.py
index 5dfacbcfd4..f67192c464 100644
--- a/tests/kubernetes/checks/test_AllowedCapabilities.py
+++ b/tests/kubernetes/checks/test_AllowedCapabilities.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['passed'], 3)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_AllowedCapabilitiesPSP.py b/tests/kubernetes/checks/test_AllowedCapabilitiesPSP.py
index cb863c5f84..d396e0a0d9 100644
--- a/tests/kubernetes/checks/test_AllowedCapabilitiesPSP.py
+++ b/tests/kubernetes/checks/test_AllowedCapabilitiesPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_ApiServerAdmissionControlAlwaysAdmit.py b/tests/kubernetes/checks/test_ApiServerAdmissionControlAlwaysAdmit.py
new file mode 100644
index 0000000000..f1f6bf373f
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAdmissionControlAlwaysAdmit.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAdmissionControlAlwaysAdmit import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAdmissionControlAlwaysAdmit(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAdmissionControlAlwaysAdmit"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAdmissionControlEventRateLimit.py b/tests/kubernetes/checks/test_ApiServerAdmissionControlEventRateLimit.py
new file mode 100644
index 0000000000..1a0538d9ef
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAdmissionControlEventRateLimit.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAdmissionControlEventRateLimit import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAdmissionControlEventRateLimit(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAdmissionControlEventRateLimit"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAlwaysPullImagesPlugin.py b/tests/kubernetes/checks/test_ApiServerAlwaysPullImagesPlugin.py
new file mode 100644
index 0000000000..c4bd78f865
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAlwaysPullImagesPlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAlwaysPullImagesPlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAlwaysPullImagesPlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAlwaysPullImagesPlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAnonymousAuth.py b/tests/kubernetes/checks/test_ApiServerAnonymousAuth.py
new file mode 100644
index 0000000000..608ce1f8d0
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAnonymousAuth.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAnonymousAuth import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAnonymousAuth(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAnonymousAuth"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuditLog.py b/tests/kubernetes/checks/test_ApiServerAuditLog.py
new file mode 100644
index 0000000000..44680da4b1
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuditLog.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuditLog import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuditLog"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuditLogMaxAge.py b/tests/kubernetes/checks/test_ApiServerAuditLogMaxAge.py
new file mode 100644
index 0000000000..f8779e7d9e
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuditLogMaxAge.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuditLogMaxAge import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAuditLogMaxAge(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuditLogMaxAge"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuditLogMaxBackup.py b/tests/kubernetes/checks/test_ApiServerAuditLogMaxBackup.py
new file mode 100644
index 0000000000..e8d7cee092
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuditLogMaxBackup.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuditLogMaxBackup import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuditLogMaxBackup"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuditLogMaxSize.py b/tests/kubernetes/checks/test_ApiServerAuditLogMaxSize.py
new file mode 100644
index 0000000000..acd9e52b3d
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuditLogMaxSize.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuditLogMaxSize import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAuditLogMaxSize(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuditLogMaxSize"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuthorizationModeNode.py b/tests/kubernetes/checks/test_ApiServerAuthorizationModeNode.py
new file mode 100644
index 0000000000..1df6b3e349
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuthorizationModeNode.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuthorizationModeNode import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAuthorizationModeNode(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuthorizationModeNode"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuthorizationModeNotAlwaysAllow.py b/tests/kubernetes/checks/test_ApiServerAuthorizationModeNotAlwaysAllow.py
new file mode 100644
index 0000000000..b397e09c2f
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuthorizationModeNotAlwaysAllow.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuthorizationModeNotAlwaysAllow import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAuthorizationModeNotAlwaysAllow(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuthorizationModeNotAlwaysAllow"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerAuthorizationModeRBAC.py b/tests/kubernetes/checks/test_ApiServerAuthorizationModeRBAC.py
new file mode 100644
index 0000000000..c54d88e5a7
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerAuthorizationModeRBAC.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerAuthorizationModeRBAC import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerAuthorizationModeRBAC(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerAuthorizationModeRBAC"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerBasicAuthFile.py b/tests/kubernetes/checks/test_ApiServerBasicAuthFile.py
new file mode 100644
index 0000000000..729341ae5f
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerBasicAuthFile.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerBasicAuthFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerBasicAuthFile"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerEncryptionProviders.py b/tests/kubernetes/checks/test_ApiServerEncryptionProviders.py
new file mode 100644
index 0000000000..5ff9d0fe54
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerEncryptionProviders.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerEncryptionProviders import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerEncryptionProviders(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerEncryptionProviders"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_ApiServerEtcdCaFile.py b/tests/kubernetes/checks/test_ApiServerEtcdCaFile.py
new file mode 100644
index 0000000000..893f4bfb7a
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerEtcdCaFile.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerEtcdCaFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerEtcdCaFile(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerEtcdCaFile"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_ApiServerEtcdCertAndKey.py b/tests/kubernetes/checks/test_ApiServerEtcdCertAndKey.py
new file mode 100644
index 0000000000..778233fab1
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerEtcdCertAndKey.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerEtcdCertAndKey import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerEtcdCertAndKey(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerEtcdCertAndKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerInsecureBindAddress.py b/tests/kubernetes/checks/test_ApiServerInsecureBindAddress.py
new file mode 100644
index 0000000000..aec253b111
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerInsecureBindAddress.py
@@ -0,0 +1,27 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerInsecureBindAddress import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerInsecureBindAddress(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerInsecureBindAddress"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerInsecurePort.py b/tests/kubernetes/checks/test_ApiServerInsecurePort.py
new file mode 100644
index 0000000000..a4b258b404
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerInsecurePort.py
@@ -0,0 +1,27 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerInsecurePort import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerInsecureBindAddress(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerInsecurePort"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerKubeletClientCertAndKey.py b/tests/kubernetes/checks/test_ApiServerKubeletClientCertAndKey.py
new file mode 100644
index 0000000000..59b75641b9
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerKubeletClientCertAndKey.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerKubeletClientCertAndKey import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerKubeletClientCertAndKey(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerKubeletClientCertAndKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_ApiServerKubeletHttps.py b/tests/kubernetes/checks/test_ApiServerKubeletHttps.py
new file mode 100644
index 0000000000..cba2d8b456
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerKubeletHttps.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerKubeletHttps import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class ApiServerKubeletHttps(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerKubeletHttps"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerNamespaceLifecyclePlugin.py b/tests/kubernetes/checks/test_ApiServerNamespaceLifecyclePlugin.py
new file mode 100644
index 0000000000..3b3fb2338c
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerNamespaceLifecyclePlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerNamespaceLifecyclePlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerNamespaceLifecyclePlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerNamespaceLifecyclePlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerNodeRestrictionPlugin.py b/tests/kubernetes/checks/test_ApiServerNodeRestrictionPlugin.py
new file mode 100644
index 0000000000..c7f466af65
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerNodeRestrictionPlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerNodeRestrictionPlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerNodeRestrictionPlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerNodeRestrictionPlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerPodSecurityPolicyPlugin.py b/tests/kubernetes/checks/test_ApiServerPodSecurityPolicyPlugin.py
new file mode 100644
index 0000000000..00322c7476
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerPodSecurityPolicyPlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerPodSecurityPolicyPlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerPodSecurityPolicyPlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerPodSecurityPolicyPlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerProfiling.py b/tests/kubernetes/checks/test_ApiServerProfiling.py
new file mode 100644
index 0000000000..6b6d823bff
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerProfiling.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerProfiling import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerProfiling"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerRequestTimeout.py b/tests/kubernetes/checks/test_ApiServerRequestTimeout.py
new file mode 100644
index 0000000000..9c5f9200a8
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerRequestTimeout.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerRequestTimeout import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerRequestTimeout(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerRequestTimeout"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerSecurePort.py b/tests/kubernetes/checks/test_ApiServerSecurePort.py
new file mode 100644
index 0000000000..197d4b9d07
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerSecurePort.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerSecurePort import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerSecurePort(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerSecurePort"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerSecurityContextDenyPlugin.py b/tests/kubernetes/checks/test_ApiServerSecurityContextDenyPlugin.py
new file mode 100644
index 0000000000..76f45b7787
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerSecurityContextDenyPlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerSecurityContextDenyPlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerSecurityContextDenyPlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerSecurityContextDenyPlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerServiceAccountKeyFile.py b/tests/kubernetes/checks/test_ApiServerServiceAccountKeyFile.py
new file mode 100644
index 0000000000..034e4273f9
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerServiceAccountKeyFile.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerServiceAccountKeyFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerServiceAccountKeyFile(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerServiceAccountKeyFile"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerServiceAccountLookup.py b/tests/kubernetes/checks/test_ApiServerServiceAccountLookup.py
new file mode 100644
index 0000000000..e1fb2a751f
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerServiceAccountLookup.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerServiceAccountLookup import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerServiceAccountLookup(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerServiceAccountLookup"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerServiceAccountPlugin.py b/tests/kubernetes/checks/test_ApiServerServiceAccountPlugin.py
new file mode 100644
index 0000000000..a55ed6c61b
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerServiceAccountPlugin.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerServiceAccountPlugin import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerServiceAccountPlugin(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerServiceAccountPlugin"
+
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerStrongCryptographicCiphers.py b/tests/kubernetes/checks/test_ApiServerStrongCryptographicCiphers.py
new file mode 100644
index 0000000000..a3b8f65fc1
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerStrongCryptographicCiphers.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerStrongCryptographicCiphers import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerStrongCryptographicCiphers(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerStrongCryptographicCiphers"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_ApiServerTlsCertAndKey.py b/tests/kubernetes/checks/test_ApiServerTlsCertAndKey.py
new file mode 100644
index 0000000000..59ce49fefb
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerTlsCertAndKey.py
@@ -0,0 +1,30 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerTlsCertAndKey import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerTlsCertAndKey(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerTlsCertAndKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerTokenAuthFile.py b/tests/kubernetes/checks/test_ApiServerTokenAuthFile.py
new file mode 100644
index 0000000000..6c43788943
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerTokenAuthFile.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerTokenAuthFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerTokenAuthFile"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_ApiServerkubeletCertificateAuthority.py b/tests/kubernetes/checks/test_ApiServerkubeletCertificateAuthority.py
new file mode 100644
index 0000000000..652522be43
--- /dev/null
+++ b/tests/kubernetes/checks/test_ApiServerkubeletCertificateAuthority.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.ApiServerkubeletCertificateAuthority import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestApiServerkubeletCertificateAuthority(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ApiServerkubeletCertificateAuthority"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_ControllerManagerBindAddress.py b/tests/kubernetes/checks/test_ControllerManagerBindAddress.py
new file mode 100644
index 0000000000..9356206763
--- /dev/null
+++ b/tests/kubernetes/checks/test_ControllerManagerBindAddress.py
@@ -0,0 +1,36 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.ControllerManagerBindAddress import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestControllerManagerBindAddress(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ControllerManagerBindAddress"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ with self.subTest(record=record):
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ with self.subTest(record=record):
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_DropCapabilities.py b/tests/kubernetes/checks/test_DropCapabilities.py
index 5e1849c60c..96494fdef0 100644
--- a/tests/kubernetes/checks/test_DropCapabilities.py
+++ b/tests/kubernetes/checks/test_DropCapabilities.py
@@ -16,8 +16,8 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 2)
- self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['passed'], 3)
+ self.assertEqual(summary['failed'], 2)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_DropCapabilitiesPSP.py b/tests/kubernetes/checks/test_DropCapabilitiesPSP.py
index e9e8b8c80c..6d10b18a5b 100644
--- a/tests/kubernetes/checks/test_DropCapabilitiesPSP.py
+++ b/tests/kubernetes/checks/test_DropCapabilitiesPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_EtcdAutoTls.py b/tests/kubernetes/checks/test_EtcdAutoTls.py
new file mode 100644
index 0000000000..c30c3325f2
--- /dev/null
+++ b/tests/kubernetes/checks/test_EtcdAutoTls.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.EtcdAutoTls import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEtcdAutoTls(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EtcdAutoTls"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_EtcdCertAndKey.py b/tests/kubernetes/checks/test_EtcdCertAndKey.py
new file mode 100644
index 0000000000..9980ded1a4
--- /dev/null
+++ b/tests/kubernetes/checks/test_EtcdCertAndKey.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.EtcdCertAndKey import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEtcdCertAndKey(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EtcdCertAndKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_EtcdClientCertAuth.py b/tests/kubernetes/checks/test_EtcdClientCertAuth.py
new file mode 100644
index 0000000000..0de10e0923
--- /dev/null
+++ b/tests/kubernetes/checks/test_EtcdClientCertAuth.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.EtcdClientCertAuth import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEtcdClientCertAuth(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EtcdClientCertAuth"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_EtcdPeerFiles.py b/tests/kubernetes/checks/test_EtcdPeerFiles.py
new file mode 100644
index 0000000000..0e98c37ed3
--- /dev/null
+++ b/tests/kubernetes/checks/test_EtcdPeerFiles.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.EtcdPeerFiles import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestEtcdPeerFiles(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_EtcdPeerFiles"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerBlockProfiles.py b/tests/kubernetes/checks/test_KubeControllerManagerBlockProfiles.py
new file mode 100644
index 0000000000..09adff4f59
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerBlockProfiles.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerBlockProfiles import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerBlockProfiles(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerBlockProfiles"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(1, summary['passed'])
+ self.assertEqual(2, summary['failed'])
+ self.assertEqual(0, summary['skipped'])
+ self.assertEqual(0, summary['parsing_errors'])
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerRootCAFile.py b/tests/kubernetes/checks/test_KubeControllerManagerRootCAFile.py
new file mode 100644
index 0000000000..28b2f5e739
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerRootCAFile.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerRootCAFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerRootCAFile(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerRootCAFile"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerRotateKubeletServerCertificate.py b/tests/kubernetes/checks/test_KubeControllerManagerRotateKubeletServerCertificate.py
new file mode 100644
index 0000000000..5dbc0af576
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerRotateKubeletServerCertificate.py
@@ -0,0 +1,36 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerRotateKubeletServerCertificate import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerRotateKubeletServerCertificate(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerRotateKubeletServerCertificate"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountCredentials.py b/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountCredentials.py
new file mode 100644
index 0000000000..a4381d1138
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountCredentials.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerServiceAccountCredentials import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerServiceAccountCredentials(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerServiceAccountCredentials"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(1, summary['passed'])
+ self.assertEqual(1, summary['failed'])
+ self.assertEqual(0, summary['skipped'])
+ self.assertEqual(0, summary['parsing_errors'])
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountPrivateKeyFile.py b/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountPrivateKeyFile.py
new file mode 100644
index 0000000000..ab8f5e7a18
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerServiceAccountPrivateKeyFile.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerServiceAccountPrivateKeyFile import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerServiceAccountPrivateKeyFile(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerServiceAccountPrivateKeyFile"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeControllerManagerTerminatedPods.py b/tests/kubernetes/checks/test_KubeControllerManagerTerminatedPods.py
new file mode 100644
index 0000000000..e73ec70ccc
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeControllerManagerTerminatedPods.py
@@ -0,0 +1,31 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeControllerManagerTerminatedPods import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeControllerManagerTerminatedPods(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeControllerManagerTerminatedPods"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeletAnonymousAuth.py b/tests/kubernetes/checks/test_KubeletAnonymousAuth.py
new file mode 100644
index 0000000000..5f66ba2584
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletAnonymousAuth.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletAnonymousAuth import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletAnonymousAuth(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletAnonymousAuth"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeletAuthorizationModeNotAlwaysAllow.py b/tests/kubernetes/checks/test_KubeletAuthorizationModeNotAlwaysAllow.py
new file mode 100644
index 0000000000..1b2b9f46b6
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletAuthorizationModeNotAlwaysAllow.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletAuthorizationModeNotAlwaysAllow import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletAuthorizationModeNotAlwaysAllow(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletAuthorizationModeNotAlwaysAllow"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeletClientCa.py b/tests/kubernetes/checks/test_KubeletClientCa.py
new file mode 100644
index 0000000000..6163208d2d
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletClientCa.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletClientCa import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletClientCa(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletClientCa"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletCryptographicCiphers.py b/tests/kubernetes/checks/test_KubeletCryptographicCiphers.py
new file mode 100644
index 0000000000..f004e26e25
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletCryptographicCiphers.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletCryptographicCiphers import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletCryptographicCiphers(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletCryptographicCiphers"
+ report = runner.run(root_folder=test_files_dir,
+ runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletHostnameOverride.py b/tests/kubernetes/checks/test_KubeletHostnameOverride.py
new file mode 100644
index 0000000000..2a680f9f4c
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletHostnameOverride.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletHostnameOverride import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletHostnameOverride(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletHostnameOverride"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubeletKeyFilesSetAppropriate.py b/tests/kubernetes/checks/test_KubeletKeyFilesSetAppropriate.py
new file mode 100644
index 0000000000..998795fccc
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletKeyFilesSetAppropriate.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletKeyFilesSetAppropriate import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletKeyFilesSetAppropriate(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletKeyFilesSetAppropriate"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletMakeIptablesUtilChains.py b/tests/kubernetes/checks/test_KubeletMakeIptablesUtilChains.py
new file mode 100644
index 0000000000..f0457acaa4
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletMakeIptablesUtilChains.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletMakeIptablesUtilChains import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletMakeIptablesUtilChains(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletMakeIptablesUtilChains"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletProtectKernelDefaults.py b/tests/kubernetes/checks/test_KubeletProtectKernelDefaults.py
new file mode 100644
index 0000000000..2020a7ade4
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletProtectKernelDefaults.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletProtectKernelDefaults import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletProtectKernelDefaults(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletProtectKernelDefaults"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletReadOnlyPort.py b/tests/kubernetes/checks/test_KubeletReadOnlyPort.py
new file mode 100644
index 0000000000..bc523f964c
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletReadOnlyPort.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletReadOnlyPort import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletReadOnlyPort(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletReadOnlyPort"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubeletStreamingConnectionIdleTimeout.py b/tests/kubernetes/checks/test_KubeletStreamingConnectionIdleTimeout.py
new file mode 100644
index 0000000000..7486b2a8e1
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubeletStreamingConnectionIdleTimeout.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubeletStreamingConnectionIdleTimeout import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubeletStreamingConnectionIdleTimeout(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubeletStreamingConnectionIdleTimeout"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubletEventCapture.py b/tests/kubernetes/checks/test_KubletEventCapture.py
new file mode 100644
index 0000000000..b5f2b8ba7b
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubletEventCapture.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubletEventCapture import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubletEventCapture(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubletEventCapture"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_KubletRotateCertificates.py b/tests/kubernetes/checks/test_KubletRotateCertificates.py
new file mode 100644
index 0000000000..5d4bf1e0a5
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubletRotateCertificates.py
@@ -0,0 +1,37 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubletRotateCertificates import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubletRotateCertificates(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubletRotateCertificates"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ for record in report.failed_checks:
+ with self.subTest(record=record):
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ with self.subTest(record=record):
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_KubletRotateKubeletServerCertificate.py b/tests/kubernetes/checks/test_KubletRotateKubeletServerCertificate.py
new file mode 100644
index 0000000000..e5d68f879a
--- /dev/null
+++ b/tests/kubernetes/checks/test_KubletRotateKubeletServerCertificate.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.KubletRotateKubeletServerCertificate import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestKubletRotateKubeletServerCertificate(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubletRotateKubeletServerCertificate"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_MinimizeCapabilities.py b/tests/kubernetes/checks/test_MinimizeCapabilities.py
index 3931f75b62..10c4ad29b0 100644
--- a/tests/kubernetes/checks/test_MinimizeCapabilities.py
+++ b/tests/kubernetes/checks/test_MinimizeCapabilities.py
@@ -16,8 +16,8 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 3)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_MinimizeCapabilitiesPSP.py b/tests/kubernetes/checks/test_MinimizeCapabilitiesPSP.py
index 36ed68c951..fafe29557e 100644
--- a/tests/kubernetes/checks/test_MinimizeCapabilitiesPSP.py
+++ b/tests/kubernetes/checks/test_MinimizeCapabilitiesPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_PeerClientCertAuthTrue.py b/tests/kubernetes/checks/test_PeerClientCertAuthTrue.py
new file mode 100644
index 0000000000..044b886aab
--- /dev/null
+++ b/tests/kubernetes/checks/test_PeerClientCertAuthTrue.py
@@ -0,0 +1,27 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.PeerClientCertAuthTrue import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestPeerClientCertAuthTrue(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_PeerClientCertAuthTrue"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+ self.assertEqual(1, summary['passed'])
+ self.assertEqual(2, summary['failed'])
+ for failed in report.failed_checks:
+ self.assertIn("should-fail", failed.resource)
+ for passed in report.passed_checks:
+ self.assertIn("should-pass", passed.resource)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_PrivilegedContainersPSP.py b/tests/kubernetes/checks/test_PrivilegedContainersPSP.py
index e143edee2f..30062f64e1 100644
--- a/tests/kubernetes/checks/test_PrivilegedContainersPSP.py
+++ b/tests/kubernetes/checks/test_PrivilegedContainersPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_SchedulerBindAddressy.py b/tests/kubernetes/checks/test_SchedulerBindAddressy.py
new file mode 100644
index 0000000000..db99e6e6b9
--- /dev/null
+++ b/tests/kubernetes/checks/test_SchedulerBindAddressy.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.SchedulerBindAddress import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestSchedulerBindAddress(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SchedulerBindAddress"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 2)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/checks/test_SchedulerProfiling.py b/tests/kubernetes/checks/test_SchedulerProfiling.py
new file mode 100644
index 0000000000..608809317d
--- /dev/null
+++ b/tests/kubernetes/checks/test_SchedulerProfiling.py
@@ -0,0 +1,38 @@
+
+import os
+import unittest
+
+from checkov.kubernetes.checks.SchedulerProfiling import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestSchedulerProfiling(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SchedulerProfiling"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+ for record in report.failed_checks:
+ self.assertIn("FAILED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+ for record in report.passed_checks:
+ self.assertIn("PASSED", record.file_path)
+ self.assertIn(record.check_id, [check.id])
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
\ No newline at end of file
diff --git a/tests/kubernetes/checks/test_Seccomp.py b/tests/kubernetes/checks/test_Seccomp.py
index 01530f4323..0ba7ad115d 100644
--- a/tests/kubernetes/checks/test_Seccomp.py
+++ b/tests/kubernetes/checks/test_Seccomp.py
@@ -16,8 +16,8 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 3)
- self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['passed'], 6)
+ self.assertEqual(summary['failed'], 2)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_SeccompPSP.py b/tests/kubernetes/checks/test_SeccompPSP.py
index c571d5401a..58b8445ebb 100644
--- a/tests/kubernetes/checks/test_SeccompPSP.py
+++ b/tests/kubernetes/checks/test_SeccompPSP.py
@@ -17,7 +17,7 @@ def test_summary(self):
summary = report.get_summary()
self.assertEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 1)
+ self.assertEqual(summary['failed'], 2)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_ShareHostIPCPSP.py b/tests/kubernetes/checks/test_ShareHostIPCPSP.py
index 8743289df3..8c5c71b095 100644
--- a/tests/kubernetes/checks/test_ShareHostIPCPSP.py
+++ b/tests/kubernetes/checks/test_ShareHostIPCPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_ShareHostPIDPSP.py b/tests/kubernetes/checks/test_ShareHostPIDPSP.py
index 9d5033c31b..81004636b5 100644
--- a/tests/kubernetes/checks/test_ShareHostPIDPSP.py
+++ b/tests/kubernetes/checks/test_ShareHostPIDPSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_SharedHostNetworkNamespacePSP.py b/tests/kubernetes/checks/test_SharedHostNetworkNamespacePSP.py
index a5e09df357..cbcab38bd6 100644
--- a/tests/kubernetes/checks/test_SharedHostNetworkNamespacePSP.py
+++ b/tests/kubernetes/checks/test_SharedHostNetworkNamespacePSP.py
@@ -16,7 +16,7 @@ def test_summary(self):
report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()
- self.assertEqual(summary['passed'], 1)
+ self.assertEqual(summary['passed'], 2)
self.assertEqual(summary['failed'], 1)
self.assertEqual(summary['skipped'], 0)
self.assertEqual(summary['parsing_errors'], 0)
diff --git a/tests/kubernetes/checks/test_WildcardRoles.py b/tests/kubernetes/checks/test_WildcardRoles.py
new file mode 100644
index 0000000000..62e8217711
--- /dev/null
+++ b/tests/kubernetes/checks/test_WildcardRoles.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.kubernetes.checks.WildcardRoles import check
+from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestWildcardRoles(unittest.TestCase):
+
+ def test_summary(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_WildcardRoles"
+ report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ 'Role.test-should-pass-3.test',
+ 'Role.test-should-pass-2.test'
+ }
+ failing_resources = {
+ 'Role.test-should-fail-1.test',
+ 'Role.test-should-fail-2.test',
+ 'ClusterRole.test-should-fail-3.test'
+ }
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 3)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/kubernetes/runner/list_annotation/example.yaml b/tests/kubernetes/runner/list_annotation/example.yaml
new file mode 100644
index 0000000000..875522c687
--- /dev/null
+++ b/tests/kubernetes/runner/list_annotation/example.yaml
@@ -0,0 +1,7 @@
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+ name: myName
+ description: description
+ annotations:
+ - backstage.io/techdocs-ref: url:https://github.com/test/repo
diff --git a/tests/kubernetes/runner/resources/example_multiple.yaml b/tests/kubernetes/runner/resources/example_multiple.yaml
new file mode 100644
index 0000000000..41266fc24a
--- /dev/null
+++ b/tests/kubernetes/runner/resources/example_multiple.yaml
@@ -0,0 +1,45 @@
+---
+# Source: a/templates/pod_disruption_budget.yaml
+---
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+ name: a
+ namespace: a
+spec:
+ maxUnavailable: 1
+ selector:
+ matchLabels:
+ app: a
+
+---
+# Source: a/templates/service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: a
+ labels:
+ app: a
+ owner: core
+ helm.sh/chart: a-0.0.0
+ app.kubernetes.io/instance: release-name
+ app.kubernetes.io/managed-by: Tiller
+ version: "a"
+ CostProduct: a
+ CostTech: "k8s"
+spec:
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 8000
+ selector:
+ app: a
+
+---
+# Source: a/templates/deployment.yaml
+---
+
+---
+# Source: a/templates/cron_job.yaml
+
+
diff --git a/tests/kubernetes/runner/test_runner.py b/tests/kubernetes/runner/test_runner.py
index a205fc5734..e8974cd079 100644
--- a/tests/kubernetes/runner/test_runner.py
+++ b/tests/kubernetes/runner/test_runner.py
@@ -1,5 +1,8 @@
+import dis
+import inspect
import os
import unittest
+from pathlib import Path
from checkov.runner_filter import RunnerFilter
from checkov.kubernetes.runner import Runner
@@ -24,7 +27,7 @@ def test_record_relative_path_with_relative_dir(self):
runner_filter=RunnerFilter(framework='kubernetes', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -47,7 +50,7 @@ def test_record_relative_path_with_abs_dir(self):
runner_filter=RunnerFilter(framework='kubernetes', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -69,7 +72,7 @@ def test_record_relative_path_with_relative_file(self):
runner_filter=RunnerFilter(framework='kubernetes', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
@@ -91,11 +94,52 @@ def test_record_relative_path_with_abs_file(self):
runner_filter=RunnerFilter(framework='kubernetes', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
+ def test_list_metadata_annotations(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ scan_file_path = os.path.join(current_dir, "list_annotation", "example.yaml")
+ file_rel_path = os.path.relpath(scan_file_path)
+ runner = Runner()
+ try:
+ runner.run(root_folder=None, external_checks_dir=None, files=[file_rel_path],
+ runner_filter=RunnerFilter(framework='kubernetes'))
+ except:
+ self.assertTrue(False, "Could not run K8 runner on configuration")
+
+ def test_wrong_check_imports(self):
+ wrong_imports = ["arm", "cloudformation", "dockerfile", "helm", "serverless", "terraform"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
+ def test_parse_with_empty_blocks(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ scan_file_path = os.path.join(current_dir, "resources", "example_multiple.yaml")
+ file_rel_path = os.path.relpath(scan_file_path)
+ runner = Runner()
+ try:
+ report = runner.run(root_folder=None, external_checks_dir=None, files=[file_rel_path],
+ runner_filter=RunnerFilter(framework='kubernetes'))
+ # just check that something was parsed and scanned
+ self.assertGreater(len(report.failed_checks) + len(report.passed_checks), 0)
+ except:
+ self.assertTrue(False, "Could not run K8 runner on configuration")
+
def tearDown(self):
pass
diff --git a/tests/kubernetes/test_base_registry.py b/tests/kubernetes/test_base_registry.py
index bf6ec089bf..51847a7ff2 100644
--- a/tests/kubernetes/test_base_registry.py
+++ b/tests/kubernetes/test_base_registry.py
@@ -18,21 +18,41 @@ def test_run_by_id_specific_enable(self):
run_filter = RunnerFilter(checks=["CKV_1"], skip_checks=[])
self.assertTrue(instance._should_run_scan("CKV_1", {}, run_filter))
+ def test_run_by_id_specific_enable_bc_id(self):
+ instance = Registry()
+ run_filter = RunnerFilter(checks=["BC_CKV_1"], skip_checks=[])
+ self.assertTrue(instance._should_run_scan("CKV_1", {}, run_filter, "BC_CKV_1"))
+
def test_run_by_id_omitted_specific_enable(self):
instance = Registry()
run_filter = RunnerFilter(checks=["CKV_1"], skip_checks=[])
self.assertFalse(instance._should_run_scan("CKV_999", {}, run_filter))
+ def test_run_by_id_omitted_specific_enablebc_id(self):
+ instance = Registry()
+ run_filter = RunnerFilter(checks=["BC_CKV_1"], skip_checks=[])
+ self.assertFalse(instance._should_run_scan("CKV_999", {}, run_filter, "BC_CKV_999"))
+
def test_run_by_id_specific_disable(self):
instance = Registry()
run_filter = RunnerFilter(checks=[], skip_checks=["CKV_1"])
self.assertFalse(instance._should_run_scan("CKV_1", {}, run_filter))
+ def test_run_by_id_specific_disable_bc_id(self):
+ instance = Registry()
+ run_filter = RunnerFilter(checks=[], skip_checks=["BC_CKV_1"])
+ self.assertFalse(instance._should_run_scan("CKV_1", {}, run_filter, "BC_CKV_1"))
+
def test_run_by_id_omitted_specific_disable(self):
instance = Registry()
run_filter = RunnerFilter(checks=[], skip_checks=["CKV_1"])
self.assertTrue(instance._should_run_scan("CKV_999", {}, run_filter))
+ def test_run_by_id_omitted_specific_disable_bc_id(self):
+ instance = Registry()
+ run_filter = RunnerFilter(checks=[], skip_checks=["BC_CKV_1"])
+ self.assertTrue(instance._should_run_scan("CKV_999", {}, run_filter, "BC_CKV_999"))
+
def test_run_by_id_external(self):
instance = Registry()
run_filter = RunnerFilter(checks=[], skip_checks=["CKV_1"])
@@ -43,7 +63,7 @@ def test_run_by_id_external2(self):
instance = Registry()
run_filter = RunnerFilter(checks=["CKV_1"], skip_checks=["CKV_2"])
run_filter.notify_external_check("CKV_EXT_999")
- self.assertTrue(instance._should_run_scan("CKV_EXT_999", {}, run_filter))
+ self.assertFalse(instance._should_run_scan("CKV_EXT_999", {}, run_filter))
def test_run_by_id_external3(self):
instance = Registry()
@@ -51,6 +71,12 @@ def test_run_by_id_external3(self):
run_filter.notify_external_check("CKV_EXT_999")
self.assertTrue(instance._should_run_scan("CKV_EXT_999", {}, run_filter))
+ def test_run_by_id_external4(self):
+ instance = Registry()
+ run_filter = RunnerFilter(checks=["CKV_1"], skip_checks=["CKV_2"], all_external=True)
+ run_filter.notify_external_check("CKV_EXT_999")
+ self.assertTrue(instance._should_run_scan("CKV_EXT_999", {}, run_filter))
+
def test_run_by_id_external_disabled(self):
instance = Registry()
run_filter = RunnerFilter(checks=[], skip_checks=["CKV_1", "CKV_EXT_999"])
diff --git a/tests/secrets/__init__.py b/tests/secrets/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/secrets/resources/cfn/secret.yml b/tests/secrets/resources/cfn/secret.yml
new file mode 100644
index 0000000000..b6c0cd7ba4
--- /dev/null
+++ b/tests/secrets/resources/cfn/secret.yml
@@ -0,0 +1,23 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: AWS CloudFormation Template to deploy insecure infrastructure
+
+Resources:
+ AnalysisLambda:
+ Type: AWS::Lambda::Function
+ Properties:
+ FunctionName: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ Runtime: nodejs12.x
+ Role: !GetAtt IAM4Lambda.Arn
+ Handler: exports.test
+ Code:
+ ZipFile: |
+ console.log("Hello World");
+ Environment:
+ Variables:
+ access_key: "AKIAIOSFODNN7EXAMPLE"
+ secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+ Tags:
+ - Key: Name
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}-analysis"
+ - Key: Environment
+ Value: !Sub "${AWS::AccountId}-${CompanyName}-${Environment}"
diff --git a/tests/secrets/resources/terraform/main.tf b/tests/secrets/resources/terraform/main.tf
new file mode 100644
index 0000000000..15e9c3d5b4
--- /dev/null
+++ b/tests/secrets/resources/terraform/main.tf
@@ -0,0 +1,4 @@
+resource "local_file" "npmrc" {
+ content = "no"
+ filename = ".npmrc"
+}
\ No newline at end of file
diff --git a/tests/secrets/resources/terraform_failed/main.tf b/tests/secrets/resources/terraform_failed/main.tf
new file mode 100644
index 0000000000..05a08627f2
--- /dev/null
+++ b/tests/secrets/resources/terraform_failed/main.tf
@@ -0,0 +1,4 @@
+provider "aws" {
+ access_key = "AKIAIOSFODNN7EXAMPLE"
+ secret_key = "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"
+}
\ No newline at end of file
diff --git a/tests/secrets/resources/terraform_skip/main.tf b/tests/secrets/resources/terraform_skip/main.tf
new file mode 100644
index 0000000000..d775e7b6fa
--- /dev/null
+++ b/tests/secrets/resources/terraform_skip/main.tf
@@ -0,0 +1,33 @@
+resource "aws_lambda_function" "skip" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ access_key = "AKIAIOSFODNN7EXAMPLE" #checkov:skip=CKV_SECRET_2:example
+ secret_key = ""
+ }
+ }
+}
+
+resource "aws_lambda_function" "wrong_skip" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ access_key = "AKIAIOS3F6KN7EXAMPLE" #checkov:skip=CKV_SECRET_5:wrong check id
+ secret_key = ""
+ }
+ }
+}
diff --git a/tests/secrets/test_plugin.py b/tests/secrets/test_plugin.py
new file mode 100644
index 0000000000..60a51aa0a8
--- /dev/null
+++ b/tests/secrets/test_plugin.py
@@ -0,0 +1,22 @@
+import unittest
+from checkov.secrets.plugins.entropy_keyword_combinator import EntropyKeywordCombinator
+
+class TestCombinatorPlugin(unittest.TestCase):
+
+ def setUp(self) -> None:
+ self.plugin = EntropyKeywordCombinator(4.5)
+
+ def test_positive_value(self):
+ result = self.plugin.analyze_line("mock.tf", "api_key = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMAAAKEY\"", 5)
+ self.assertEqual(1, len(result))
+ secret = result.pop()
+ self.assertEqual('Base64 High Entropy String', secret.type)
+ self.assertEqual('c00f1a6e4b20aa64691d50781b810756d6254b8e', secret.secret_hash)
+
+ def test_negative_keyword_value(self):
+ result = self.plugin.analyze_line("mock.tf", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMAAAKEY", 5)
+ self.assertEqual(0, len(result))
+
+ def test_negative_entropy_value(self):
+ result = self.plugin.analyze_line("mock.tf", "api_key = var.api_key", 5)
+ self.assertEqual(0, len(result))
diff --git a/tests/secrets/test_runner.py b/tests/secrets/test_runner.py
new file mode 100644
index 0000000000..62c2451ae9
--- /dev/null
+++ b/tests/secrets/test_runner.py
@@ -0,0 +1,86 @@
+import unittest
+
+import os
+from pathlib import Path
+
+from checkov.secrets.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestRunnerValid(unittest.TestCase):
+
+ def test_runner_failing_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/cfn"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets'))
+ self.assertEqual(len(report.failed_checks), 2)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_passing_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/terraform"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets'))
+ self.assertEqual(len(report.passed_checks), 0)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.failed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_tf_failing_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/terraform_failed"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets'))
+ self.assertEqual(2, len(report.failed_checks))
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(report.skipped_checks, [])
+ report.print_console()
+
+ def test_runner_tf_skip_check(self):
+ valid_dir_path = Path(__file__).parent / "resources/terraform_skip"
+
+ report = Runner().run(
+ root_folder=valid_dir_path,
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets')
+ )
+
+ self.assertEqual(len(report.skipped_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(len(report.skipped_checks), 1)
+
+ report.print_console()
+
+ def test_runner_skip_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources/cfn"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets', skip_checks=['CKV_SECRET_2']))
+ self.assertEqual(len(report.skipped_checks), 1)
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+
+ def test_runner_multiple_files(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_dir_path = current_dir + "/resources"
+ runner = Runner()
+ report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='secrets'))
+ self.assertEqual(5, len(report.failed_checks))
+ self.assertEqual(report.parsing_errors, [])
+ self.assertEqual(report.passed_checks, [])
+ self.assertEqual(len(report.skipped_checks), 1)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/serverless/runner/resources/serverless.yml b/tests/serverless/runner/resources/serverless.yml
index 976d0c7150..3251620dc5 100644
--- a/tests/serverless/runner/resources/serverless.yml
+++ b/tests/serverless/runner/resources/serverless.yml
@@ -28,4 +28,11 @@ layers:
functions:
myFunction:
- handler: myfunction.invoke
\ No newline at end of file
+ handler: myfunction.invoke
+
+resources:
+ Resources:
+ ApiGatewayRestApi:
+ Properties:
+ BinaryMediaTypes:
+ - "*/*"
\ No newline at end of file
diff --git a/tests/serverless/runner/test_runner.py b/tests/serverless/runner/test_runner.py
index b63bd4f43b..6475839169 100644
--- a/tests/serverless/runner/test_runner.py
+++ b/tests/serverless/runner/test_runner.py
@@ -1,5 +1,8 @@
+import dis
+import inspect
import os
import unittest
+from pathlib import Path
from checkov.runner_filter import RunnerFilter
from checkov.serverless.runner import Runner
@@ -24,7 +27,7 @@ def test_record_relative_path_with_relative_dir(self):
runner_filter=RunnerFilter(framework='serverless', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -47,7 +50,7 @@ def test_record_relative_path_with_abs_dir(self):
runner_filter=RunnerFilter(framework='serverless', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -69,7 +72,7 @@ def test_record_relative_path_with_relative_file(self):
runner_filter=RunnerFilter(framework='serverless', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
@@ -91,11 +94,28 @@ def test_record_relative_path_with_abs_file(self):
runner_filter=RunnerFilter(framework='serverless', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
for record in all_checks:
# no need to join with a '/' because the CFN runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
+ def test_wrong_check_imports(self):
+ wrong_imports = ["arm", "cloudformation", "dockerfile", "helm", "kubernetes", "terraform"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
def tearDown(self):
pass
diff --git a/tests/terraform/checks/data/aws/example_CloudsplainingIAMWrite/main.tf b/tests/terraform/checks/data/aws/example_CloudsplainingIAMWrite/main.tf
new file mode 100644
index 0000000000..a6f990e688
--- /dev/null
+++ b/tests/terraform/checks/data/aws/example_CloudsplainingIAMWrite/main.tf
@@ -0,0 +1,49 @@
+# pass
+
+data "aws_iam_policy_document" "restrictable" {
+ version = "2012-10-17"
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "s3:*",
+ ]
+ resources = [
+ "arn:aws:s3:::bucket",
+ ]
+ }
+}
+
+data "aws_iam_policy_document" "unrestrictable" {
+ version = "2012-10-17"
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "xray:PutTelemetryRecords",
+ "xray:PutTraceSegments",
+ ]
+ resources = [
+ "*",
+ ]
+ }
+}
+
+# fail
+
+data "aws_iam_policy_document" "fail" {
+ version = "2012-10-17"
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "s3:*",
+ ]
+ resources = [
+ "*",
+ ]
+ }
+}
+
+# unknown
+
diff --git a/tests/terraform/checks/data/aws/test_AdminPolicyDocument.py b/tests/terraform/checks/data/aws/test_AdminPolicyDocument.py
index 752a64d8fa..6f5d46288e 100644
--- a/tests/terraform/checks/data/aws/test_AdminPolicyDocument.py
+++ b/tests/terraform/checks/data/aws/test_AdminPolicyDocument.py
@@ -1,5 +1,7 @@
import unittest
+import hcl2
+
from checkov.terraform.checks.data.aws.AdminPolicyDocument import check
from checkov.common.models.enums import CheckResult
@@ -16,6 +18,36 @@ def test_failure(self):
scan_result = check.scan_data_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_failure_no_effect(self):
+ resource_conf = {'version': ['2012-10-17'], 'statement': [{'actions': [['*']], 'resources': [['*']]}]}
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_passed_list_statement(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "default" {
+ statement = [{
+ actions = ["s3:GetObject"]
+
+ resources = ["${aws_s3_bucket.default.arn}/*"]
+
+ principals {
+ type = "AWS"
+ identifiers = ["*"]
+ }
+ }]
+
+ # Support replication ARNs
+ statement = ["${flatten(data.aws_iam_policy_document.replication.*.statement)}"]
+
+ # Support deployment ARNs
+ statement = ["${flatten(data.aws_iam_policy_document.deployment.*.statement)}"]
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['default']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_CloudSplainingCredentialsExposure.py b/tests/terraform/checks/data/aws/test_CloudSplainingCredentialsExposure.py
new file mode 100644
index 0000000000..91013b45b4
--- /dev/null
+++ b/tests/terraform/checks/data/aws/test_CloudSplainingCredentialsExposure.py
@@ -0,0 +1,109 @@
+import unittest
+
+import hcl2
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.data.aws.IAMCredentialsExposure import check
+
+
+class TestcloudsplainingPrivilegeEscalation(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "s3:GetObject",
+ "iam:CreateAccessKey"
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "lambda:CreateFunction",
+ "lambda:CreateEventSourceMapping",
+ "dynamodb:CreateTable",
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_allowed_credential_actions(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "ecr:GetAuthorizationToken",
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_deny(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "DenyOutsideCallers" {
+ statement {
+ sid = "DenyOutsideCallers"
+ effect = "Deny"
+ actions = ["*"]
+ resources = ["*"]
+
+ condition {
+ test = "NotIpAddress"
+ variable = "aws:SourceIp"
+ values = [
+ "1.2.3.4/16"
+ ]
+ }
+
+ condition {
+ test = "Bool"
+ variable = "aws:ViaAWSService"
+ values = ["false"]
+ }
+ }
+ }
+ """)
+
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['DenyOutsideCallers']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_CloudSplainingDataExfiltration.py b/tests/terraform/checks/data/aws/test_CloudSplainingDataExfiltration.py
new file mode 100644
index 0000000000..899c31da9b
--- /dev/null
+++ b/tests/terraform/checks/data/aws/test_CloudSplainingDataExfiltration.py
@@ -0,0 +1,64 @@
+import unittest
+
+import hcl2
+from checkov.terraform.checks.data.aws.IAMDataExfiltration import check
+
+from checkov.common.models.enums import CheckResult
+
+
+class TestcloudsplainingDataExfiltration(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "iam:PassRole",
+ "ssm:GetParameter",
+ "s3:GetObject",
+ "ssm:GetParameter",
+ "ssm:GetParameters",
+ "ssm:GetParametersByPath",
+ "secretsmanager:GetSecretValue",
+ "s3:PutObject",
+ "ec2:CreateTags"
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "lambda:CreateFunction",
+ "lambda:CreateEventSourceMapping",
+ "dynamodb:CreateTable",
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_CloudSplainingPrivilegeEscalation.py b/tests/terraform/checks/data/aws/test_CloudSplainingPrivilegeEscalation.py
new file mode 100644
index 0000000000..4a6847f2b4
--- /dev/null
+++ b/tests/terraform/checks/data/aws/test_CloudSplainingPrivilegeEscalation.py
@@ -0,0 +1,60 @@
+import unittest
+
+import hcl2
+from checkov.terraform.checks.data.aws.IAMPrivilegeEscalation import check
+
+from checkov.common.models.enums import CheckResult
+
+
+class TestcloudsplainingPrivilegeEscalation(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "iam:PassRole",
+ "lambda:CreateFunction",
+ "lambda:CreateEventSourceMapping",
+ "dynamodb:CreateTable",
+ "dynamodb:PutItem",
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "lambda:CreateFunction",
+ "lambda:CreateEventSourceMapping",
+ "dynamodb:CreateTable",
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_CloudsplainingIAMWrite.py b/tests/terraform/checks/data/aws/test_CloudsplainingIAMWrite.py
new file mode 100644
index 0000000000..0a5486ad99
--- /dev/null
+++ b/tests/terraform/checks/data/aws/test_CloudsplainingIAMWrite.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.data.aws.IAMWriteAccess import check
+from checkov.terraform.runner import Runner
+
+
+class TestCloudsplainingIAMWrite(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_CloudsplainingIAMWrite"
+
+ report = Runner().run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_iam_policy_document.restrictable",
+ "aws_iam_policy_document.unrestrictable",
+ }
+ failing_resources = {
+ "aws_iam_policy_document.fail",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_CloudsplainingPermissionsManagement.py b/tests/terraform/checks/data/aws/test_CloudsplainingPermissionsManagement.py
new file mode 100644
index 0000000000..84f9431079
--- /dev/null
+++ b/tests/terraform/checks/data/aws/test_CloudsplainingPermissionsManagement.py
@@ -0,0 +1,54 @@
+import unittest
+
+import hcl2
+from checkov.terraform.checks.data.aws.IAMPermissionsManagement import check
+
+from checkov.common.models.enums import CheckResult
+
+
+class TestCloudsplainingIAMWrite(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "iam:*"
+ ]
+
+ resources = [
+ "*",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ data "aws_iam_policy_document" "example" {
+ statement {
+ sid = "1"
+ effect = "Allow"
+
+ actions = [
+ "s3:*"
+ ]
+
+ resources = [
+ "foo",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['data'][0]['aws_iam_policy_document']['example']
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/data/aws/test_StarActionPolicyDocument.py b/tests/terraform/checks/data/aws/test_StarActionPolicyDocument.py
index 2e8021472c..3a2e418bb6 100644
--- a/tests/terraform/checks/data/aws/test_StarActionPolicyDocument.py
+++ b/tests/terraform/checks/data/aws/test_StarActionPolicyDocument.py
@@ -1,5 +1,7 @@
import unittest
+import hcl2
+
from checkov.terraform.checks.data.aws.StarActionPolicyDocument import check
from checkov.common.models.enums import CheckResult
@@ -17,6 +19,12 @@ def test_success(self):
scan_result = check.scan_data_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_unknown(self):
+ resource_conf = {'statement': [[{'actions': ['s3:GetObject'], 'principals': {'identifiers': ['*'], 'type': 'AWS'}, 'resources': ['aws_s3_bucket.default.arn/*']}], 'flatten(data.aws_iam_policy_document.deployment.*.statement)', 'flatten(data.aws_iam_policy_document.replication.*.statement)']}
+
+ scan_result = check.scan_data_conf(resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
def test_failure(self):
resource_conf = {
"statement": [{
@@ -28,6 +36,26 @@ def test_failure(self):
scan_result = check.scan_data_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_failure_no_effect(self):
+ resource_conf = {
+ "statement": [{
+ "actions": [["*"]],
+ "resources": [["arn:aws:s3:::my_corporate_bucket/*"]]
+ }]
+ }
+ scan_result = check.scan_data_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_flatten_operator(self):
+ conf = hcl2.loads("""
+ data "aws_iam_policy_document" "mock_policy" {
+ statement = flatten(var.policy_json, [])
+ }
+ """)
+
+ scan_result = check.scan_data_conf(conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/data/test_base_data_check.py b/tests/terraform/checks/data/test_base_data_check.py
new file mode 100644
index 0000000000..3a92312678
--- /dev/null
+++ b/tests/terraform/checks/data/test_base_data_check.py
@@ -0,0 +1,43 @@
+import pytest
+
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.data.base_check import BaseDataCheck
+
+
+class TestStaticCheck(BaseDataCheck):
+ # for pytest not to collect this class as tests
+ __test__ = False
+
+ def __init__(self):
+ name = "Test something"
+ id = "CKV_TEST_2"
+ supported_data = ["ckv_test"]
+ categories = [CheckCategories.CONVENTION]
+ super().__init__(name=name, id=id, categories=categories, supported_data=supported_data)
+
+ def scan_data_conf(self, conf):
+ if "check_result" in conf.keys():
+ check_result = conf["check_result"][0]
+ if check_result:
+ return CheckResult.PASSED
+
+ return CheckResult.FAILED
+
+ return CheckResult.UNKNOWN
+
+
+@pytest.mark.parametrize(
+ "conf,expected",
+ [
+ ({"check_result": [True]}, CheckResult.PASSED),
+ ({"check_result": [False]}, CheckResult.FAILED),
+ ({"foo": ["bar"]}, CheckResult.UNKNOWN),
+ ({"count": [0], "check_result": [True]}, CheckResult.UNKNOWN),
+ ({"count": [1], "check_result": [True]}, CheckResult.PASSED),
+ ],
+ ids=["pass", "fail", "unknown", "count_zero", "count_one"],
+)
+def test_scan_entity_conf(conf, expected):
+ result = TestStaticCheck().scan_entity_conf(conf, "ckv_test")
+
+ assert result == expected
diff --git a/tests/terraform/checks/data/test_registry.py b/tests/terraform/checks/data/test_registry.py
index ff39c8dc1a..318359791f 100644
--- a/tests/terraform/checks/data/test_registry.py
+++ b/tests/terraform/checks/data/test_registry.py
@@ -25,7 +25,7 @@ def test_without_init(self, mock_path_exists):
def test_registry_external_check_load(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
external_dir = current_dir + "/example_external_dir/extra_checks"
- self.registry.load_external_checks(external_dir, RunnerFilter())
+ self.registry.load_external_checks(external_dir)
external_check_loaded = False
external_check = None
diff --git a/tests/terraform/checks/example_WildcardEntities/main.tf b/tests/terraform/checks/example_WildcardEntities/main.tf
index 96f4798bda..60d05fb48c 100644
--- a/tests/terraform/checks/example_WildcardEntities/main.tf
+++ b/tests/terraform/checks/example_WildcardEntities/main.tf
@@ -4,6 +4,10 @@ locals {
bucket_name = var.bucket_name
}
+variable "user_exists" {
+ default = false
+}
+
resource "aws_cognito_user_group" "user_group" {
name = "${var.customer_name}_group"
description = "${var.customer_name} user group"
diff --git a/tests/terraform/checks/module/registry/test_registry.py b/tests/terraform/checks/module/registry/test_registry.py
index d6ee010844..cbfa38f290 100644
--- a/tests/terraform/checks/module/registry/test_registry.py
+++ b/tests/terraform/checks/module/registry/test_registry.py
@@ -24,7 +24,7 @@ def test_without_init(self, mock_path_exists):
def test_registry_external_check_load(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
external_dir = current_dir + "/example_external_dir/extra_checks"
- self.registry.load_external_checks(external_dir, RunnerFilter())
+ self.registry.load_external_checks(external_dir)
external_check_loaded = False
external_check = None
diff --git a/tests/terraform/checks/resource/aws/example_ALBDropHttpHeaders/main.tf b/tests/terraform/checks/resource/aws/example_ALBDropHttpHeaders/main.tf
new file mode 100644
index 0000000000..4c120eb928
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_ALBDropHttpHeaders/main.tf
@@ -0,0 +1,71 @@
+# pass
+
+resource "aws_lb" "enabled" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ drop_invalid_header_fields = true
+}
+
+resource "aws_alb" "enabled" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ drop_invalid_header_fields = true
+}
+
+# failure
+
+resource "aws_lb" "default" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+}
+
+resource "aws_alb" "default" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+}
+
+resource "aws_lb" "disabled" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ drop_invalid_header_fields = false
+}
+
+resource "aws_alb" "disabled" {
+ internal = false
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ drop_invalid_header_fields = false
+}
+
+# unknown
+
+resource "aws_lb" "network" {
+ internal = false
+ load_balancer_type = "network"
+ name = "nlb"
+ subnets = var.public_subnet_ids
+}
+
+resource "aws_lb" "gateway" {
+ load_balancer_type = "gateway"
+ name = "glb"
+
+ subnet_mapping {
+ subnet_id = var.subnet_id
+ }
+}
diff --git a/tests/terraform/checks/resource/aws/example_AppLoadBalancerTLS12/main.tf b/tests/terraform/checks/resource/aws/example_AppLoadBalancerTLS12/main.tf
new file mode 100644
index 0000000000..a342e6f635
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_AppLoadBalancerTLS12/main.tf
@@ -0,0 +1,115 @@
+# pass
+
+resource "aws_lb_listener" "http_redirect" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "HTTP"
+ port = "80"
+
+ default_action {
+ type = "redirect"
+
+ redirect {
+ port = "443"
+ protocol = "HTTPS"
+ status_code = "HTTP_301"
+ }
+ }
+}
+
+resource "aws_lb_listener" "tcp" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "TCP"
+ port = "8080"
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "udp" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "UDP"
+ port = "8080"
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "tcp_udp" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "TCP_UDP"
+ port = "8080"
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "tls_fs_1_2" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "TLS"
+ port = "8080"
+ ssl_policy = "ELBSecurityPolicy-FS-1-2-Res-2019-08"
+ certificate_arn = var.certificate_arn
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "https_fs_1_2" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "HTTPS"
+ port = "443"
+ ssl_policy = "ELBSecurityPolicy-FS-1-2-Res-2019-08"
+ certificate_arn = var.certificate_arn
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+# failure
+
+resource "aws_lb_listener" "http" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "HTTP"
+ port = "80"
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "https_2016" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "HTTPS"
+ port = "443"
+ ssl_policy = "ELBSecurityPolicy-2016-08"
+ certificate_arn = var.certificate_arn
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
+
+resource "aws_lb_listener" "tls_fs_1_1" {
+ load_balancer_arn = var.aws_lb_arn
+ protocol = "TLS"
+ port = "8080"
+ ssl_policy = "ELBSecurityPolicy-FS-1-1-2019-08"
+ certificate_arn = var.certificate_arn
+
+ default_action {
+ type = "forward"
+ target_group_arn = var.aws_lb_target_group_arn
+ }
+}
diff --git a/tests/terraform/checks/resource/aws/example_AthenaWorkgroupEncryption/main.tf b/tests/terraform/checks/resource/aws/example_AthenaWorkgroupEncryption/main.tf
new file mode 100644
index 0000000000..d68fbd54b3
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_AthenaWorkgroupEncryption/main.tf
@@ -0,0 +1,30 @@
+resource "aws_athena_workgroup" "pass" {
+ name = "wg-encrypted"
+
+ configuration {
+ enforce_workgroup_configuration = true
+ publish_cloudwatch_metrics_enabled = true
+
+ result_configuration {
+ output_location = "s3://mys3bucket"
+ encryption_configuration {
+ encryption_option = "SSE_KMS"
+ kms_key_arn = "mykmsarn"
+ }
+ }
+ }
+}
+
+resource "aws_athena_workgroup" "fail" {
+ name = "wg-non-encrypted"
+
+ configuration {
+ enforce_workgroup_configuration = true
+ publish_cloudwatch_metrics_enabled = true
+
+ result_configuration {
+ output_location = "s3://mys3bucket"
+ }
+ }
+}
+
diff --git a/tests/terraform/checks/resource/aws/example_AutoScalingTagging/main.tf b/tests/terraform/checks/resource/aws/example_AutoScalingTagging/main.tf
new file mode 100644
index 0000000000..b70606afb1
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_AutoScalingTagging/main.tf
@@ -0,0 +1,58 @@
+resource "aws_autoscaling_group" "passtag" {
+ name = "foobar3-terraform-test"
+ max_size = 5
+ min_size = 2
+ health_check_grace_period = 300
+ health_check_type = "ELB"
+ desired_capacity = 4
+ force_delete = true
+ placement_group = aws_placement_group.test.id
+ launch_configuration = aws_launch_configuration.foobar.name
+ vpc_zone_identifier = [aws_subnet.example1.id, aws_subnet.example2.id]
+
+ tag {
+ key = "foo"
+ value = "bar"
+ propagate_at_launch = true
+ }
+
+ tag {
+ key = "lorem"
+ value = "ipsum"
+ propagate_at_launch = false
+ }
+}
+
+
+resource "aws_autoscaling_group" "passtags" {
+ name = "foobar3-terraform-test"
+ max_size = 5
+ min_size = 2
+ launch_configuration = aws_launch_configuration.foobar.name
+ vpc_zone_identifier = [aws_subnet.example1.id, aws_subnet.example2.id]
+
+ tags = concat(
+ [
+ {
+ "key" = "interpolation1"
+ "value" = "value3"
+ "propagate_at_launch" = true
+ },
+ {
+ "key" = "interpolation2"
+ "value" = "value4"
+ "propagate_at_launch" = true
+ },
+ ],
+ var.extra_tags,
+ )
+}
+
+
+resource "aws_autoscaling_group" "fail" {
+ name = "foobar3-terraform-test"
+ max_size = 5
+ min_size = 2
+ launch_configuration = aws_launch_configuration.foobar.name
+ vpc_zone_identifier = [aws_subnet.example1.id, aws_subnet.example2.id]
+}
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/example_BackupVaultEncrypted/main.tf b/tests/terraform/checks/resource/aws/example_BackupVaultEncrypted/main.tf
new file mode 100644
index 0000000000..21f470deb5
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_BackupVaultEncrypted/main.tf
@@ -0,0 +1,10 @@
+# fail
+resource "aws_backup_vault" "backup" {
+ name = "example_backup_vault"
+}
+
+# pass
+resource "aws_backup_vault" "backup_with_kms_key" {
+ name = "example_backup_vault"
+ kms_key_arn = aws_kms_key.example.arn
+}
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupKMSKey/main.tf b/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupKMSKey/main.tf
new file mode 100644
index 0000000000..a3ec45be21
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupKMSKey/main.tf
@@ -0,0 +1,8 @@
+resource "aws_cloudwatch_log_group" "pass" {
+ retention_in_days = 1
+ kms_key_id = "someKey"
+}
+
+resource "aws_cloudwatch_log_group" "fail" {
+ retention_in_days = 1
+}
diff --git a/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupRetention/main.tf b/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupRetention/main.tf
new file mode 100644
index 0000000000..4d0c91d380
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_CloudWatchLogGroupRetention/main.tf
@@ -0,0 +1,5 @@
+resource "aws_cloudwatch_log_group" "pass" {
+ retention_in_days = 3
+}
+
+resource "aws_cloudwatch_log_group" "fail" {}
diff --git a/tests/terraform/checks/resource/aws/example_CodeBuildProjectEncryption/main.tf b/tests/terraform/checks/resource/aws/example_CodeBuildProjectEncryption/main.tf
new file mode 100644
index 0000000000..760dfbf558
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_CodeBuildProjectEncryption/main.tf
@@ -0,0 +1,31 @@
+resource "aws_codebuild_project" "fail" {
+ name = "fail-project"
+ artifacts {
+ type = S3
+ encryption_disabled = true
+ }
+
+}
+
+resource "aws_codebuild_project" "no_artifacts_encryption_ignored" {
+ name = "no-art-project"
+ artifacts {
+ type = "NO_ARTIFACTS"
+ encryption_disabled = true
+ }
+}
+
+resource "aws_codebuild_project" "success_no_encryption_disabled" {
+ name = "default-project"
+ artifacts {
+ type = "S3"
+ }
+}
+
+resource "aws_codebuild_project" "success" {
+ name = "success-project"
+ artifacts {
+ type = "S3"
+ encryption_disabled = false
+ }
+}
diff --git a/tests/terraform/checks/resource/aws/example_DBInstanceLogging/main.tf b/tests/terraform/checks/resource/aws/example_DBInstanceLogging/main.tf
new file mode 100644
index 0000000000..623da921b2
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_DBInstanceLogging/main.tf
@@ -0,0 +1,41 @@
+# pass
+
+resource "aws_db_instance" "postgres" {
+ allocated_storage = 5
+ engine = "postgres"
+ instance_class = "db.t3.small"
+ password = "postgres"
+ username = "postgres"
+
+ enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
+}
+
+resource "aws_db_instance" "mysql" {
+ allocated_storage = 5
+ engine = "mysql"
+ instance_class = "db.t3.small"
+ password = "mysql"
+ username = "mysql"
+
+ enabled_cloudwatch_logs_exports = ["general", "error", "slowquery"]
+}
+
+# failure
+
+resource "aws_db_instance" "default" {
+ allocated_storage = 5
+ engine = "mysql"
+ instance_class = "db.t3.small"
+ password = "mysql"
+ username = "mysql"
+}
+
+resource "aws_db_instance" "empty" {
+ allocated_storage = 5
+ engine = "mysql"
+ instance_class = "db.t3.small"
+ password = "mysql"
+ username = "mysql"
+
+ enabled_cloudwatch_logs_exports = []
+}
diff --git a/tests/terraform/checks/resource/aws/example_EBSDefaultEncryption/main.tf b/tests/terraform/checks/resource/aws/example_EBSDefaultEncryption/main.tf
new file mode 100644
index 0000000000..1fc02d1ff5
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_EBSDefaultEncryption/main.tf
@@ -0,0 +1,14 @@
+# pass
+
+resource "aws_ebs_encryption_by_default" "enabled" {
+ enabled = true
+}
+
+resource "aws_ebs_encryption_by_default" "default" {
+}
+
+# failure
+
+resource "aws_ebs_encryption_by_default" "disabled" {
+ enabled = false
+}
diff --git a/tests/terraform/checks/resource/aws/example_ELBv2AccessLogs/main.tf b/tests/terraform/checks/resource/aws/example_ELBv2AccessLogs/main.tf
new file mode 100644
index 0000000000..d67a30af43
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_ELBv2AccessLogs/main.tf
@@ -0,0 +1,91 @@
+# pass
+
+resource "aws_lb" "enabled" {
+ load_balancer_type = "network"
+ name = "nlb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ enabled = true
+ }
+}
+
+resource "aws_alb" "enabled" {
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ enabled = true
+ }
+}
+
+# failure
+
+resource "aws_lb" "default" {
+ load_balancer_type = "network"
+ name = "nlb"
+ subnets = var.public_subnet_ids
+}
+
+resource "aws_alb" "default" {
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+}
+
+resource "aws_lb" "only_bucket" {
+ load_balancer_type = "network"
+ name = "nlb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ }
+}
+
+resource "aws_alb" "only_bucket" {
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ }
+}
+
+resource "aws_lb" "disabled" {
+ load_balancer_type = "network"
+ name = "nlb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ enabled = false
+ }
+}
+
+resource "aws_alb" "disabled" {
+ load_balancer_type = "application"
+ name = "alb"
+ subnets = var.public_subnet_ids
+
+ access_logs {
+ bucket = var.bucket_name
+ enabled = false
+ }
+}
+
+# unknown
+
+resource "aws_lb" "gateway" {
+ name = "glb"
+ load_balancer_type = "gateway"
+
+ subnet_mapping {
+ subnet_id = var.subnet_id
+ }
+}
+
diff --git a/tests/terraform/checks/resource/aws/example_EMRClusterIsEncryptedKMS/main.tf b/tests/terraform/checks/resource/aws/example_EMRClusterIsEncryptedKMS/main.tf
new file mode 100644
index 0000000000..677647fc19
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_EMRClusterIsEncryptedKMS/main.tf
@@ -0,0 +1,44 @@
+resource "aws_emr_security_configuration" "fail" {
+ name = "fail"
+
+ configuration = < 1
+ }
+
+ node_to_node_encryption {
+ enabled = true
+ }
+}
+
+resource "aws_elasticsearch_domain" "old_hcl" {
+ domain_name = "old_hcl"
+
+ cluster_config = {
+ instance_count = 2
+ }
+
+ node_to_node_encryption = {
+ enabled = true
+ }
+}
+
+# fail
+resource "aws_elasticsearch_domain" "node_to_node_encryption_disabled" {
+ domain_name = "node_to_node_encryption_disabled"
+
+ cluster_config {
+ instance_count = 2 // a value > 1
+ }
+
+ node_to_node_encryption {
+ enabled = false
+ }
+}
+
+resource "aws_elasticsearch_domain" "node_to_node_encryption_doesnt_exist" {
+ domain_name = "node_to_node_encryption_doesnt_exist"
+
+ cluster_config {
+ instance_count = 2 // a value > 1
+ }
+}
+
+# unknown
+resource "aws_elasticsearch_domain" "instance_count_not_number" {
+ domain_name = "instance_count_not_number"
+
+ cluster_config {
+ instance_count = "not_int"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/example_GlacierVaultAnyPrincipal/main.tf b/tests/terraform/checks/resource/aws/example_GlacierVaultAnyPrincipal/main.tf
new file mode 100644
index 0000000000..6cab65ecef
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_GlacierVaultAnyPrincipal/main.tf
@@ -0,0 +1,146 @@
+# pass
+resource "aws_glacier_vault" "my_archive1" {
+ name = "MyArchive"
+
+ access_policy = < 0 ? True : False}'],
- 'volume_size': ['${var.ebs_volume_size}'], 'volume_type': ['${var.ebs_volume_type}'],
- 'iops': ['${var.ebs_iops}']}],
- 'encrypt_at_rest': [{'enabled': [False], 'kms_key_id': ['${var.encrypt_at_rest_kms_key_id}']}],
- 'cluster_config': [{'instance_count': ['${var.foo}'], 'instance_type': ['${var.instance_type}'],
- 'dedicated_master_enabled': ['${var.dedicated_master_enabled}'],
- 'dedicated_master_count': ['${var.dedicated_master_count}'],
- 'dedicated_master_type': ['${var.dedicated_master_type}'],
- 'zone_awareness_enabled': ['${var.zone_awareness_enabled}'],
- 'zone_awareness_config': [
- {'availability_zone_count': ['${var.availability_zone_count}']}]}],
- 'vpc_options': [
- {'security_group_ids': [['${join("",aws_security_group.default.*.id)}']],
- 'subnet_ids': ['${var.subnet_ids}']}], 'snapshot_options': [
- {'automated_snapshot_start_hour': ['${var.automated_snapshot_start_hour}']}],
- 'log_publishing_options': [
- {'enabled': ['${var.log_publishing_index_enabled}'], 'log_type': ['INDEX_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_index_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_search_enabled}'], 'log_type': ['SEARCH_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_search_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_application_enabled}'],
- 'log_type': ['ES_APPLICATION_LOGS'], 'cloudwatch_log_group_arn': [
- '${var.log_publishing_application_cloudwatch_log_group_arn}']}],
- 'tags': ['${module.label.tags}'], 'depends_on': [['${aws_iam_service_linked_role.default}']]}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.UNKNOWN, scan_result)
-
- def test_failure(self):
- resource_conf = {'count': ['${var.enabled ? 1 : 0}'], 'domain_name': ['${module.label.id}'],
- 'elasticsearch_version': ['${var.elasticsearch_version}'],
- 'advanced_options': ['${var.advanced_options}'], 'ebs_options': [
- {'ebs_enabled': ['${var.ebs_volume_size > 0 ? True : False}'],
- 'volume_size': ['${var.ebs_volume_size}'], 'volume_type': ['${var.ebs_volume_type}'],
- 'iops': ['${var.ebs_iops}']}],
- 'encrypt_at_rest': [{'enabled': [False], 'kms_key_id': ['${var.encrypt_at_rest_kms_key_id}']}],
- 'cluster_config': [{'instance_count': 3, 'instance_type': ['${var.instance_type}'],
- 'dedicated_master_enabled': ['${var.dedicated_master_enabled}'],
- 'dedicated_master_count': ['${var.dedicated_master_count}'],
- 'dedicated_master_type': ['${var.dedicated_master_type}'],
- 'zone_awareness_enabled': ['${var.zone_awareness_enabled}'],
- 'zone_awareness_config': [
- {'availability_zone_count': ['${var.availability_zone_count}']}]}],
- 'node_to_node_encryption': [{'enabled': False}], 'vpc_options': [
- {'security_group_ids': [['${join("",aws_security_group.default.*.id)}']],
- 'subnet_ids': ['${var.subnet_ids}']}], 'snapshot_options': [
- {'automated_snapshot_start_hour': ['${var.automated_snapshot_start_hour}']}],
- 'log_publishing_options': [
- {'enabled': ['${var.log_publishing_index_enabled}'], 'log_type': ['INDEX_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_index_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_search_enabled}'], 'log_type': ['SEARCH_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_search_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_application_enabled}'],
- 'log_type': ['ES_APPLICATION_LOGS'], 'cloudwatch_log_group_arn': [
- '${var.log_publishing_application_cloudwatch_log_group_arn}']}],
- 'tags': ['${module.label.tags}'], 'depends_on': [['${aws_iam_service_linked_role.default}']]}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
-
- def test_failure_node_to_node_encryption_missing(self):
- resource_conf = {'count': ['${var.enabled ? 1 : 0}'], 'domain_name': ['${module.label.id}'],
- 'elasticsearch_version': ['${var.elasticsearch_version}'],
- 'advanced_options': ['${var.advanced_options}'], 'ebs_options': [
- {'ebs_enabled': ['${var.ebs_volume_size > 0 ? True : False}'],
- 'volume_size': ['${var.ebs_volume_size}'], 'volume_type': ['${var.ebs_volume_type}'],
- 'iops': ['${var.ebs_iops}']}],
- 'encrypt_at_rest': [{'enabled': [False], 'kms_key_id': ['${var.encrypt_at_rest_kms_key_id}']}],
- 'cluster_config': [{'instance_count': 3, 'instance_type': ['${var.instance_type}'],
- 'dedicated_master_enabled': ['${var.dedicated_master_enabled}'],
- 'dedicated_master_count': ['${var.dedicated_master_count}'],
- 'dedicated_master_type': ['${var.dedicated_master_type}'],
- 'zone_awareness_enabled': ['${var.zone_awareness_enabled}'],
- 'zone_awareness_config': [
- {'availability_zone_count': ['${var.availability_zone_count}']}]}],
- 'vpc_options': [
- {'security_group_ids': [['${join("",aws_security_group.default.*.id)}']],
- 'subnet_ids': ['${var.subnet_ids}']}], 'snapshot_options': [
- {'automated_snapshot_start_hour': ['${var.automated_snapshot_start_hour}']}],
- 'log_publishing_options': [
- {'enabled': ['${var.log_publishing_index_enabled}'], 'log_type': ['INDEX_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_index_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_search_enabled}'], 'log_type': ['SEARCH_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_search_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_application_enabled}'],
- 'log_type': ['ES_APPLICATION_LOGS'], 'cloudwatch_log_group_arn': [
- '${var.log_publishing_application_cloudwatch_log_group_arn}']}],
- 'tags': ['${module.label.tags}'], 'depends_on': [['${aws_iam_service_linked_role.default}']]}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
-
- def test_success(self):
- resource_conf = {'count': ['${var.enabled ? 1 : 0}'], 'domain_name': ['${module.label.id}'],
- 'elasticsearch_version': ['${var.elasticsearch_version}'],
- 'advanced_options': ['${var.advanced_options}'], 'ebs_options': [
- {'ebs_enabled': ['${var.ebs_volume_size > 0 ? True : False}'],
- 'volume_size': ['${var.ebs_volume_size}'], 'volume_type': ['${var.ebs_volume_type}'],
- 'iops': ['${var.ebs_iops}']}],
- 'encrypt_at_rest': [{'enabled': [True], 'kms_key_id': ['${var.encrypt_at_rest_kms_key_id}']}],
- 'cluster_config': [{'instance_count': 3, 'instance_type': ['${var.instance_type}'],
- 'dedicated_master_enabled': ['${var.dedicated_master_enabled}'],
- 'dedicated_master_count': ['${var.dedicated_master_count}'],
- 'dedicated_master_type': ['${var.dedicated_master_type}'],
- 'zone_awareness_enabled': ['${var.zone_awareness_enabled}'],
- 'zone_awareness_config': [
- {'availability_zone_count': ['${var.availability_zone_count}']}]}],
- 'node_to_node_encryption': [{'enabled': True}], 'vpc_options': [
- {'security_group_ids': [['${join("",aws_security_group.default.*.id)}']],
- 'subnet_ids': ['${var.subnet_ids}']}], 'snapshot_options': [
- {'automated_snapshot_start_hour': ['${var.automated_snapshot_start_hour}']}],
- 'log_publishing_options': [
- {'enabled': ['${var.log_publishing_index_enabled}'], 'log_type': ['INDEX_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_index_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_search_enabled}'], 'log_type': ['SEARCH_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_search_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_application_enabled}'],
- 'log_type': ['ES_APPLICATION_LOGS'], 'cloudwatch_log_group_arn': [
- '${var.log_publishing_application_cloudwatch_log_group_arn}']}],
- 'tags': ['${module.label.tags}'], 'depends_on': [['${aws_iam_service_linked_role.default}']]}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.PASSED, scan_result)
-
- def test_success_single_node(self):
- resource_conf = {'count': ['${var.enabled ? 1 : 0}'], 'domain_name': ['${module.label.id}'],
- 'elasticsearch_version': ['${var.elasticsearch_version}'],
- 'advanced_options': ['${var.advanced_options}'], 'ebs_options': [
- {'ebs_enabled': ['${var.ebs_volume_size > 0 ? True : False}'],
- 'volume_size': ['${var.ebs_volume_size}'], 'volume_type': ['${var.ebs_volume_type}'],
- 'iops': ['${var.ebs_iops}']}],
- 'encrypt_at_rest': [{'enabled': [True], 'kms_key_id': ['${var.encrypt_at_rest_kms_key_id}']}],
- 'cluster_config': [{'instance_count': 1, 'instance_type': ['${var.instance_type}'],
- 'dedicated_master_enabled': ['${var.dedicated_master_enabled}'],
- 'dedicated_master_count': ['${var.dedicated_master_count}'],
- 'dedicated_master_type': ['${var.dedicated_master_type}'],
- 'zone_awareness_enabled': ['${var.zone_awareness_enabled}'],
- 'zone_awareness_config': [
- {'availability_zone_count': ['${var.availability_zone_count}']}]}],
- 'node_to_node_encryption': [{'enabled': False}], 'vpc_options': [
- {'security_group_ids': [['${join("",aws_security_group.default.*.id)}']],
- 'subnet_ids': ['${var.subnet_ids}']}], 'snapshot_options': [
- {'automated_snapshot_start_hour': ['${var.automated_snapshot_start_hour}']}],
- 'log_publishing_options': [
- {'enabled': ['${var.log_publishing_index_enabled}'], 'log_type': ['INDEX_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_index_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_search_enabled}'], 'log_type': ['SEARCH_SLOW_LOGS'],
- 'cloudwatch_log_group_arn': ['${var.log_publishing_search_cloudwatch_log_group_arn}']},
- {'enabled': ['${var.log_publishing_application_enabled}'],
- 'log_type': ['ES_APPLICATION_LOGS'], 'cloudwatch_log_group_arn': [
- '${var.log_publishing_application_cloudwatch_log_group_arn}']}],
- 'tags': ['${module.label.tags}'], 'depends_on': [['${aws_iam_service_linked_role.default}']]}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.PASSED, scan_result)
-
-
-if __name__ == '__main__':
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_ElasticsearchNodeToNodeEncryption"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_elasticsearch_domain.without_cluster_config",
+ "aws_elasticsearch_domain.without_instance_count",
+ "aws_elasticsearch_domain.instance_count_not_bigger_than_1",
+ "aws_elasticsearch_domain.node_to_node_encryption_enabled",
+ "aws_elasticsearch_domain.old_hcl"
+ }
+ failing_resources = {
+ "aws_elasticsearch_domain.node_to_node_encryption_disabled",
+ "aws_elasticsearch_domain.node_to_node_encryption_doesnt_exist",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 5)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_GlacierVaultAnyPrincipal.py b/tests/terraform/checks/resource/aws/test_GlacierVaultAnyPrincipal.py
new file mode 100644
index 0000000000..7919615cd1
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_GlacierVaultAnyPrincipal.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.GlacierVaultAnyPrincipal import check
+from checkov.terraform.runner import Runner
+
+
+class TestBackupVaultEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_GlacierVaultAnyPrincipal"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_glacier_vault.my_archive1",
+ "aws_glacier_vault.my_archive6",
+ }
+ failing_resources = {
+ "aws_glacier_vault.my_archive2",
+ "aws_glacier_vault.my_archive3",
+ "aws_glacier_vault.my_archive4",
+ "aws_glacier_vault.my_archive5",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/test_IAMAdminPolicyDocument.py b/tests/terraform/checks/resource/aws/test_IAMAdminPolicyDocument.py
index 023c862971..7e6266f97a 100644
--- a/tests/terraform/checks/resource/aws/test_IAMAdminPolicyDocument.py
+++ b/tests/terraform/checks/resource/aws/test_IAMAdminPolicyDocument.py
@@ -1,41 +1,42 @@
+import os
import unittest
+from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.resource.aws.IAMAdminPolicyDocument import check
-from checkov.common.models.enums import CheckResult
-
-
-class TestAdminPolicyDocument(unittest.TestCase):
-
- def test_success(self):
- resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': ['{\n "Version": "2012-10-17", \n \
- "Statement": [\n {\n \
- "Action": [\n "ec2:Describe*"\n ],\n \
- "Effect": "Allow",\n \
- "Resource": "*"\n }\n ]\n}']}
- scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
- self.assertEqual(CheckResult.PASSED, scan_result)
-
- def test_failure(self):
- resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': ['{\n "Version": "2012-10-17", \n \
- "Statement": [\n {\n \
- "Action": [\n "*"\n ],\n \
- "Effect": "Allow",\n \
- "Resource": "*"\n }\n ]\n}']}
- scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
- self.assertEqual(CheckResult.FAILED, scan_result)
-
- def test_failure_multiple_statements(self):
- resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': [
- '{"Version":"2012-10-17","Statement":[{"Sid":"SqsAllow","Effect":"Allow","Action":['
- '"sqs:GetQueueAttributes","sqs:GetQueueUrl","sqs:ListDeadLetterSourceQueues",'
- '"sqs:ListQueues","sqs:ReceiveMessage","sqs:SendMessage","sqs:SendMessageBatch"],'
- '"Resource":"*"},{"Sid":"ALL","Effect":"Allow","Action":["*"],"Resource":["*"]}]} '
- ]}
- scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
- self.assertEqual(CheckResult.FAILED, scan_result)
+from checkov.terraform.runner import Runner
+
+
+class TestIAMAdminPolicyDocument(unittest.TestCase):
+
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_IAMAdminPolicyDocument"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ 'aws_iam_policy.pass1',
+ 'aws_iam_policy.pass2'
+ }
+ failing_resources = {
+ 'aws_iam_policy.fail1',
+ 'aws_iam_policy.fail2',
+ 'aws_iam_policy.fail3',
+ 'aws_iam_policy.fail4'
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary['passed'], 2)
+ self.assertEqual(summary['failed'], 4)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
if __name__ == '__main__':
diff --git a/tests/terraform/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py b/tests/terraform/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py
index a5277b5b3c..a991732941 100644
--- a/tests/terraform/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py
+++ b/tests/terraform/checks/resource/aws/test_IAMRoleAllowsPublicAssume.py
@@ -5,7 +5,7 @@
from checkov.common.models.enums import CheckResult
-class TestIAMRoleAllowAssumeFromAccount(unittest.TestCase):
+class TestIAMRoleAllowsPublicAssume(unittest.TestCase):
def test_failure(self):
hcl_res = hcl2.loads("""
diff --git a/tests/terraform/checks/resource/aws/test_IAMRoleAttachedToService.py b/tests/terraform/checks/resource/aws/test_IAMRoleAttachedToService.py
deleted file mode 100644
index 990664df5a..0000000000
--- a/tests/terraform/checks/resource/aws/test_IAMRoleAttachedToService.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import unittest
-
-from checkov.terraform.checks.resource.aws.IAMRoleAllowsPublicAssume import check
-from checkov.common.models.enums import CheckResult
-
-
-class TestIAMRoleAllowsPublicAssume(unittest.TestCase):
-
- def test_failure(self):
- resource_conf = {
- 'name': ['${var.name}-default'],
- 'assume_role_policy': ['{\n "Version": "2012-10-17",\n '
- '"Statement": [\n '
- '{\n '
- '"Action": "sts:AssumeRole",'
- '\n "Principal": {\n '
- '"AWS": '
- '"*"\n },'
- '\n "Effect": "Allow",'
- '\n "Sid": ""\n }\n ]\n}']}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
-
-
- def test_success(self):
- resource_conf = {
- 'name': ['${var.name}-default'],
- 'assume_role_policy': ['{\n "Version": "2012-10-17",\n '
- '"Statement": [\n '
- '{\n '
- '"Action": "sts:AssumeRole",'
- '\n "Principal": {\n '
- '"Service": '
- '"ecs-tasks.amazonaws.com"\n },'
- '\n "Effect": "Allow",'
- '\n "Sid": ""\n }\n ]\n}']}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.PASSED, scan_result)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_IAMStarActionPolicyDocument.py b/tests/terraform/checks/resource/aws/test_IAMStarActionPolicyDocument.py
index b18b4e844a..27816d6fa6 100644
--- a/tests/terraform/checks/resource/aws/test_IAMStarActionPolicyDocument.py
+++ b/tests/terraform/checks/resource/aws/test_IAMStarActionPolicyDocument.py
@@ -1,51 +1,49 @@
import unittest
-from checkov.terraform.checks.resource.aws.IAMStarActionPolicyDocument import check
from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.IAMStarActionPolicyDocument import check
-class TestAdminPolicyDocument(unittest.TestCase):
+class TestIAMStarActionPolicyDocument(unittest.TestCase):
def test_success(self):
resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': ['{\n "Version": "2012-10-17", \n \
- "Statement": [\n {\n \
- "Action": [\n "ec2:Describe*"\n ],\n \
- "Effect": "Allow",\n \
- "Resource": "abc*"\n }\n ]\n}']}
+ 'policy': ['{"Version": "2012-10-17", "Statement": [{"Action": ["ec2:Describe*"], "Effect": "Allow", "Resource": "abc*"}]}']}
scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
self.assertEqual(CheckResult.PASSED, scan_result)
def test_success_service_star(self):
resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': ['{\n "Version": "2012-10-17", \n \
- "Statement": [\n {\n \
- "Action": "ec2:*",\n \
- "Effect": "Allow",\n \
- "Resource": "abc*"\n }\n ]\n}']}
+ 'policy': [{'Version': '2012-10-17',
+ 'Statement': [{'Action': 'ec2:*', 'Effect': 'Allow', 'Resource': 'abc*'}]}]}
scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
self.assertEqual(CheckResult.PASSED, scan_result)
def test_failure(self):
resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
- 'policy': ['{\n "Version": "2012-10-17", \n \
- "Statement": [\n {\n \
- "Action": [\n "*"\n ],\n \
- "Effect": "Allow",\n \
- "Resource": "abc*"\n }\n ]\n}']}
+ 'policy': [{'Version': '2012-10-17',
+ 'Statement': [{'Action': ['*'], 'Effect': 'Allow', 'Resource': 'abc*'}]}]}
scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
self.assertEqual(CheckResult.FAILED, scan_result)
def test_failure_multiple_statements(self):
resource_conf = {'name': ['test'], 'user': ['${aws_iam_user.lb.name}'],
'policy': [
- '{"Version":"2012-10-17","Statement":[{"Sid":"SqsAllow","Effect":"Allow","Action":['
- '"sqs:GetQueueAttributes","sqs:GetQueueUrl","sqs:ListDeadLetterSourceQueues",'
- '"sqs:ListQueues","sqs:ReceiveMessage","sqs:SendMessage","sqs:SendMessageBatch"],'
- '"Resource":"*"},{"Sid":"ALL","Effect":"Allow","Action":["*"],"Resource":["${var.my_resource_arn}"]}]}'
+ {'Version': '2012-10-17', 'Statement': [{'Sid': 'SqsAllow', 'Effect': 'Allow',
+ 'Action': ['sqs:GetQueueAttributes',
+ 'sqs:GetQueueUrl',
+ 'sqs:ListDeadLetterSourceQueues',
+ 'sqs:ListQueues', 'sqs:ReceiveMessage',
+ 'sqs:SendMessage',
+ 'sqs:SendMessageBatch'],
+ 'Resource': '*'},
+ {'Sid': 'ALL', 'Effect': 'Allow', 'Action': ['*'],
+ 'Resource': ['${var.my_resource_arn}']}]}
+
]}
scan_result = check.scan_entity_conf(conf=resource_conf, entity_type='aws_iam_policy')
self.assertEqual(CheckResult.FAILED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_KMSKeyWildcardPrincipal.py b/tests/terraform/checks/resource/aws/test_KMSKeyWildcardPrincipal.py
new file mode 100644
index 0000000000..c39b5424e1
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_KMSKeyWildcardPrincipal.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.KMSKeyWildcardPrincipal import check
+from checkov.terraform.runner import Runner
+
+
+class TestKMSKeyWildcardPrincipal(unittest.TestCase):
+
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KMSKeyWildcardPrincipal"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ 'aws_kms_key.pass_0',
+ 'aws_kms_key.pass_1',
+ 'aws_kms_key.pass_2',
+ 'aws_kms_key.pass_3'
+ }
+ failing_resources = {
+ 'aws_kms_key.fail_0',
+ 'aws_kms_key.fail_1',
+ 'aws_kms_key.fail_2',
+ 'aws_kms_key.fail_3',
+ 'aws_kms_key.fail_4'
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+ self.assertEqual(summary['passed'], 4)
+ self.assertEqual(summary['failed'], 5)
+ self.assertEqual(summary['skipped'], 0)
+ self.assertEqual(summary['parsing_errors'], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_KMSRotation.py b/tests/terraform/checks/resource/aws/test_KMSRotation.py
index 7e5e580193..a2699f5bf1 100644
--- a/tests/terraform/checks/resource/aws/test_KMSRotation.py
+++ b/tests/terraform/checks/resource/aws/test_KMSRotation.py
@@ -1,36 +1,43 @@
+import os
import unittest
+from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.resource.aws.KMSRotation import check
-from checkov.common.models.enums import CheckResult
+from checkov.terraform.runner import Runner
class TestKMSRotation(unittest.TestCase):
- def test_success(self):
- resource_conf = {
- "description": "KMS key 1",
- "deletion_window_in_days": 10,
- "enable_key_rotation": [True]
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KMSRotation"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_kms_key.pass1",
+ "aws_kms_key.pass2",
+ "aws_kms_key.pass3",
}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.PASSED, scan_result)
-
- def test_failure(self):
- resource_conf = {
- "description": "KMS key 1",
- "deletion_window_in_days": 10,
- "enable_key_rotation": [False]
+ failing_resources = {
+ "aws_kms_key.fail1",
+ "aws_kms_key.fail2",
+ "aws_kms_key.fail3",
+ "aws_kms_key.fail4",
}
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
- def test_failure_on_missing_property(self):
- resource_conf = {
- "description": "KMS key 1",
- "deletion_window_in_days": 10,
- }
- scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 3)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
if __name__ == '__main__':
diff --git a/tests/terraform/checks/resource/aws/test_KubernetesSecretsEncryptedUsingCMK.py b/tests/terraform/checks/resource/aws/test_KubernetesSecretsEncryptedUsingCMK.py
new file mode 100644
index 0000000000..a10a3d7cc9
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_KubernetesSecretsEncryptedUsingCMK.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.KubernetesSecretsEncryptedUsingCMK import check
+from checkov.terraform.runner import Runner
+
+
+class TestKubernetesSecretsEncryptedUsingCMK(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_KubernetesSecretsEncryptedUsingCMK"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_eks_cluster.enabled",
+ }
+ failing_resources = {
+ "aws_eks_cluster.default",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_LBCrossZone.py b/tests/terraform/checks/resource/aws/test_LBCrossZone.py
new file mode 100644
index 0000000000..31800b2ea8
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_LBCrossZone.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.LBCrossZone import check
+from checkov.terraform.runner import Runner
+
+
+class TestLBCrossZone(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_LBCrossZone"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_lb.enabled",
+ "aws_alb.enabled",
+ }
+ failing_resources = {
+ "aws_lb.default",
+ "aws_alb.default",
+ "aws_lb.disabled",
+ "aws_alb.disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_LBDeletionProtection.py b/tests/terraform/checks/resource/aws/test_LBDeletionProtection.py
new file mode 100644
index 0000000000..d3191f069e
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_LBDeletionProtection.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.LBDeletionProtection import check
+from checkov.terraform.runner import Runner
+
+
+class TestLBDeletionProtection(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_LBDeletionProtection"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_lb.enabled",
+ "aws_alb.enabled",
+ }
+ failing_resources = {
+ "aws_lb.default",
+ "aws_alb.default",
+ "aws_lb.disabled",
+ "aws_alb.disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_LambdaDLQConfigured.py b/tests/terraform/checks/resource/aws/test_LambdaDLQConfigured.py
new file mode 100644
index 0000000000..88501d0fe2
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_LambdaDLQConfigured.py
@@ -0,0 +1,63 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.aws.LambdaDLQConfigured import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestLambdaDLQConfigured(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+
+ runtime = "nodejs12.x"
+
+ dead_letter_config {
+ target_arn = "test"
+ }
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_LambdaFunctionLevelConcurrentExecutionLimit.py b/tests/terraform/checks/resource/aws/test_LambdaFunctionLevelConcurrentExecutionLimit.py
new file mode 100644
index 0000000000..9c699a4e8b
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_LambdaFunctionLevelConcurrentExecutionLimit.py
@@ -0,0 +1,109 @@
+import unittest
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.LambdaFunctionLevelConcurrentExecutionLimit import check
+import hcl2
+
+
+class TestLambdaFunctionLevelConcurrentExecutionLimit(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ reserved_concurrent_executions = -1
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ reserved_concurrent_executions = 0
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ reserved_concurrent_executions = 1000
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_LambdaInVPC.py b/tests/terraform/checks/resource/aws/test_LambdaInVPC.py
new file mode 100644
index 0000000000..cfe82e6d9f
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_LambdaInVPC.py
@@ -0,0 +1,61 @@
+import unittest
+
+from checkov.terraform.checks.resource.aws.LambdaInVPC import check
+from checkov.common.models.enums import CheckResult
+
+import hcl2
+
+
+class TestLambdaInVPC(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+
+ runtime = "nodejs12.x"
+
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_lambda_function" "test_lambda" {
+ filename = "lambda_function_payload.zip"
+ function_name = "lambda_function_name"
+ role = aws_iam_role.iam_for_lambda.arn
+ handler = "exports.test"
+ source_code_hash = filebase64sha256("lambda_function_payload.zip")
+ runtime = "nodejs12.x"
+ vpc_config {
+ # Every subnet should be able to reach an EFS mount target in the same Availability Zone. Cross-AZ mounts are not permitted.
+ subnet_ids = [aws_subnet.subnet_for_lambda.id]
+ security_group_ids = [aws_security_group.sg_for_lambda.id]
+ }
+ environment {
+ variables = {
+ foo = "bar"
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_lambda_function']['test_lambda']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_NeptuneClusterLogging.py b/tests/terraform/checks/resource/aws/test_NeptuneClusterLogging.py
new file mode 100644
index 0000000000..ef97a07c6f
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_NeptuneClusterLogging.py
@@ -0,0 +1,43 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.NeptuneClusterLogging import check
+from checkov.common.models.enums import CheckResult
+
+class TestNeptuneClusterLogging(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_neptune_cluster" "test" {
+ cluster_identifier = "neptune-cluster-demo"
+ engine = "neptune"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ skip_final_snapshot = true
+ iam_database_authentication_enabled = true
+ apply_immediately = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_neptune_cluster']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_neptune_cluster" "test" {
+ cluster_identifier = "neptune-cluster-demo"
+ engine = "neptune"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ skip_final_snapshot = true
+ iam_database_authentication_enabled = true
+ apply_immediately = true
+ enable_cloudwatch_logs_exports = ["audit"]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_neptune_cluster']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_NeptuneInstancePublic.py b/tests/terraform/checks/resource/aws/test_NeptuneInstancePublic.py
new file mode 100644
index 0000000000..9e7107fbfa
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_NeptuneInstancePublic.py
@@ -0,0 +1,55 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.NeptuneClusterInstancePublic import check
+from checkov.common.models.enums import CheckResult
+
+class TestNeptuneClusterInstancePublic(unittest.TestCase):
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+resource "aws_neptune_cluster_instance" "example" {
+ count = 2
+ cluster_identifier = aws_neptune_cluster.default.id
+ engine = "neptune"
+ instance_class = "db.r4.large"
+ apply_immediately = true
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_neptune_cluster_instance']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_explicit(self):
+ hcl_res = hcl2.loads("""
+resource "aws_neptune_cluster_instance" "example" {
+ count = 2
+ cluster_identifier = aws_neptune_cluster.default.id
+ engine = "neptune"
+ instance_class = "db.r4.large"
+ apply_immediately = true
+ publicly_accessible = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_neptune_cluster_instance']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+resource "aws_neptune_cluster_instance" "example" {
+ count = 2
+ cluster_identifier = aws_neptune_cluster.default.id
+ engine = "neptune"
+ instance_class = "db.r4.large"
+ apply_immediately = true
+ publicly_accessible = true
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_neptune_cluster_instance']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_QLDBLedgerDeletionProtection.py b/tests/terraform/checks/resource/aws/test_QLDBLedgerDeletionProtection.py
new file mode 100644
index 0000000000..ed9cd67361
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_QLDBLedgerDeletionProtection.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.QLDBLedgerDeletionProtection import check
+from checkov.terraform.runner import Runner
+
+
+class TestQLDBLedgerDeletionProtection(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_QLDBLedgerDeletionProtection"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_qldb_ledger.default",
+ "aws_qldb_ledger.enabled",
+ }
+ failing_resources = {
+ "aws_qldb_ledger.disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_QLDBLedgerPermissionsMode.py b/tests/terraform/checks/resource/aws/test_QLDBLedgerPermissionsMode.py
new file mode 100644
index 0000000000..943805dab3
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_QLDBLedgerPermissionsMode.py
@@ -0,0 +1,36 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.QLDBLedgerPermissionsMode import check
+from checkov.terraform.runner import Runner
+
+
+class TestQLDBLedgerPermissionsMode(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_QLDBLedgerPermissionsMode"
+
+ report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_qldb_ledger.standard",
+ }
+ failing_resources = {
+ "aws_qldb_ledger.allow_all",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSClusterEncrypted.py b/tests/terraform/checks/resource/aws/test_RDSClusterEncrypted.py
new file mode 100644
index 0000000000..7cbbda5081
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSClusterEncrypted.py
@@ -0,0 +1,62 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RDSClusterEncrypted import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRDSClusterEncrypted(unittest.TestCase):
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_global_cluster" "example" {
+ provider = aws.primary
+
+ global_cluster_identifier = "example"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_global_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_explicit(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_global_cluster" "example" {
+ provider = aws.primary
+
+ global_cluster_identifier = "example"
+ storage_encrypted = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_global_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_global_cluster" "example" {
+ provider = aws.primary
+
+ global_cluster_identifier = "example"
+ storage_encrypted = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_global_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_with_source_db_cluster_identifier(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_global_cluster" "example" {
+ provider = aws.primary
+
+ global_cluster_identifier = "example"
+ source_db_cluster_identifier = "some_arn"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_global_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.UNKNOWN, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSClusterIAMAuthentication.py b/tests/terraform/checks/resource/aws/test_RDSClusterIAMAuthentication.py
new file mode 100644
index 0000000000..a3670d60e1
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSClusterIAMAuthentication.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.RDSClusterIAMAuthentication import check
+from checkov.terraform.runner import Runner
+
+
+class TestRDSClusterIAMAuthentication(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_RDSClusterIAMAuthentication"
+
+ report = Runner().run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_rds_cluster.enabled",
+ }
+ failing_resources = {
+ "aws_rds_cluster.default",
+ "aws_rds_cluster.disabled",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSClusterSnapshotEncrypted.py b/tests/terraform/checks/resource/aws/test_RDSClusterSnapshotEncrypted.py
new file mode 100644
index 0000000000..c173040241
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSClusterSnapshotEncrypted.py
@@ -0,0 +1,47 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RDSClusterSnapshotEncrypted import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRDSClusterSnapshotEncrypted(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_db_cluster_snapshot" "example" {
+ db_cluster_identifier = aws_rds_cluster.example.id
+ db_cluster_snapshot_identifier = "resourcetestsnapshot1234"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_cluster_snapshot']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_db_cluster_snapshot" "example" {
+ db_cluster_identifier = aws_rds_cluster.example.id
+ db_cluster_snapshot_identifier = "resourcetestsnapshot1234"
+ storage_encrypted = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_cluster_snapshot']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_db_cluster_snapshot" "example" {
+ db_cluster_identifier = aws_rds_cluster.example.id
+ db_cluster_snapshot_identifier = "resourcetestsnapshot1234"
+ storage_encrypted = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_cluster_snapshot']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSDeletionProtection.py b/tests/terraform/checks/resource/aws/test_RDSDeletionProtection.py
new file mode 100644
index 0000000000..78451072f8
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSDeletionProtection.py
@@ -0,0 +1,64 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RDSDeletionProtection import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRDSDeletionProtection(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ deletion_protection = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ deletion_protection = true
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSEnableIAMAuthentication.py b/tests/terraform/checks/resource/aws/test_RDSEnableIAMAuthentication.py
new file mode 100644
index 0000000000..0f0c643d67
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSEnableIAMAuthentication.py
@@ -0,0 +1,64 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RDSEnableIAMAuthentication import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRDSEnableIAMAuthentication(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ iam_database_authentication_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_rds_cluster" "default" {
+ cluster_identifier = "aurora-cluster-demo"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+ backup_retention_period = 5
+ preferred_backup_window = "07:00-09:00"
+ iam_database_authentication_enabled = true
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_rds_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSEnhancedMonitorEnabled.py b/tests/terraform/checks/resource/aws/test_RDSEnhancedMonitorEnabled.py
new file mode 100644
index 0000000000..16959783b3
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSEnhancedMonitorEnabled.py
@@ -0,0 +1,106 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RDSEnhancedMonitorEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRDSEnhancedMonitorEnabled(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+resource "aws_db_instance" "default" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_instance']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+resource "aws_db_instance" "default" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+ monitoring_interval = 0
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_instance']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure3(self):
+ hcl_res = hcl2.loads("""
+resource "aws_db_instance" "default" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+ monitoring_interval = "5"
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_instance']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+resource "aws_db_instance" "default" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+ monitoring_interval = 5
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_instance']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+resource "aws_db_instance" "default" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+ monitoring_interval = 15
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_db_instance']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSIAMAuthentication.py b/tests/terraform/checks/resource/aws/test_RDSIAMAuthentication.py
new file mode 100644
index 0000000000..cc7604b3fd
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSIAMAuthentication.py
@@ -0,0 +1,40 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.RDSIAMAuthentication import check
+from checkov.terraform.runner import Runner
+
+
+class TestRDSIAMAuthentication(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_RDSIAMAuthentication"
+
+ report = Runner().run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_db_instance.enabled_mysql",
+ "aws_db_instance.enabled_postgres",
+ }
+ failing_resources = {
+ "aws_db_instance.default_mysql",
+ "aws_db_instance.default_postgres",
+ "aws_db_instance.disabled_mysql",
+ "aws_db_instance.disabled_postgres",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RDSMultiAZEnabled.py b/tests/terraform/checks/resource/aws/test_RDSMultiAZEnabled.py
new file mode 100644
index 0000000000..fe57e67644
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RDSMultiAZEnabled.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.RDSMultiAZEnabled import check
+from checkov.terraform.runner import Runner
+
+
+class TestRDSMultiAZEnabled(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RDSMultiAZEnabled"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_db_instance.enabled",
+ }
+ failing_resources = {
+ "aws_db_instance.disabled",
+ "aws_db_instance.default",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RedShiftSSL.py b/tests/terraform/checks/resource/aws/test_RedShiftSSL.py
new file mode 100644
index 0000000000..a4306dc6cf
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RedShiftSSL.py
@@ -0,0 +1,41 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.RedShiftSSL import check
+from checkov.terraform.runner import Runner
+
+class TestRedShiftSSL(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RedShiftSSL"
+ report = runner.run(
+ root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])
+ )
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_redshift_parameter_group.pass",
+ "aws_redshift_parameter_group.passbutbool",
+ }
+ failing_resources = {
+ "aws_redshift_parameter_group.fail",
+ "aws_redshift_parameter_group.failasfalse"
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RedshiftClusterAllowVersionUpgrade.py b/tests/terraform/checks/resource/aws/test_RedshiftClusterAllowVersionUpgrade.py
new file mode 100644
index 0000000000..83da15e72b
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RedshiftClusterAllowVersionUpgrade.py
@@ -0,0 +1,60 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.aws.RedshiftClusterAllowVersionUpgrade import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRedshiftClusterAllowVersionUpgrade(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "default" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+ allow_version_upgrade = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "default" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "default" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+ allow_version_upgrade = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RedshiftClusterKMSKey.py b/tests/terraform/checks/resource/aws/test_RedshiftClusterKMSKey.py
new file mode 100644
index 0000000000..fed8630ce4
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RedshiftClusterKMSKey.py
@@ -0,0 +1,43 @@
+import unittest
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.RedshiftClusterKMSKey import check
+import hcl2
+
+
+class TestRedshiftClusterKMSKey(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "test" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "test" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+ kms_key_id = "someKey"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py b/tests/terraform/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py
index c42d124dc1..2372c9fba5 100644
--- a/tests/terraform/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py
+++ b/tests/terraform/checks/resource/aws/test_RedshiftClusterPubliclyAccessible.py
@@ -1,7 +1,7 @@
import unittest
import hcl2
-from checkov.terraform.checks.resource.aws.RDSPubliclyAccessible import check
+from checkov.terraform.checks.resource.aws.RedshitClusterPubliclyAvailable import check
from checkov.common.models.enums import CheckResult
@@ -22,6 +22,20 @@ def test_failure(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_redshift_cluster" "public" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_redshift_cluster']['public']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
def test_success(self):
hcl_res = hcl2.loads("""
resource "aws_redshift_cluster" "private" {
diff --git a/tests/terraform/checks/resource/aws/test_RedshiftInEc2ClassicMode.py b/tests/terraform/checks/resource/aws/test_RedshiftInEc2ClassicMode.py
new file mode 100644
index 0000000000..2c74491f39
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_RedshiftInEc2ClassicMode.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.RedshiftInEc2ClassicMode import check
+from checkov.terraform.runner import Runner
+
+class TestRedshiftInEc2ClassicMode(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_RedshiftInEc2ClassicMode"
+ report = runner.run(
+ root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])
+ )
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_redshift_cluster.pass", }
+ failing_resources = {
+ "aws_redshift_cluster.fail",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_S3BucketObjectLock.py b/tests/terraform/checks/resource/aws/test_S3BucketObjectLock.py
new file mode 100644
index 0000000000..25f80616b5
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_S3BucketObjectLock.py
@@ -0,0 +1,40 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.S3BucketObjectLock import check
+from checkov.terraform.runner import Runner
+
+
+class TestS3BucketObjectLock(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_S3BucketObjectLock"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_s3_bucket.enabled_via_object",
+ "aws_s3_bucket.enabled_via_block",
+ }
+ failing_resources = {
+ "aws_s3_bucket.disabled_via_object",
+ "aws_s3_bucket.disabled_via_block",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py b/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py
new file mode 100644
index 0000000000..153eb3d016
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py
@@ -0,0 +1,85 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.S3BucketReplicationConfiguration import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestS3BucketReplicationConfiguration(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "test" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Dev"
+ }
+ replication_configuration {
+ rules {
+ id = "foobar"
+ prefix = "foo"
+ status = "Enabled"
+
+ destination {
+ bucket = aws_s3_bucket.destination.arn
+ storage_class = "STANDARD"
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "test" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Dev"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "test" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Dev"
+ }
+ replication_configuration {
+ role = aws_iam_role.replication.arn
+ rules {
+ id = "foobar"
+ prefix = "foo"
+ status = "Enabled"
+
+ destination {
+ bucket = aws_s3_bucket.destination.arn
+ storage_class = "STANDARD"
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_S3Encryption.py b/tests/terraform/checks/resource/aws/test_S3Encryption.py
index eb9048af6a..ffe7624b96 100644
--- a/tests/terraform/checks/resource/aws/test_S3Encryption.py
+++ b/tests/terraform/checks/resource/aws/test_S3Encryption.py
@@ -2,8 +2,8 @@
import hcl2
-from checkov.terraform.checks.resource.aws.S3Encryption import check
from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.S3Encryption import check
class TestS3Encryption(unittest.TestCase):
@@ -56,5 +56,45 @@ def test_success_oneline(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_dynamic_value(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "default" {
+ count = local.enabled ? 1 : 0
+ bucket = module.this.id
+ acl = "private"
+ force_destroy = var.force_destroy
+ tags = module.this.tags
+
+ versioning {
+ enabled = var.versioning_enabled
+ }
+
+ dynamic "logging" {
+ for_each = var.access_log_bucket_name != "" ? [1] : []
+ content {
+ target_bucket = var.access_log_bucket_name
+ target_prefix = "logs/${module.this.id}/"
+ }
+ }
+
+ dynamic "server_side_encryption_configuration" {
+ for_each = var.s3_bucket_encryption_enabled ? [1] : []
+
+ content {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+ }
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_S3KMSEncryptedByDefault.py b/tests/terraform/checks/resource/aws/test_S3KMSEncryptedByDefault.py
new file mode 100644
index 0000000000..01e53c6ab3
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_S3KMSEncryptedByDefault.py
@@ -0,0 +1,59 @@
+import unittest
+
+import hcl2
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.S3KMSEncryptedByDefault import check
+
+
+class TestS3KMSEncryptedByDefault(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "mybucket" {
+ bucket = "mybucket"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['mybucket']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "mybucket" {
+ bucket = "mybucket"
+
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['mybucket']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_s3_bucket" "mybucket" {
+ bucket = "mybucket"
+
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = aws_kms_key.mykey.arn
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['mybucket']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_S3MFADelete.py b/tests/terraform/checks/resource/aws/test_S3MFADelete.py
deleted file mode 100644
index 1c466e5cad..0000000000
--- a/tests/terraform/checks/resource/aws/test_S3MFADelete.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import unittest
-
-from checkov.terraform.checks.resource.aws.S3MFADelete import scanner
-from checkov.common.models.enums import CheckResult
-
-
-class TestS3MFADelete(unittest.TestCase):
-
- def test_failure(self):
- resource_conf = {"region": ["us-west-2"],
- "bucket": ["my_bucket"],
- "acl": ["public-read"],
- "force_destroy": [True],
- "tags": [{"Name": "my-bucket"}]}
- scan_result = scanner.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
-
- def test_failure_versioning_enabled(self):
- resource_conf = {"region": ["us-west-2"],
- "bucket": ["my_bucket"],
- "acl": ["public-read"],
- "force_destroy": [True],
- "tags": [{"Name": "my-bucket"}],
- "versioning": [{"enabled": [True]}]}
- scan_result = scanner.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
-
- def test_success(self):
- resource_conf = {"region": ["us-west-2"],
- "bucket": ["my_bucket"],
- "acl": ["public-read"],
- "force_destroy": [True],
- "tags": [{"Name": "my-bucket"}],
- "logging": [{"target_bucket": "logging-bucket",
- "target_prefix": "log/"
- }],
- "versioning": [
- {"enabled": [True]},
- {"mfa_delete": [True]}
- ]
- }
- scan_result = scanner.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.PASSED, scan_result)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_SNSTopicPolicyAnyPrincipal.py b/tests/terraform/checks/resource/aws/test_SNSTopicPolicyAnyPrincipal.py
new file mode 100644
index 0000000000..1f6b7f0533
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SNSTopicPolicyAnyPrincipal.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.SNSTopicPolicyAnyPrincipal import check
+from checkov.terraform.runner import Runner
+
+
+class TestBackupVaultEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SNSTopicPolicyAnyPrincipal"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_sns_topic_policy.sns_tp1",
+ "aws_sns_topic_policy.sns_tp6",
+ }
+ failing_resources = {
+ "aws_sns_topic_policy.sns_tp2",
+ "aws_sns_topic_policy.sns_tp3",
+ "aws_sns_topic_policy.sns_tp4",
+ "aws_sns_topic_policy.sns_tp5",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/test_SQSQueuePolicyAnyPrincipal.py b/tests/terraform/checks/resource/aws/test_SQSQueuePolicyAnyPrincipal.py
new file mode 100644
index 0000000000..337c016988
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SQSQueuePolicyAnyPrincipal.py
@@ -0,0 +1,42 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.SQSQueuePolicyAnyPrincipal import check
+from checkov.terraform.runner import Runner
+
+
+class TestBackupVaultEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SQSQueuePolicyAnyPrincipal"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_sqs_queue_policy.q1",
+ "aws_sqs_queue_policy.q6",
+ }
+ failing_resources = {
+ "aws_sqs_queue_policy.q2",
+ "aws_sqs_queue_policy.q3",
+ "aws_sqs_queue_policy.q4",
+ "aws_sqs_queue_policy.q5",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 4)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentEncryption.py b/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentEncryption.py
new file mode 100644
index 0000000000..f061c0ec07
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentEncryption.py
@@ -0,0 +1,34 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.SSMSessionManagerDocumentEncryption import check
+from checkov.terraform.runner import Runner
+
+
+class TestSSMSessionManagerDocumentEncryption(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SSMSessionManagerDocumentEncryption"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {"aws_ssm_document.enabled", "aws_ssm_document.enabled_yaml"}
+ failing_resources = {"aws_ssm_document.disabled", "aws_ssm_document.disabled_yaml"}
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentLogging.py b/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentLogging.py
new file mode 100644
index 0000000000..945027d852
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SSMSessionManagerDocumentLogging.py
@@ -0,0 +1,46 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.SSMSessionManagerDocumentLogging import check
+from checkov.terraform.runner import Runner
+
+
+class TestSSMSessionManagerDocumentLogging(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SSMSessionManagerDocumentLogging"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_ssm_document.s3_enabled_encrypted",
+ "aws_ssm_document.s3_enabled_encrypted_yaml",
+ "aws_ssm_document.cw_enabled_encrypted",
+ "aws_ssm_document.cw_enabled_encrypted_yaml",
+ }
+ failing_resources = {
+ "aws_ssm_document.disabled",
+ "aws_ssm_document.disabled_yaml",
+ "aws_ssm_document.s3_enabled_not_encrypted",
+ "aws_ssm_document.s3_enabled_not_encrypted_yaml",
+ "aws_ssm_document.cw_enabled_not_encrypted",
+ "aws_ssm_document.cw_enabled_not_encrypted_yaml",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 4)
+ self.assertEqual(summary["failed"], 6)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_SageMakerInternetAccessDisabled.py b/tests/terraform/checks/resource/aws/test_SageMakerInternetAccessDisabled.py
new file mode 100644
index 0000000000..a233a583e0
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SageMakerInternetAccessDisabled.py
@@ -0,0 +1,62 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.SageMakerInternetAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSageMakerInternetAccessDisabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_sagemaker_notebook_instance" "test" {
+ name = "my-notebook-instance"
+ role_arn = aws_iam_role.role.arn
+ instance_type = "ml.t2.medium"
+ direct_internet_access = "Enabled"
+
+ tags = {
+ Name = "foo"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_sagemaker_notebook_instance']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_sagemaker_notebook_instance" "test" {
+ name = "my-notebook-instance"
+ role_arn = aws_iam_role.role.arn
+ instance_type = "ml.t2.medium"
+ direct_internet_access = "Disabled"
+
+ tags = {
+ Name = "foo"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_sagemaker_notebook_instance']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_sagemaker_notebook_instance" "test" {
+ name = "my-notebook-instance"
+ role_arn = aws_iam_role.role.arn
+ instance_type = "ml.t2.medium"
+
+ tags = {
+ Name = "foo"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_sagemaker_notebook_instance']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_SecretManagerSecretEncrypted.py b/tests/terraform/checks/resource/aws/test_SecretManagerSecretEncrypted.py
new file mode 100644
index 0000000000..42591eddbf
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SecretManagerSecretEncrypted.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.SecretManagerSecretEncrypted import check
+from checkov.terraform.runner import Runner
+
+
+class TestSecretManagerSecretEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_SecretManagerSecretEncrypted"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_secretsmanager_secret.enabled",
+ }
+ failing_resources = {
+ "aws_secretsmanager_secret.default",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py b/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
index ca82690e71..8106214f20 100644
--- a/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
+++ b/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
@@ -8,27 +8,79 @@
class TestSecurityGroupUnrestrictedIngress22(unittest.TestCase):
- def test_failure(self):
+ def test_failure_ipv4(self):
hcl_res = hcl2.loads("""
-resource "aws_security_group" "bar-sg" {
- name = "sg-bar"
- vpc_id = aws_vpc.main.id
+ resource "aws_security_group" "bar-sg" {
+ name = "sg-bar"
+ vpc_id = aws_vpc.main.id
+
+ ingress {
+ from_port = 22
+ to_port = 22
+ protocol = "tcp"
+ cidr_blocks = ["192.168.0.0/16", "0.0.0.0/0"]
+ description = "foo"
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_security_group']['bar-sg']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
- ingress {
- from_port = 22
- to_port = 22
- protocol = "tcp"
- cidr_blocks = ["192.168.0.0/16", "0.0.0.0/0"]
- description = "foo"
- }
+ def test_failure_0_0(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_security_group" "bar-sg" {
+ name = "sg-bar"
+ vpc_id = aws_vpc.main.id
+
+ ingress {
+ from_port = 0
+ to_port = 0
+ protocol = "tcp"
+ cidr_blocks = ["192.168.0.0/16", "0.0.0.0/0"]
+ description = "foo"
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_security_group']['bar-sg']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
- egress {
- from_port = 0
- to_port = 0
- protocol = "-1"
- cidr_blocks = ["0.0.0.0/0"]
- }
-}
+ def test_failure_ipv6(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_security_group" "bar-sg" {
+ name = "sg-bar"
+ vpc_id = aws_vpc.main.id
+
+ ingress {
+ from_port = 22
+ to_port = 22
+ protocol = "tcp"
+ ipv6_cidr_blocks = ["192.168.0.0/16", "::/0"]
+ description = "foo"
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+ }
""")
resource_conf = hcl_res['resource'][0]['aws_security_group']['bar-sg']
scan_result = check.scan_resource_conf(conf=resource_conf)
diff --git a/tests/terraform/checks/resource/aws/test_SubnetPublicIP.py b/tests/terraform/checks/resource/aws/test_SubnetPublicIP.py
new file mode 100644
index 0000000000..cbcb5b6620
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_SubnetPublicIP.py
@@ -0,0 +1,50 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.SubnetPublicIP import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSubnetPublicIP(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_subnet" "test" {
+ vpc_id = aws_vpc.main.id
+ cidr_block = "10.0.1.0/24"
+
+ map_public_ip_on_launch = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_subnet']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_subnet" "test" {
+ vpc_id = aws_vpc.main.id
+ cidr_block = "10.0.1.0/24"
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_subnet']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_implicit(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_subnet" "test" {
+ vpc_id = aws_vpc.main.id
+ cidr_block = "10.0.1.0/24"
+
+ map_public_ip_on_launch = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_subnet']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_TimestreamDatabaseKMSKey.py b/tests/terraform/checks/resource/aws/test_TimestreamDatabaseKMSKey.py
new file mode 100644
index 0000000000..34d9bfb197
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_TimestreamDatabaseKMSKey.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.TimestreamDatabaseKMSKey import check
+from checkov.terraform.runner import Runner
+
+
+class TestTimestreamDatabaseKMSKey(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_TimestreamDatabaseKMSKey"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_timestreamwrite_database.enabled",
+ }
+ failing_resources = {
+ "aws_timestreamwrite_database.default",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_TransferServerIsPublic.py b/tests/terraform/checks/resource/aws/test_TransferServerIsPublic.py
new file mode 100644
index 0000000000..64257a3557
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_TransferServerIsPublic.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.TransferServerIsPublic import check
+from checkov.terraform.runner import Runner
+
+
+class TestBackupVaultEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_TransferServerIsPublic"
+ report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_transfer_server.example_vpc",
+ }
+ failing_resources = {
+ "aws_transfer_server.example_public",
+ "aws_transfer_server.example",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 2)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/aws/test_VPCDefaultNetwork.py b/tests/terraform/checks/resource/aws/test_VPCDefaultNetwork.py
new file mode 100644
index 0000000000..925410702e
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_VPCDefaultNetwork.py
@@ -0,0 +1,39 @@
+import unittest
+
+import hcl2
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.VPCDefaultNetwork import check
+
+
+class TestDefaultVPC(unittest.TestCase):
+
+ def test_failure(self):
+ """
+ When there is a resource with aws_default_vpc, it should fail whatever the config is.
+ """
+ hcl_res = hcl2.loads("""
+ resource "aws_default_vpc" "default" {
+ tags = {
+ Name = "Default VPC"
+ }
+ }
+ """)
+
+ resource_conf = hcl_res['resource'][0]['aws_default_vpc']['default']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_config(self):
+ """
+ There is no success/pass scenario for this resource as we want to avoid the creation of this resource.
+ """
+ resource_conf = {
+ "enable_dns_support": "true"
+ }
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py b/tests/terraform/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py
new file mode 100644
index 0000000000..b9d3c883f4
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_VPCEndpointAcceptanceConfigured.py
@@ -0,0 +1,34 @@
+import unittest
+import hcl2
+
+from checkov.terraform.checks.resource.aws.VPCEndpointAcceptanceConfigured import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestVPCEndpointAcceptanceConfigured(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_vpc_endpoint_service" "example" {
+ acceptance_required = false
+ network_load_balancer_arns = [aws_lb.example.arn]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_vpc_endpoint_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "aws_vpc_endpoint_service" "example" {
+ acceptance_required = true
+ network_load_balancer_arns = [aws_lb.example.arn]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['aws_vpc_endpoint_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py b/tests/terraform/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py
new file mode 100644
index 0000000000..e739efdd14
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_WorkspaceRootVolumeEncrypted.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.WorkspaceRootVolumeEncrypted import check
+from checkov.terraform.runner import Runner
+
+class TestWorkspaceRootVolumeEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_WorkspaceRootVolumeEncrypted"
+ report = runner.run(
+ root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])
+ )
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_workspaces_workspace.pass",
+ }
+ failing_resources = {
+ "aws_workspaces_workspace.fail",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py b/tests/terraform/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py
new file mode 100644
index 0000000000..0e7153473f
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_WorkspaceUserVolumeEncrypted.py
@@ -0,0 +1,39 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.WorkspaceUserVolumeEncrypted import check
+from checkov.terraform.runner import Runner
+
+class TestWorkspaceUserVolumeEncrypted(unittest.TestCase):
+ def test(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ test_files_dir = current_dir + "/example_WorkspaceUserVolumeEncrypted"
+ report = runner.run(
+ root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])
+ )
+ summary = report.get_summary()
+
+ passing_resources = {
+ "aws_workspaces_workspace.pass",
+ }
+ failing_resources = {
+ "aws_workspaces_workspace.fail",
+ }
+
+ passed_check_resources = set([c.resource for c in report.passed_checks])
+ failed_check_resources = set([c.resource for c in report.failed_checks])
+
+ self.assertEqual(summary["passed"], 1)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/example_DataFactoryUsesGitRepository/main.tf b/tests/terraform/checks/resource/azure/example_DataFactoryUsesGitRepository/main.tf
new file mode 100644
index 0000000000..22a0e549b1
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/example_DataFactoryUsesGitRepository/main.tf
@@ -0,0 +1,38 @@
+# pass
+
+resource "azurerm_data_factory" "github" {
+ location = azurerm_resource_group.example.location
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+
+ github_configuration {
+ account_name = "bridgecrewio"
+ branch_name = "master"
+ git_url = "https://github.com"
+ repository_name = "checkov"
+ root_folder = "/"
+ }
+}
+
+resource "azurerm_data_factory" "vsts" {
+ location = azurerm_resource_group.example.location
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+
+ vsts_configuration {
+ account_name = "bridgecrewio"
+ branch_name = "master"
+ project_name = "chechov"
+ repository_name = "checkov"
+ root_folder = "/"
+ tenant_id = "123456789"
+ }
+}
+
+# fail
+
+resource "azurerm_data_factory" "fail" {
+ location = azurerm_resource_group.example.location
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+}
diff --git a/tests/terraform/checks/resource/azure/test_AKSApiServerAuthorizedIpRanges.py b/tests/terraform/checks/resource/azure/test_AKSApiServerAuthorizedIpRanges.py
index 3373810d84..2a9c16c8d3 100644
--- a/tests/terraform/checks/resource/azure/test_AKSApiServerAuthorizedIpRanges.py
+++ b/tests/terraform/checks/resource/azure/test_AKSApiServerAuthorizedIpRanges.py
@@ -11,28 +11,28 @@ def test_failure(self):
'resource_group_name': ['${azurerm_resource_group.example.name}'], 'dns_prefix': ['exampleaks1'],
'default_node_pool': [{'name': ['default'], 'node_count': [1], 'vm_size': ['Standard_D2_v2']}],
'identity': [{'type': ['SystemAssigned']}], 'agent_pool_profile': [{}], 'service_principal': [{}],
- 'api_server_authorized_ip_ranges': [[]], 'role_based_access_control': [{'enabled': [True]}],
- 'tags': [{'Environment': 'Production'}]}
-
+ 'role_based_access_control': [{'enabled': [False]}], 'tags': [{'Environment': 'Production'}]}
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
- def test_failure2(self):
+ def test_success(self):
resource_conf = {'name': ['example-aks1'], 'location': ['${azurerm_resource_group.example.location}'],
'resource_group_name': ['${azurerm_resource_group.example.name}'], 'dns_prefix': ['exampleaks1'],
'default_node_pool': [{'name': ['default'], 'node_count': [1], 'vm_size': ['Standard_D2_v2']}],
'identity': [{'type': ['SystemAssigned']}], 'agent_pool_profile': [{}], 'service_principal': [{}],
- 'role_based_access_control': [{'enabled': [False]}], 'tags': [{'Environment': 'Production'}]}
+ 'api_server_authorized_ip_ranges': [['192.168.0.0/16']], 'tags': [{'Environment': 'Production'}],
+ 'addon_profile': [{'oms_agent': [{'enabled': [True], 'log_analytics_workspace_id': ['']}]}]}
+
scan_result = check.scan_resource_conf(conf=resource_conf)
- self.assertEqual(CheckResult.FAILED, scan_result)
+ self.assertEqual(CheckResult.PASSED, scan_result)
- def test_success(self):
+ def test_success2(self):
resource_conf = {'name': ['example-aks1'], 'location': ['${azurerm_resource_group.example.location}'],
'resource_group_name': ['${azurerm_resource_group.example.name}'], 'dns_prefix': ['exampleaks1'],
'default_node_pool': [{'name': ['default'], 'node_count': [1], 'vm_size': ['Standard_D2_v2']}],
'identity': [{'type': ['SystemAssigned']}], 'agent_pool_profile': [{}], 'service_principal': [{}],
- 'api_server_authorized_ip_ranges': [['192.168.0.0/16']], 'tags': [{'Environment': 'Production'}],
- 'addon_profile': [{'oms_agent': [{'enabled': [True], 'log_analytics_workspace_id': ['']}]}]}
+ 'api_server_authorized_ip_ranges': [[]], 'role_based_access_control': [{'enabled': [True]}],
+ 'tags': [{'Environment': 'Production'}]}
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
diff --git a/tests/terraform/checks/resource/azure/test_AKSEnablesPrivateClusters.py b/tests/terraform/checks/resource/azure/test_AKSEnablesPrivateClusters.py
new file mode 100644
index 0000000000..e10f574289
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AKSEnablesPrivateClusters.py
@@ -0,0 +1,96 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AKSEnablesPrivateClusters import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAKSEnablesPrivateClusters(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+ private_cluster_enabled = false
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+ private_cluster_enabled = true
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py b/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py
new file mode 100644
index 0000000000..33a84bc4f4
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py
@@ -0,0 +1,137 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AKSUsesAzurePoliciesAddon import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAKSUsesAzurePoliciesAddon(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+
+ addon_profile {
+ azure_policy {
+ enabled = false
+ }
+ }
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+
+ azure_policy {
+ enabled = true
+ }
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+ addon_profile {
+ azure_policy {
+ enabled = true
+ }
+ }
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AKSUsesDiskEncryptionSet.py b/tests/terraform/checks/resource/azure/test_AKSUsesDiskEncryptionSet.py
new file mode 100644
index 0000000000..a7f7ef897d
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AKSUsesDiskEncryptionSet.py
@@ -0,0 +1,68 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AKSUsesDiskEncryptionSet import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAKSUsesDiskEncryptionSet(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kubernetes_cluster" "example" {
+ name = "example-aks1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ dns_prefix = "exampleaks1"
+ disk_encryption_set_id = "someId"
+
+ default_node_pool {
+ name = "default"
+ node_count = 1
+ vm_size = "Standard_D2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_APIServicesUseVirtualNetwork.py b/tests/terraform/checks/resource/azure/test_APIServicesUseVirtualNetwork.py
new file mode 100644
index 0000000000..6113a424ce
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_APIServicesUseVirtualNetwork.py
@@ -0,0 +1,71 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.APIServicesUseVirtualNetwork import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAPIServicesUseVirtualNetwork(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_api_management" "example" {
+ name = "example-apim"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ publisher_name = "My Company"
+ publisher_email = "company@terraform.io"
+
+ sku_name = "Developer_1"
+
+ policy {
+ xml_content = <
+
+
+
+
+
+ XML
+
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_api_management']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_api_management" "example" {
+ name = "example-apim"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ publisher_name = "My Company"
+ publisher_email = "company@terraform.io"
+
+ sku_name = "Developer_1"
+ virtual_network_configuration {
+ subnet_id = azure_subnet.subnet_not_public_ip.id
+ }
+ policy {
+ xml_content = <
+
+
+
+
+
+ XML
+
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_api_management']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_ActiveDirectoryUsedAuthenticationServiceFabric.py b/tests/terraform/checks/resource/azure/test_ActiveDirectoryUsedAuthenticationServiceFabric.py
new file mode 100644
index 0000000000..d6fce615ea
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_ActiveDirectoryUsedAuthenticationServiceFabric.py
@@ -0,0 +1,65 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.ActiveDirectoryUsedAuthenticationServiceFabric import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestActiveDirectoryUsedAuthenticationServiceFabric(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_service_fabric_cluster" "example" {
+ name = "example-servicefabric"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ reliability_level = "Bronze"
+ upgrade_mode = "Manual"
+ cluster_code_version = "7.1.456.959"
+ vm_image = "Windows"
+ management_endpoint = "https://example:80"
+
+ node_type {
+ name = "first"
+ instance_count = 3
+ is_primary = true
+ client_endpoint_port = 2020
+ http_endpoint_port = 80
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_service_fabric_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_service_fabric_cluster" "example" {
+ name = "example-servicefabric"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ reliability_level = "Bronze"
+ upgrade_mode = "Manual"
+ cluster_code_version = "7.1.456.959"
+ vm_image = "Windows"
+ management_endpoint = "https://example:80"
+ azure_active_directory {
+ tenant_id = "tenant"
+ }
+ node_type {
+ name = "first"
+ instance_count = 3
+ is_primary = true
+ client_endpoint_port = 2020
+ http_endpoint_port = 80
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_service_fabric_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceDetailedErrorMessagesEnabled.py b/tests/terraform/checks/resource/azure/test_AppServiceDetailedErrorMessagesEnabled.py
new file mode 100644
index 0000000000..383d5f7960
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceDetailedErrorMessagesEnabled.py
@@ -0,0 +1,181 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceDetailedErrorMessagesEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceDetailedErrorMessagesEnabled(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ logs {
+ application_logs {
+ azure_blob_storage {
+ level = "warning"
+ sas_url = "www.example.com"
+ retention_in_days = 4
+ }
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ logs {
+ application_logs {
+ azure_blob_storage {
+ level = "warning"
+ sas_url = "www.example.com"
+ retention_in_days = 4
+ }
+ }
+ detailed_error_messages_enabled = false
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ logs {
+ http_logs {
+ retention_in_days = 4
+ retention_in_mb = 10
+ }
+ detailed_error_messages_enabled = true
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ logs {
+ detailed_error_messages_enabled = true
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceDisallowedCORS.py b/tests/terraform/checks/resource/azure/test_AppServiceDisallowedCORS.py
new file mode 100644
index 0000000000..4816b9d3cd
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceDisallowedCORS.py
@@ -0,0 +1,103 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceDisallowCORS import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceDisallowCORS(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ cors {
+ allowed_origins = ["*"]
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ cors {
+ allowed_origins = ["192.0.0.1"]
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceDotnetFrameworkVersion.py b/tests/terraform/checks/resource/azure/test_AppServiceDotnetFrameworkVersion.py
new file mode 100644
index 0000000000..d392d226e5
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceDotnetFrameworkVersion.py
@@ -0,0 +1,66 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceDotnetFrameworkVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceDotnetFrameworkVersion(unittest.TestCase):
+
+ def test_failure_old_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_default_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_latest_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ dotnet_framework_version = "v5.0"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceEnableFailedRequest.py b/tests/terraform/checks/resource/azure/test_AppServiceEnableFailedRequest.py
new file mode 100644
index 0000000000..7d3f30d2a7
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceEnableFailedRequest.py
@@ -0,0 +1,91 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceEnableFailedRequest import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceEnableFailedRequest(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ logs {
+ failed_request_tracing_enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ logs {
+ failed_request_tracing_enabled = true
+ }
+ storage_account {
+ name = "test_name"
+ type = "AzureFiles"
+ account_name ="test_account_name"
+ share_name = "test_share_name"
+ access_key = "test_access_key"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failed_missing_failed_request_tracing_enabled_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ logs {
+ application_logs = "test"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failed_missing_logs_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceFTPSState.py b/tests/terraform/checks/resource/azure/test_AppServiceFTPSState.py
new file mode 100644
index 0000000000..aad59ed677
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceFTPSState.py
@@ -0,0 +1,80 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceFTPSState import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceFTPSState(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ ftps_state = "AllAllowed"
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ ftps_state = "FtpsOnly"
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ ftps_state = "Disabled"
+ }
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceHttpLoggingEnabled.py b/tests/terraform/checks/resource/azure/test_AppServiceHttpLoggingEnabled.py
new file mode 100644
index 0000000000..77fc75e896
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceHttpLoggingEnabled.py
@@ -0,0 +1,151 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceHttpLoggingEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceHttpLoggingEnabled(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ logs {
+ application_logs {
+ azure_blob_storage {
+ level = "warning"
+ sas_url = "www.example.com"
+ retention_in_days = 4
+ }
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ logs {
+ http_logs {
+ retention_in_days = 4
+ retention_in_mb = 10
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ logs {
+ application_logs {
+ azure_blob_storage {
+ level = "warning"
+ sas_url = "www.example.com"
+ retention_in_days = 4
+ }
+ }
+ http_logs {
+ retention_in_days = 4
+ retention_in_mb = 10
+ }
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceIdentityProviderEnabled.py b/tests/terraform/checks/resource/azure/test_AppServiceIdentityProviderEnabled.py
new file mode 100644
index 0000000000..7c924aa496
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceIdentityProviderEnabled.py
@@ -0,0 +1,62 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceIdentityProviderEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceIdentityProviderEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ dotnet_framework_version = "v5.0"
+ scm_type = "someValue"
+ }
+ identity {
+ type = "SystemAssigned"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceJavaVersion.py b/tests/terraform/checks/resource/azure/test_AppServiceJavaVersion.py
new file mode 100644
index 0000000000..2190a8c597
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceJavaVersion.py
@@ -0,0 +1,66 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceJavaVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServiceJavaVersion(unittest.TestCase):
+
+ def test_failure_old_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ java_version = "1.7.0_80"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_latest_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ java_version = "11"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_java(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServicePHPVersion.py b/tests/terraform/checks/resource/azure/test_AppServicePHPVersion.py
new file mode 100644
index 0000000000..44b6a34980
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServicePHPVersion.py
@@ -0,0 +1,66 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServicePHPVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServicePHPVersion(unittest.TestCase):
+
+ def test_failure_old_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ php_version = "5.6"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_latest_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ php_version = "7.4"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_php(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServicePythonVersion.py b/tests/terraform/checks/resource/azure/test_AppServicePythonVersion.py
new file mode 100644
index 0000000000..4714621e26
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServicePythonVersion.py
@@ -0,0 +1,66 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServicePythonVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServicePythonVersion(unittest.TestCase):
+
+ def test_failure_old_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ python_version = "2.7"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_latest_version(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ python_version = "3.4"
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_python(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AppServiceUsedAzureFiles.py b/tests/terraform/checks/resource/azure/test_AppServiceUsedAzureFiles.py
new file mode 100644
index 0000000000..3318724940
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AppServiceUsedAzureFiles.py
@@ -0,0 +1,72 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AppServiceUsedAzureFiles import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAppServicePythonVersion(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ storage_account {
+ name = "test_name"
+ type = "AzureBlob"
+ account_name ="test_account_name"
+ share_name = "test_share_name"
+ access_key = "test_access_key"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ storage_account {
+ name = "test_name"
+ type = "AzureFiles"
+ account_name ="test_account_name"
+ share_name = "test_share_name"
+ access_key = "test_access_key"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failed_no_storage_account(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ site_config {
+ scm_type = "someValue"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_ApplicationGatewayEnablesWAF.py b/tests/terraform/checks/resource/azure/test_ApplicationGatewayEnablesWAF.py
new file mode 100644
index 0000000000..68ba021425
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_ApplicationGatewayEnablesWAF.py
@@ -0,0 +1,200 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.ApplicationGatewayEnablesWAF import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestApplicationGatewayEnablesWAF(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_application_gateway" "network" {
+ name = "example-appgateway"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ sku {
+ name = "Standard_Small"
+ tier = "Standard"
+ capacity = 2
+ }
+
+ gateway_ip_configuration {
+ name = "my-gateway-ip-configuration"
+ subnet_id = azurerm_subnet.frontend.id
+ }
+
+ frontend_port {
+ name = local.frontend_port_name
+ port = 80
+ }
+
+ frontend_ip_configuration {
+ name = local.frontend_ip_configuration_name
+ public_ip_address_id = azurerm_public_ip.example.id
+ }
+
+ backend_address_pool {
+ name = local.backend_address_pool_name
+ }
+
+ backend_http_settings {
+ name = local.http_setting_name
+ cookie_based_affinity = "Disabled"
+ path = "/path1/"
+ port = 80
+ protocol = "Http"
+ request_timeout = 60
+ }
+
+ http_listener {
+ name = local.listener_name
+ frontend_ip_configuration_name = local.frontend_ip_configuration_name
+ frontend_port_name = local.frontend_port_name
+ protocol = "Http"
+ }
+
+ request_routing_rule {
+ name = local.request_routing_rule_name
+ rule_type = "Basic"
+ http_listener_name = local.listener_name
+ backend_address_pool_name = local.backend_address_pool_name
+ backend_http_settings_name = local.http_setting_name
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_application_gateway']['network']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_application_gateway" "network" {
+ name = "example-appgateway"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ waf_configuration {
+ enabled = false
+ }
+ sku {
+ name = "Standard_Small"
+ tier = "Standard"
+ capacity = 2
+ }
+
+ gateway_ip_configuration {
+ name = "my-gateway-ip-configuration"
+ subnet_id = azurerm_subnet.frontend.id
+ }
+
+ frontend_port {
+ name = local.frontend_port_name
+ port = 80
+ }
+
+ frontend_ip_configuration {
+ name = local.frontend_ip_configuration_name
+ public_ip_address_id = azurerm_public_ip.example.id
+ }
+
+ backend_address_pool {
+ name = local.backend_address_pool_name
+ }
+
+ backend_http_settings {
+ name = local.http_setting_name
+ cookie_based_affinity = "Disabled"
+ path = "/path1/"
+ port = 80
+ protocol = "Http"
+ request_timeout = 60
+ }
+
+ http_listener {
+ name = local.listener_name
+ frontend_ip_configuration_name = local.frontend_ip_configuration_name
+ frontend_port_name = local.frontend_port_name
+ protocol = "Http"
+ }
+
+ request_routing_rule {
+ name = local.request_routing_rule_name
+ rule_type = "Basic"
+ http_listener_name = local.listener_name
+ backend_address_pool_name = local.backend_address_pool_name
+ backend_http_settings_name = local.http_setting_name
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_application_gateway']['network']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_application_gateway" "network" {
+ name = "example-appgateway"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ waf_configuration {
+ enabled = true
+ }
+ sku {
+ name = "Standard_Small"
+ tier = "Standard"
+ capacity = 2
+ }
+
+ gateway_ip_configuration {
+ name = "my-gateway-ip-configuration"
+ subnet_id = azurerm_subnet.frontend.id
+ }
+
+ frontend_port {
+ name = local.frontend_port_name
+ port = 80
+ }
+
+ frontend_ip_configuration {
+ name = local.frontend_ip_configuration_name
+ public_ip_address_id = azurerm_public_ip.example.id
+ }
+
+ backend_address_pool {
+ name = local.backend_address_pool_name
+ }
+
+ backend_http_settings {
+ name = local.http_setting_name
+ cookie_based_affinity = "Disabled"
+ path = "/path1/"
+ port = 80
+ protocol = "Http"
+ request_timeout = 60
+ }
+
+ http_listener {
+ name = local.listener_name
+ frontend_ip_configuration_name = local.frontend_ip_configuration_name
+ frontend_port_name = local.frontend_port_name
+ protocol = "Http"
+ }
+
+ request_routing_rule {
+ name = local.request_routing_rule_name
+ rule_type = "Basic"
+ http_listener_name = local.listener_name
+ backend_address_pool_name = local.backend_address_pool_name
+ backend_http_settings_name = local.http_setting_name
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_application_gateway']['network']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AutomationEncrypted.py b/tests/terraform/checks/resource/azure/test_AutomationEncrypted.py
new file mode 100644
index 0000000000..8f4058dda8
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AutomationEncrypted.py
@@ -0,0 +1,54 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AutomationEncrypted import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAutomationEncrypted(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_automation_variable_string" "example" {
+ name = "tfex-example-var"
+ resource_group_name = azurerm_resource_group.example.name
+ automation_account_name = azurerm_automation_account.example.name
+ value = "Hello, Terraform Basic Test."
+ encrypted = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_automation_variable_string']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_automation_variable_datetime" "example" {
+ name = "tfex-example-var"
+ resource_group_name = azurerm_resource_group.example.name
+ automation_account_name = azurerm_automation_account.example.name
+ value = "Hello, Terraform Basic Test."
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_automation_variable_datetime']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_automation_variable_int" "example" {
+ name = "tfex-example-var"
+ resource_group_name = azurerm_resource_group.example.name
+ automation_account_name = azurerm_automation_account.example.name
+ value = "Hello, Terraform Basic Test."
+ encrypted = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_automation_variable_int']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureBatchAccountUsesKeyVaultEncryption.py b/tests/terraform/checks/resource/azure/test_AzureBatchAccountUsesKeyVaultEncryption.py
new file mode 100644
index 0000000000..655f07b361
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureBatchAccountUsesKeyVaultEncryption.py
@@ -0,0 +1,53 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureBatchAccountUsesKeyVaultEncryption import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureBatchAccountUsesKeyVaultEncryption(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_batch_account" "example" {
+ name = "testbatchaccount"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ pool_allocation_mode = "BatchService"
+ storage_account_id = azurerm_storage_account.example.id
+
+ tags = {
+ env = "test"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_batch_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_batch_account" "example" {
+ name = "testbatchaccount"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ pool_allocation_mode = "BatchService"
+ storage_account_id = azurerm_storage_account.example.id
+ key_vault_reference {
+ id = "test"
+ url = "https://test.com"
+ }
+
+ tags = {
+ env = "test"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_batch_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureContainerGroupDeployedIntoVirtualNetwork.py b/tests/terraform/checks/resource/azure/test_AzureContainerGroupDeployedIntoVirtualNetwork.py
new file mode 100644
index 0000000000..8231348257
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureContainerGroupDeployedIntoVirtualNetwork.py
@@ -0,0 +1,83 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureContainerGroupDeployedIntoVirtualNetwork import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureContainerGroupDeployedIntoVirtualNetwork(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_container_group" "example" {
+ name = "example-continst"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ ip_address_type = "public"
+ dns_name_label = "aci-label"
+ os_type = "Linux"
+
+ container {
+ name = "hello-world"
+ image = "microsoft/aci-helloworld:latest"
+ cpu = "0.5"
+ memory = "1.5"
+
+ ports {
+ port = 443
+ protocol = "TCP"
+ }
+ }
+
+ container {
+ name = "sidecar"
+ image = "microsoft/aci-tutorial-sidecar"
+ cpu = "0.5"
+ memory = "1.5"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_container_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_container_group" "example" {
+ name = "example-continst"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ ip_address_type = "public"
+ dns_name_label = "aci-label"
+ os_type = "Linux"
+
+ container {
+ name = "hello-world"
+ image = "microsoft/aci-helloworld:latest"
+ cpu = "0.5"
+ memory = "1.5"
+
+ ports {
+ port = 443
+ protocol = "TCP"
+ }
+ }
+
+ container {
+ name = "sidecar"
+ image = "microsoft/aci-tutorial-sidecar"
+ cpu = "0.5"
+ memory = "1.5"
+ }
+
+ network_profile_id = "network_profile_id"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_container_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDataExplorerDoubleEncryptionEnabled.py b/tests/terraform/checks/resource/azure/test_AzureDataExplorerDoubleEncryptionEnabled.py
new file mode 100644
index 0000000000..a791f08dd0
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDataExplorerDoubleEncryptionEnabled.py
@@ -0,0 +1,79 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDataExplorerDoubleEncryptionEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDataExplorerDoubleEncryptionEnabled(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ double_encryption_enabled = false
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ double_encryption_enabled = true
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnAppServices.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnAppServices.py
new file mode 100644
index 0000000000..a210fe68f1
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnAppServices.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnAppServices import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnAppServices(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "AppServices"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "AppServices"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnContainerRegistry.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnContainerRegistry.py
new file mode 100644
index 0000000000..9cee8dabdd
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnContainerRegistry.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnContainerRegistry import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnContainerRegistry(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "ContainerRegistry"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "ContainerRegistry"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnKeyVaults.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnKeyVaults.py
new file mode 100644
index 0000000000..63e96102da
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnKeyVaults.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnKeyVaults import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnKeyVaults(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "KeyVaults"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "KeyVaults"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnKubernetes.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnKubernetes.py
new file mode 100644
index 0000000000..8a86a6a310
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnKubernetes.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnKubernetes import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnKubernetes(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "KubernetesService"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "KubernetesService"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnServers.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnServers.py
new file mode 100644
index 0000000000..1d47480cbf
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnServers.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnServers import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnServers(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "VirtualMachines"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "VirtualMachines"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServers.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServers.py
new file mode 100644
index 0000000000..e543e56d4f
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServers.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnSqlServers import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnSqlServers(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "SqlServers"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "SqlServers"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServersVMS.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServersVMS.py
new file mode 100644
index 0000000000..e9d101cc1e
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnSqlServersVMS.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnSqlServerVMS import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnSqlServersVMS(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "SqlServerVirtualMachines"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "SqlServerVirtualMachines"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureDefenderOnStorage.py b/tests/terraform/checks/resource/azure/test_AzureDefenderOnStorage.py
new file mode 100644
index 0000000000..01e75649da
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureDefenderOnStorage.py
@@ -0,0 +1,35 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureDefenderOnStorage import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureDefenderOnStorage(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Free"
+ resource_type = "StorageAccounts"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_subscription_pricing" "example" {
+ tier = "Standard"
+ resource_type = "StorageAccounts"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_subscription_pricing']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureFrontDoorEnablesWAF.py b/tests/terraform/checks/resource/azure/test_AzureFrontDoorEnablesWAF.py
new file mode 100644
index 0000000000..ef4e9fc07b
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureFrontDoorEnablesWAF.py
@@ -0,0 +1,116 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureFrontDoorEnablesWAF import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureFrontDoorEnablesWAF(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor" "example" {
+ name = "example-FrontDoor"
+ location = "EastUS2"
+ resource_group_name = azurerm_resource_group.example.name
+ enforce_backend_pools_certificate_name_check = false
+
+ routing_rule {
+ name = "exampleRoutingRule1"
+ accepted_protocols = ["Http", "Https"]
+ patterns_to_match = ["/*"]
+ frontend_endpoints = ["exampleFrontendEndpoint1"]
+ forwarding_configuration {
+ forwarding_protocol = "MatchRequest"
+ backend_pool_name = "exampleBackendBing"
+ }
+ }
+
+ backend_pool_load_balancing {
+ name = "exampleLoadBalancingSettings1"
+ }
+
+ backend_pool_health_probe {
+ name = "exampleHealthProbeSetting1"
+ }
+
+ backend_pool {
+ name = "exampleBackendBing"
+ backend {
+ host_header = "www.bing.com"
+ address = "www.bing.com"
+ http_port = 80
+ https_port = 443
+ }
+
+ load_balancing_name = "exampleLoadBalancingSettings1"
+ health_probe_name = "exampleHealthProbeSetting1"
+ }
+
+ frontend_endpoint {
+ name = "exampleFrontendEndpoint1"
+ host_name = "example-FrontDoor.azurefd.net"
+ custom_https_provisioning_enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor" "example" {
+ name = "example-FrontDoor"
+ location = "EastUS2"
+ resource_group_name = azurerm_resource_group.example.name
+ enforce_backend_pools_certificate_name_check = false
+ web_application_firewall_policy_link_id = "this_is_id"
+
+ routing_rule {
+ name = "exampleRoutingRule1"
+ accepted_protocols = ["Http", "Https"]
+ patterns_to_match = ["/*"]
+ frontend_endpoints = ["exampleFrontendEndpoint1"]
+ forwarding_configuration {
+ forwarding_protocol = "MatchRequest"
+ backend_pool_name = "exampleBackendBing"
+ }
+ }
+
+ backend_pool_load_balancing {
+ name = "exampleLoadBalancingSettings1"
+ }
+
+ backend_pool_health_probe {
+ name = "exampleHealthProbeSetting1"
+ }
+
+ backend_pool {
+ name = "exampleBackendBing"
+ backend {
+ host_header = "www.bing.com"
+ address = "www.bing.com"
+ http_port = 80
+ https_port = 443
+ }
+
+ load_balancing_name = "exampleLoadBalancingSettings1"
+ health_probe_name = "exampleHealthProbeSetting1"
+ }
+
+ frontend_endpoint {
+ name = "exampleFrontendEndpoint1"
+ host_name = "example-FrontDoor.azurefd.net"
+ custom_https_provisioning_enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureInstanceExtensions.py b/tests/terraform/checks/resource/azure/test_AzureInstanceExtensions.py
new file mode 100644
index 0000000000..759f79055f
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureInstanceExtensions.py
@@ -0,0 +1,80 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureInstanceExtensions import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureInstanceExtensions(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_linux_virtual_machine" "example" {
+ name = "example-machine"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ size = "Standard_F2"
+ admin_username = "adminuser"
+ allow_extension_operations=true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_linux_virtual_machine']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_linux_virtual_machine" "example" {
+ name = "example-machine"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ size = "Standard_F2"
+ admin_username = "adminuser"
+ allow_extension_operations=false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_linux_virtual_machine']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_winfailure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_windows_virtual_machine" "example" {
+ name = "example-machine"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ size = "Standard_F2"
+ admin_username = "adminuser"
+ admin_password = "P@$$w0rd1234!"
+ allow_extension_operations = true
+ network_interface_ids = [
+ azurerm_network_interface.example.id,
+ ]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_windows_virtual_machine']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_winsuccess(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_windows_virtual_machine" "example" {
+ name = "example-machine"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ size = "Standard_F2"
+ admin_username = "adminuser"
+ admin_password = "P@$$w0rd1234!"
+ network_interface_ids = [
+ azurerm_network_interface.example.id,
+ ]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_windows_virtual_machine']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureManagedDiscEncryption.py b/tests/terraform/checks/resource/azure/test_AzureManagedDiscEncryption.py
index ceaca7897e..9acca3e3b2 100644
--- a/tests/terraform/checks/resource/azure/test_AzureManagedDiscEncryption.py
+++ b/tests/terraform/checks/resource/azure/test_AzureManagedDiscEncryption.py
@@ -1,19 +1,83 @@
import unittest
+import hcl2
from checkov.common.models.enums import CheckResult
-from checkov.terraform.checks.resource.azure.AzureManagedDiscEncryption import check
+from checkov.terraform.checks.resource.azure.AzureManagedDiskEncryption import check
class TestAzureManagedDiscEncryption(unittest.TestCase):
def test_failure(self):
- resource_conf = {'encryption_settings': [{'enabled': [False]}]}
-
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "example" {
+ name = var.disk_name
+ location = var.location
+ resource_group_name = var.resource_group_name
+ storage_account_type = var.storage_account_type
+ create_option = "Empty"
+ disk_size_gb = var.disk_size_gb
+ encryption_settings {
+ enabled = false
+ }
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['example']
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_default_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "example" {
+ name = var.disk_name
+ location = var.location
+ resource_group_name = var.resource_group_name
+ storage_account_type = var.storage_account_type
+ create_option = "Empty"
+ disk_size_gb = var.disk_size_gb
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
def test_success(self):
- resource_conf = {'encryption_settings': [{'enabled': [True]}]}
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "example" {
+ name = var.disk_name
+ location = var.location
+ resource_group_name = var.resource_group_name
+ storage_account_type = var.storage_account_type
+ create_option = "Empty"
+ disk_size_gb = var.disk_size_gb
+ encryption_settings {
+ enabled = true
+ }
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_encryption_set_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "source" {
+ name = "acctestmd1"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ disk_encryption_set_id = var.encryption_set_id
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['source']
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
diff --git a/tests/terraform/checks/resource/azure/test_AzureManagedDiskEncryptionSet.py b/tests/terraform/checks/resource/azure/test_AzureManagedDiskEncryptionSet.py
new file mode 100644
index 0000000000..2cf649ab7f
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureManagedDiskEncryptionSet.py
@@ -0,0 +1,51 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureManagedDiskEncryptionSet import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureManagedDiskEncryptionSet(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "source" {
+ name = "acctestmd1"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['source']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_managed_disk" "source" {
+ name = "acctestmd1"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ disk_encryption_set_id = "koko"
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_managed_disk']['source']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureScaleSetPassword.py b/tests/terraform/checks/resource/azure/test_AzureScaleSetPassword.py
new file mode 100644
index 0000000000..ab88701c30
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureScaleSetPassword.py
@@ -0,0 +1,51 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureScaleSetPassword import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureScaleSetPassword(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_linux_virtual_machine_scale_set" "example" {
+ name = var.scaleset_name
+ resource_group_name = var.resource_group.name
+ location = var.resource_group.location
+ sku = var.sku
+ instances = var.instance_count
+ admin_username = var.admin_username
+ disable_password_authentication = false
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_linux_virtual_machine_scale_set']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_linux_virtual_machine_scale_set" "example" {
+ name = var.scaleset_name
+ resource_group_name = var.resource_group.name
+ location = var.resource_group.location
+ sku = var.sku
+ instances = var.instance_count
+ admin_username = var.admin_username
+ disable_password_authentication = true
+
+ admin_ssh_key {
+ username = var.admin_username
+ public_key = tls_private_key.new.public_key_pem
+ }
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_linux_virtual_machine_scale_set']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureSearchPublicNetworkAccessDisabled.py b/tests/terraform/checks/resource/azure/test_AzureSearchPublicNetworkAccessDisabled.py
new file mode 100644
index 0000000000..80d8b417dc
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureSearchPublicNetworkAccessDisabled.py
@@ -0,0 +1,54 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureSearchPublicNetworkAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureSearchPublicNetworkAccessDisabled(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_search_service" "example" {
+ name = "example-search-service"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ sku = "standard"
+ public_network_access_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_search_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_search_service" "example" {
+ name = "example-search-service"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ sku = "standard"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_search_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_search_service" "example" {
+ name = "example-search-service"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ sku = "standard"
+ public_network_access_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_search_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_AzureServiceFabricClusterProtectionLevel.py b/tests/terraform/checks/resource/azure/test_AzureServiceFabricClusterProtectionLevel.py
new file mode 100644
index 0000000000..87d86308e2
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_AzureServiceFabricClusterProtectionLevel.py
@@ -0,0 +1,105 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.AzureServiceFabricClusterProtectionLevel import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestAzureServiceFabricClusterProtectionLevel(unittest.TestCase):
+ def test_passing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_service_fabric_cluster" "example" {
+ name = "example-servicefabric"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ reliability_level = "Bronze"
+ upgrade_mode = "Manual"
+ cluster_code_version = "7.1.456.959"
+ vm_image = "Windows"
+ management_endpoint = "https://example:80"
+ fabric_settings {
+ name = "Security"
+ parameters = {
+ name = "ClusterProtectionLevel"
+ value = "EncryptAndSign"
+ }
+ }
+
+
+ node_type {
+ name = "first"
+ instance_count = 3
+ is_primary = true
+ client_endpoint_port = 2020
+ http_endpoint_port = 80
+ }
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_service_fabric_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_missing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_service_fabric_cluster" "example" {
+ name = "example-servicefabric"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ reliability_level = "Bronze"
+ upgrade_mode = "Manual"
+ cluster_code_version = "7.1.456.959"
+ vm_image = "Windows"
+ management_endpoint = "https://example:80"
+
+
+ node_type {
+ name = "first"
+ instance_count = 3
+ is_primary = true
+ client_endpoint_port = 2020
+ http_endpoint_port = 80
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_service_fabric_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_wrong(self):
+ def test_passing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_service_fabric_cluster" "example" {
+ name = "example-servicefabric"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ reliability_level = "Bronze"
+ upgrade_mode = "Manual"
+ cluster_code_version = "7.1.456.959"
+ vm_image = "Windows"
+ management_endpoint = "https://example:80"
+ fabric_settings {
+ name = "Security"
+ parameters = {
+ name = "ClusterProtectionLevel"
+ value = "Sign"
+ }
+ }
+
+
+ node_type {
+ name = "first"
+ instance_count = 3
+ is_primary = true
+ client_endpoint_port = 2020
+ http_endpoint_port = 80
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_service_fabric_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_CosmosDBAccountsRestrictedAccess.py b/tests/terraform/checks/resource/azure/test_CosmosDBAccountsRestrictedAccess.py
new file mode 100644
index 0000000000..8908491e9f
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_CosmosDBAccountsRestrictedAccess.py
@@ -0,0 +1,542 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.CosmosDBAccountsRestrictedAccess import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestCosmosDBAccountsRestrictedAccess(unittest.TestCase):
+
+ def test_failure_public_access_default(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ is_virtual_network_filter_enabled = true
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_public_access_vn_filter(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ public_network_access_enabled = true
+ is_virtual_network_filter_enabled = false
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_vn_filter_false(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ is_virtual_network_filter_enabled = false
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_vm_filter_false_with_vn_rule(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_firewall_network_rule_collection" "example" {
+ name = "testcollection"
+ azure_firewall_name = azurerm_firewall.example.name
+ resource_group_name = azurerm_resource_group.example.name
+ priority = 100
+ action = "Allow"
+
+ rule {
+ name = "testrule"
+
+ source_addresses = [
+ "10.0.0.0/16",
+ ]
+
+ destination_ports = [
+ "53",
+ ]
+
+ destination_addresses = [
+ "8.8.8.8",
+ "8.8.4.4",
+ ]
+
+ protocols = [
+ "TCP",
+ "UDP",
+ ]
+ }
+ }
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ is_virtual_network_filter_enabled = false
+ virtual_network_rule = azurerm_firewall_network_rule_collection.example
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][1]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_public_access_with_ip_range(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ public_network_access_enabled = true
+
+ ip_range_filter = ["192.0.0.1"]
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_no_public_access(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ public_network_access_enabled = false
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_vm_filter_with_vm_rule(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_firewall_network_rule_collection" "example" {
+ name = "testcollection"
+ azure_firewall_name = azurerm_firewall.example.name
+ resource_group_name = azurerm_resource_group.example.name
+ priority = 100
+ action = "Allow"
+
+ rule {
+ name = "testrule"
+
+ source_addresses = [
+ "10.0.0.0/16",
+ ]
+
+ destination_ports = [
+ "53",
+ ]
+
+ destination_addresses = [
+ "8.8.8.8",
+ "8.8.4.4",
+ ]
+
+ protocols = [
+ "TCP",
+ "UDP",
+ ]
+ }
+ }
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ is_virtual_network_filter_enabled = true
+ virtual_network_rule = azurerm_firewall_network_rule_collection.example
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][1]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_public_access_with_vn_filter(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_firewall_network_rule_collection" "example" {
+ name = "testcollection"
+ azure_firewall_name = azurerm_firewall.example.name
+ resource_group_name = azurerm_resource_group.example.name
+ priority = 100
+ action = "Allow"
+
+ rule {
+ name = "testrule"
+
+ source_addresses = [
+ "10.0.0.0/16",
+ ]
+
+ destination_ports = [
+ "53",
+ ]
+
+ destination_addresses = [
+ "8.8.8.8",
+ "8.8.4.4",
+ ]
+
+ protocols = [
+ "TCP",
+ "UDP",
+ ]
+ }
+ }
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ public_network_access_enabled = false
+ is_virtual_network_filter_enabled = true
+ virtual_network_rule = azurerm_firewall_network_rule_collection.example
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][1]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_vn_filter_ip_range(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_firewall_network_rule_collection" "example" {
+ name = "testcollection"
+ azure_firewall_name = azurerm_firewall.example.name
+ resource_group_name = azurerm_resource_group.example.name
+ priority = 100
+ action = "Allow"
+
+ rule {
+ name = "testrule"
+
+ source_addresses = [
+ "10.0.0.0/16",
+ ]
+
+ destination_ports = [
+ "53",
+ ]
+
+ destination_addresses = [
+ "8.8.8.8",
+ "8.8.4.4",
+ ]
+
+ protocols = [
+ "TCP",
+ "UDP",
+ ]
+ }
+ }
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+ is_virtual_network_filter_enabled = true
+ ip_range_filter = ["192.0.0.1"]
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][1]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_CosmosDBDisablesPublicNetwork.py b/tests/terraform/checks/resource/azure/test_CosmosDBDisablesPublicNetwork.py
new file mode 100644
index 0000000000..648ad96df1
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_CosmosDBDisablesPublicNetwork.py
@@ -0,0 +1,151 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.CosmosDBDisablesPublicNetwork import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestCosmosDBHaveCMK(unittest.TestCase):
+
+ def test_failure_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+
+ public_network_access_enabled = true
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+
+ key_vault_key_id = "A versionless Key Vault Key ID for CMK encryption"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+
+ public_network_access_enabled = false
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+
+ key_vault_key_id = "A versionless Key Vault Key ID for CMK encryption"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_CosmosDBHaveCMK.py b/tests/terraform/checks/resource/azure/test_CosmosDBHaveCMK.py
new file mode 100644
index 0000000000..d719dbb832
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_CosmosDBHaveCMK.py
@@ -0,0 +1,103 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.CosmosDBHaveCMK import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestCosmosDBHaveCMK(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_cosmosdb_account" "db" {
+ name = "tfex-cosmos-db-${random_integer.ri.result}"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ offer_type = "Standard"
+ kind = "GlobalDocumentDB"
+
+ enable_automatic_failover = true
+
+ capabilities {
+ name = "EnableAggregationPipeline"
+ }
+
+ capabilities {
+ name = "mongoEnableDocLevelTTL"
+ }
+
+ capabilities {
+ name = "MongoDBv3.4"
+ }
+
+ consistency_policy {
+ consistency_level = "BoundedStaleness"
+ max_interval_in_seconds = 10
+ max_staleness_prefix = 200
+ }
+
+ geo_location {
+ location = var.failover_location
+ failover_priority = 1
+ }
+
+ geo_location {
+ location = azurerm_resource_group.rg.location
+ failover_priority = 0
+ }
+
+ key_vault_key_id = "A versionless Key Vault Key ID for CMK encryption"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_cosmosdb_account']['db']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_CustomRoleDefinitionSubscriptionOwner.py b/tests/terraform/checks/resource/azure/test_CustomRoleDefinitionSubscriptionOwner.py
index ec52b6c138..ac82a3dd6f 100644
--- a/tests/terraform/checks/resource/azure/test_CustomRoleDefinitionSubscriptionOwner.py
+++ b/tests/terraform/checks/resource/azure/test_CustomRoleDefinitionSubscriptionOwner.py
@@ -79,6 +79,23 @@ def test_success(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_no_assignable_scopes(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_role_definition" "example" {
+ name = "my-custom-role"
+ scope = data.azurerm_subscription.primary.id
+ description = "This is a custom role created via Terraform"
+
+ permissions {
+ actions = ["*"]
+ not_actions = []
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_role_definition']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_DataExplorerUsesDiskEncryption.py b/tests/terraform/checks/resource/azure/test_DataExplorerUsesDiskEncryption.py
new file mode 100644
index 0000000000..69c26c263b
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_DataExplorerUsesDiskEncryption.py
@@ -0,0 +1,77 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.DataExplorerUsesDiskEncryption import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestDataExplorerUsesDiskEncryption(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ tags = {
+ Environment = "Production"
+ }
+ enable_disk_encryption = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_kusto_cluster" "example" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+ tags = {
+ Environment = "Production"
+ }
+ enable_disk_encryption = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_kusto_cluster']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_DataFactoryNoPublicNetworkAccess.py b/tests/terraform/checks/resource/azure/test_DataFactoryNoPublicNetworkAccess.py
new file mode 100644
index 0000000000..5005e50192
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_DataFactoryNoPublicNetworkAccess.py
@@ -0,0 +1,51 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.DataFactoryNoPublicNetworkAccess import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestDataFactoryNoPublicNetworkAccess(unittest.TestCase):
+
+ def test_failure_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_factory" "example" {
+ name = "example"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_factory']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_factory" "example" {
+ name = "example"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ public_network_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_factory']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_factory" "example" {
+ name = "example"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ public_network_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_factory']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/azure/test_DataFactoryUsesGitRepository.py b/tests/terraform/checks/resource/azure/test_DataFactoryUsesGitRepository.py
new file mode 100644
index 0000000000..6b36b56620
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_DataFactoryUsesGitRepository.py
@@ -0,0 +1,37 @@
+import unittest
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.azure.DataFactoryUsesGitRepository import check
+from checkov.terraform.runner import Runner
+
+
+class TestDataFactoryUsesGitRepository(unittest.TestCase):
+ def test(self):
+ test_files_dir = Path(__file__).parent / "example_DataFactoryUsesGitRepository"
+
+ report = Runner().run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+ summary = report.get_summary()
+
+ passing_resources = {
+ "azurerm_data_factory.github",
+ "azurerm_data_factory.vsts",
+ }
+ failing_resources = {
+ "azurerm_data_factory.fail",
+ }
+
+ passed_check_resources = {c.resource for c in report.passed_checks}
+ failed_check_resources = {c.resource for c in report.failed_checks}
+
+ self.assertEqual(summary["passed"], 2)
+ self.assertEqual(summary["failed"], 1)
+ self.assertEqual(summary["skipped"], 0)
+ self.assertEqual(summary["parsing_errors"], 0)
+
+ self.assertEqual(passing_resources, passed_check_resources)
+ self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_DataLakeStoreEncryption.py b/tests/terraform/checks/resource/azure/test_DataLakeStoreEncryption.py
new file mode 100644
index 0000000000..529daf90b9
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_DataLakeStoreEncryption.py
@@ -0,0 +1,53 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.DataLakeStoreEncryption import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestDataLakeStoreEncryption(unittest.TestCase):
+
+ def test_failure_explicit(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_lake_store" "example" {
+ name = "consumptiondatalake"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ encryption_state = "Disabled"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_lake_store']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_lake_store" "example" {
+ name = "consumptiondatalake"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_lake_store']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_explicit(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_data_lake_store" "example" {
+ name = "consumptiondatalake"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ encryption_state = "Enabled"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_data_lake_store']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_EventgridDomainNetworkAccess.py b/tests/terraform/checks/resource/azure/test_EventgridDomainNetworkAccess.py
new file mode 100644
index 0000000000..64b48b91bf
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_EventgridDomainNetworkAccess.py
@@ -0,0 +1,53 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.EventgridDomainNetworkAccess import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestEventgridDomainNetworkAccess(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_eventgrid_domain" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_eventgrid_domain']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_explicit(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_eventgrid_domain" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ public_network_access_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_eventgrid_domain']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_eventgrid_domain" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ public_network_access_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_eventgrid_domain']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_FrontdoorUseWAFMode.py b/tests/terraform/checks/resource/azure/test_FrontdoorUseWAFMode.py
new file mode 100644
index 0000000000..42f2f9d574
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_FrontdoorUseWAFMode.py
@@ -0,0 +1,554 @@
+import unittest
+
+import hcl2
+
+from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.azure.FrontdoorUseWAFMode import check
+
+
+class TestFrontdoorUseWAFMode(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ policy_settings {
+ enabled = false
+ request_body_check = true
+ file_upload_limit_in_mb = 100
+ max_request_body_size_in_kb = 128
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ policy_settings {
+ enabled = false
+ mode = "Prevention"
+ request_body_check = true
+ file_upload_limit_in_mb = 100
+ max_request_body_size_in_kb = 128
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ policy_settings {
+ enabled = true
+ mode = "Prevention"
+ request_body_check = true
+ file_upload_limit_in_mb = 100
+ max_request_body_size_in_kb = 128
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ policy_settings {
+ mode = "Prevention"
+ request_body_check = true
+ file_upload_limit_in_mb = 100
+ max_request_body_size_in_kb = 128
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ policy_settings {
+ enabled = true
+ request_body_check = true
+ file_upload_limit_in_mb = 100
+ max_request_body_size_in_kb = 128
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success4(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_frontdoor_firewall_policy" "example" {
+ name = "example-wafpolicy"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ custom_rules {
+ name = "Rule1"
+ priority = 1
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24", "10.0.0.0/24"]
+ }
+
+ action = "Block"
+ }
+
+ custom_rules {
+ name = "Rule2"
+ priority = 2
+ rule_type = "MatchRule"
+
+ match_conditions {
+ match_variables {
+ variable_name = "RemoteAddr"
+ }
+
+ operator = "IPMatch"
+ negation_condition = false
+ match_values = ["192.168.1.0/24"]
+ }
+
+ match_conditions {
+ match_variables {
+ variable_name = "RequestHeaders"
+ selector = "UserAgent"
+ }
+
+ operator = "Contains"
+ negation_condition = false
+ match_values = ["Windows"]
+ }
+
+ action = "Block"
+ }
+
+ managed_rules {
+ exclusion {
+ match_variable = "RequestHeaderNames"
+ selector = "x-company-secret-header"
+ selector_match_operator = "Equals"
+ }
+ exclusion {
+ match_variable = "RequestCookieNames"
+ selector = "too-tasty"
+ selector_match_operator = "EndsWith"
+ }
+
+ managed_rule_set {
+ type = "OWASP"
+ version = "3.1"
+ rule_group_override {
+ rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
+ disabled_rules = [
+ "920300",
+ "920440"
+ ]
+ }
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_frontdoor_firewall_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_FunctionAppDisallowCORS.py b/tests/terraform/checks/resource/azure/test_FunctionAppDisallowCORS.py
new file mode 100644
index 0000000000..a035fe3e2a
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_FunctionAppDisallowCORS.py
@@ -0,0 +1,68 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.FunctionAppDisallowCORS import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestFunctionAppDisallowCORS(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ site_config {
+ cors {
+ allowed_origins = ["*"]
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ site_config {
+ cors {
+ allowed_origins = ["192.0.0.1"]
+ }
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_FunctionAppHttpVersionLatest.py b/tests/terraform/checks/resource/azure/test_FunctionAppHttpVersionLatest.py
new file mode 100644
index 0000000000..a54e6c05bc
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_FunctionAppHttpVersionLatest.py
@@ -0,0 +1,67 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.FunctionAppHttpVersionLatest import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestFunctionAppHttpVersionLatest(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ os_type = "linux"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ os_type = "linux"
+ site_config {
+ http2_enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ storage_account_name = azurerm_storage_account.example.name
+ storage_account_access_key = azurerm_storage_account.example.primary_access_key
+ os_type = "linux"
+ site_config {
+ http2_enabled = true
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_FunctionAppsAccessibleOverHttps.py b/tests/terraform/checks/resource/azure/test_FunctionAppsAccessibleOverHttps.py
new file mode 100644
index 0000000000..31411e4438
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_FunctionAppsAccessibleOverHttps.py
@@ -0,0 +1,54 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.FunctionAppsAccessibleOverHttps import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestFunctionAppsAccessibleOverHttps(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+ https_only = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_FunctionAppsEnableAuthentication.py b/tests/terraform/checks/resource/azure/test_FunctionAppsEnableAuthentication.py
new file mode 100644
index 0000000000..9b8a26e9e9
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_FunctionAppsEnableAuthentication.py
@@ -0,0 +1,64 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.FunctionAppsEnableAuthentication import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestFunctionAppsEnableAuthentication(unittest.TestCase):
+
+ def test_failure_missing_authentication_block(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ app_service_plan_id = "azurerm_app_service_plan.example.id"
+ storage_account_name = "azurerm_storage_account.example.name"
+ storage_account_access_key = "azurerm_storage_account.example.primary_access_key"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ app_service_plan_id = "azurerm_app_service_plan.example.id"
+ storage_account_name = "azurerm_storage_account.example.name"
+ storage_account_access_key = "azurerm_storage_account.example.primary_access_key"
+ auth_settings {
+ enabled = true
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failed(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_function_app" "example" {
+ name = "test-azure-functions"
+ location = "azurerm_resource_group.example.location"
+ resource_group_name = "azurerm_resource_group.example.name"
+ app_service_plan_id = "azurerm_app_service_plan.example.id"
+ storage_account_name = "azurerm_storage_account.example.name"
+ storage_account_access_key = "azurerm_storage_account.example.primary_access_key"
+ auth_settings {
+ enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_function_app']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_IoTNoPublicNetworkAccess.py b/tests/terraform/checks/resource/azure/test_IoTNoPublicNetworkAccess.py
new file mode 100644
index 0000000000..6248786a0d
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_IoTNoPublicNetworkAccess.py
@@ -0,0 +1,193 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.IoTNoPublicNetworkAccess import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestIoTNoPublicNetworkAccess(unittest.TestCase):
+
+ def test_success_missing_attribute(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_iothub" "example" {
+ name = "Example-IoTHub"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ sku {
+ name = "S1"
+ capacity = "1"
+ }
+
+ endpoint {
+ type = "AzureIotHub.StorageContainer"
+ connection_string = azurerm_storage_account.example.primary_blob_connection_string
+ name = "export"
+ batch_frequency_in_seconds = 60
+ max_chunk_size_in_bytes = 10485760
+ container_name = azurerm_storage_container.example.name
+ encoding = "Avro"
+ file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}"
+ }
+
+ endpoint {
+ type = "AzureIotHub.EventHub"
+ connection_string = azurerm_eventhub_authorization_rule.example.primary_connection_string
+ name = "export2"
+ }
+
+ route {
+ name = "export"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export"]
+ enabled = true
+ }
+
+ route {
+ name = "export2"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export2"]
+ enabled = true
+ }
+
+ enrichment {
+ key = "tenant"
+ value = "$twin.tags.Tenant"
+ endpoint_names = ["export", "export2"]
+ }
+
+ tags = {
+ purpose = "testing"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_iothub']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_iothub" "example" {
+ name = "Example-IoTHub"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ sku {
+ name = "S1"
+ capacity = "1"
+ }
+
+ endpoint {
+ type = "AzureIotHub.StorageContainer"
+ connection_string = azurerm_storage_account.example.primary_blob_connection_string
+ name = "export"
+ batch_frequency_in_seconds = 60
+ max_chunk_size_in_bytes = 10485760
+ container_name = azurerm_storage_container.example.name
+ encoding = "Avro"
+ file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}"
+ }
+
+ endpoint {
+ type = "AzureIotHub.EventHub"
+ connection_string = azurerm_eventhub_authorization_rule.example.primary_connection_string
+ name = "export2"
+ }
+ public_network_access_enabled = true
+ route {
+ name = "export"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export"]
+ enabled = true
+ }
+
+ route {
+ name = "export2"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export2"]
+ enabled = true
+ }
+
+ enrichment {
+ key = "tenant"
+ value = "$twin.tags.Tenant"
+ endpoint_names = ["export", "export2"]
+ }
+
+ tags = {
+ purpose = "testing"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_iothub']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_iothub" "example" {
+ name = "Example-IoTHub"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+
+ sku {
+ name = "S1"
+ capacity = "1"
+ }
+
+ endpoint {
+ type = "AzureIotHub.StorageContainer"
+ connection_string = azurerm_storage_account.example.primary_blob_connection_string
+ name = "export"
+ batch_frequency_in_seconds = 60
+ max_chunk_size_in_bytes = 10485760
+ container_name = azurerm_storage_container.example.name
+ encoding = "Avro"
+ file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}"
+ }
+
+ endpoint {
+ type = "AzureIotHub.EventHub"
+ connection_string = azurerm_eventhub_authorization_rule.example.primary_connection_string
+ name = "export2"
+ }
+
+ route {
+ name = "export"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export"]
+ enabled = true
+ }
+
+ route {
+ name = "export2"
+ source = "DeviceMessages"
+ condition = "true"
+ endpoint_names = ["export2"]
+ enabled = true
+ }
+
+ enrichment {
+ key = "tenant"
+ value = "$twin.tags.Tenant"
+ endpoint_names = ["export", "export2"]
+ }
+ public_network_access_enabled = false
+ tags = {
+ purpose = "testing"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_iothub']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_KeyBackedByHSM.py b/tests/terraform/checks/resource/azure/test_KeyBackedByHSM.py
new file mode 100644
index 0000000000..0e1f00c354
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_KeyBackedByHSM.py
@@ -0,0 +1,81 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.KeyBackedByHSM import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestKeyBackedByHSM(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault_key" "generated" {
+ name = "generated-certificate"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 2048
+
+ key_opts = [
+ "decrypt",
+ "encrypt",
+ "sign",
+ "unwrapKey",
+ "verify",
+ "wrapKey",
+ ]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault_key']['generated']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault_key" "generated" {
+ name = "generated-certificate"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "EC-HSM"
+ key_size = 2048
+
+ key_opts = [
+ "decrypt",
+ "encrypt",
+ "sign",
+ "unwrapKey",
+ "verify",
+ "wrapKey",
+ ]
+ expiration_date = "2020-12-30T20:00:00Z"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault_key']['generated']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault_key" "generated" {
+ name = "generated-certificate"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA-HSM"
+ key_size = 2048
+
+ key_opts = [
+ "decrypt",
+ "encrypt",
+ "sign",
+ "unwrapKey",
+ "verify",
+ "wrapKey",
+ ]
+ expiration_date = "2020-12-30T20:00:00Z"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault_key']['generated']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_KeyVaultEnablesFirewallRulesSettings.py b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesFirewallRulesSettings.py
new file mode 100644
index 0000000000..6b786ba7eb
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesFirewallRulesSettings.py
@@ -0,0 +1,124 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.KeyVaultEnablesFirewallRulesSettings import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestKeyVaultEnablesFirewallRulesSettings(unittest.TestCase):
+
+ def test_failure_missing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ network_acls {
+ default_action = "Deny"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_fail_allow(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ network_acls {
+ default_action = "Allow"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_KeyVaultEnablesPurgeProtection.py b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesPurgeProtection.py
new file mode 100644
index 0000000000..3f11429d98
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesPurgeProtection.py
@@ -0,0 +1,117 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.KeyVaultEnablesPurgeProtection import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestKeyVaultEnablesPurgeProtection(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = true
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_KeyVaultEnablesSoftDelete.py b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesSoftDelete.py
new file mode 100644
index 0000000000..07826278bf
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_KeyVaultEnablesSoftDelete.py
@@ -0,0 +1,118 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.KeyVaultEnablesSoftDelete import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestKeyVaultEnablesSoftDelete(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+ soft_delete_enabled = false
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault" "example" {
+ name = "examplekeyvault"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ enabled_for_disk_encryption = true
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ soft_delete_retention_days = 7
+ purge_protection_enabled = false
+ soft_delete_enabled = true
+ sku_name = "standard"
+
+ access_policy {
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+
+ key_permissions = [
+ "Get",
+ ]
+
+ secret_permissions = [
+ "Get",
+ ]
+
+ storage_permissions = [
+ "Get",
+ ]
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MSSQLServerMinTLSVersion.py b/tests/terraform/checks/resource/azure/test_MSSQLServerMinTLSVersion.py
new file mode 100644
index 0000000000..f6b5ebde54
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MSSQLServerMinTLSVersion.py
@@ -0,0 +1,55 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MSSQLServerMinTLSVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMSSQLServerMinTLSVersion(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_server" "examplea" {
+ name = var.server_name
+ resource_group_name = var.resource_group.name
+ location = var.resource_group.location
+ version = var.sql["version"]
+ administrator_login = var.sql["administrator_login"]
+ administrator_login_password = local.administrator_login_password
+ minimum_tls_version = "1.1"
+ public_network_access_enabled = var.sql["public_network_access_enabled"]
+ identity {
+ type = "SystemAssigned"
+ }
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_server" "examplea" {
+ name = var.server_name
+ resource_group_name = var.resource_group.name
+ location = var.resource_group.location
+ version = var.sql["version"]
+ administrator_login = var.sql["administrator_login"]
+ administrator_login_password = local.administrator_login_password
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = var.sql["public_network_access_enabled"]
+ identity {
+ type = "SystemAssigned"
+ }
+ tags = var.common_tags
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MariaDBGeoBackupEnabled.py b/tests/terraform/checks/resource/azure/test_MariaDBGeoBackupEnabled.py
new file mode 100644
index 0000000000..ca4c4aee4c
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MariaDBGeoBackupEnabled.py
@@ -0,0 +1,81 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MariaDBGeoBackupEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class MariaDBGeoBackupEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ public_network_access_enabled = true
+ #test this i guess
+ ssl_enforcement_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_empty(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ public_network_access_enabled = true
+ #test this i guess
+ ssl_enforcement_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ public_network_access_enabled = false
+ #test this i guess
+ ssl_enforcement_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MariaDBPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_MariaDBPublicAccessDisabled.py
new file mode 100644
index 0000000000..d752871a06
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MariaDBPublicAccessDisabled.py
@@ -0,0 +1,59 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MariaDBPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMariaDBPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ public_network_access_enabled = true
+ #test this i guess
+ ssl_enforcement_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ public_network_access_enabled = false
+ #test this i guess
+ ssl_enforcement_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MariaDBSSLEnforcementEnabled.py b/tests/terraform/checks/resource/azure/test_MariaDBSSLEnforcementEnabled.py
new file mode 100644
index 0000000000..df5ede28e4
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MariaDBSSLEnforcementEnabled.py
@@ -0,0 +1,59 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MariaDBSSLEnforcementEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMariaDBSSLEnforcementEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ public_network_access_enabled = false
+ #test this i guess
+ ssl_enforcement_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mariadb_server" "example" {
+ name = var.server_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+ administrator_login = var.admin_login
+ administrator_login_password = random_string.password.result
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "10.2"
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ public_network_access_enabled = false
+ #test this i guess
+ ssl_enforcement_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mariadb_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MonitorLogProfileCategories.py b/tests/terraform/checks/resource/azure/test_MonitorLogProfileCategories.py
index db779ec61f..36dacfd86b 100644
--- a/tests/terraform/checks/resource/azure/test_MonitorLogProfileCategories.py
+++ b/tests/terraform/checks/resource/azure/test_MonitorLogProfileCategories.py
@@ -52,6 +52,43 @@ def test_success(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_fail_missing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_monitor_log_profile" "example" {
+ name = "default"
+ locations = [
+ "westus",
+ "global",
+ ]
+ retention_policy {
+ enabled = false
+ days = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_monitor_log_profile']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+ def test_fail_empty(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_monitor_log_profile" "example" {
+ name = "default"
+ categories = []
+ locations = [
+ "westus",
+ "global",
+ ]
+ retention_policy {
+ enabled = false
+ days = 0
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_monitor_log_profile']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MonitorLogProfileRetentionDays.py b/tests/terraform/checks/resource/azure/test_MonitorLogProfileRetentionDays.py
index f9be9dcf9b..4889d6d000 100644
--- a/tests/terraform/checks/resource/azure/test_MonitorLogProfileRetentionDays.py
+++ b/tests/terraform/checks/resource/azure/test_MonitorLogProfileRetentionDays.py
@@ -77,6 +77,25 @@ def test_success_2(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_failure_missing(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_monitor_log_profile" "example" {
+ name = "default"
+ categories = [
+ "Action",
+ "Delete",
+ "Write",
+ ]
+ locations = [
+ "westus",
+ "global",
+ ]
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_monitor_log_profile']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MySQLEncryptionEnaled.py b/tests/terraform/checks/resource/azure/test_MySQLEncryptionEnaled.py
new file mode 100644
index 0000000000..3165b14dc6
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLEncryptionEnaled.py
@@ -0,0 +1,83 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.PostgersSQLEncryptionEnaled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMySQLEncryptionEnaled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ infrastructure_encryption_enabled = false
+ auto_grow_enabled = true
+ public_network_access_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+ backup_retention_days = 7
+ auto_grow_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+ backup_retention_days = 7
+ infrastructure_encryption_enabled = true
+ auto_grow_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/azure/test_MySQLGeoBackupEnabled.py b/tests/terraform/checks/resource/azure/test_MySQLGeoBackupEnabled.py
new file mode 100644
index 0000000000..4b8f8a6884
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLGeoBackupEnabled.py
@@ -0,0 +1,83 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MySQLGeoBackupEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMySQLGeoBackupEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = true
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_missing_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ infrastructure_encryption_enabled = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+resource "azurerm_mysql_server" "example" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/azure/test_MySQLPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_MySQLPublicAccessDisabled.py
new file mode 100644
index 0000000000..3a85ed00f4
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLPublicAccessDisabled.py
@@ -0,0 +1,84 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MySQLPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMySQLPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "examplea" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = true
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_missing_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "examplea" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+resource "azurerm_mysql_server" "examplea" {
+ name = var.mysqlserver_name
+ location = var.resource_group.location
+ resource_group_name = var.resource_group.name
+
+ administrator_login = var.admin_name
+ administrator_login_password = var.password
+ sku_name = var.sku_name
+ storage_mb = var.storage_mb
+ version = var.server_version
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MySQLServerMinTLSVersion.py b/tests/terraform/checks/resource/azure/test_MySQLServerMinTLSVersion.py
new file mode 100644
index 0000000000..ec9b9295dc
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLServerMinTLSVersion.py
@@ -0,0 +1,67 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MySQLServerMinTLSVersion import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMySQLServerMinTLSVersion(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "examplea" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_1"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "examplea" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['examplea']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MySQLServerPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_MySQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..fe62e9d60b
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLServerPublicAccessDisabled.py
@@ -0,0 +1,93 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MySQLServerPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMysqlSQLServerPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ infrastructure_encryption_enabled = false
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_MySQLTreatDetectionEnabled.py b/tests/terraform/checks/resource/azure/test_MySQLTreatDetectionEnabled.py
new file mode 100644
index 0000000000..dec58b6079
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_MySQLTreatDetectionEnabled.py
@@ -0,0 +1,102 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.MySQLTreatDetectionEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestMySQLTreatDetectionEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+
+ threat_detection_policy {
+ enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_empty(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ threat_detection_policy {
+ enabled = true
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_NSGRuleUDPAccessRestricted.py b/tests/terraform/checks/resource/azure/test_NSGRuleUDPAccessRestricted.py
new file mode 100644
index 0000000000..a54babf630
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_NSGRuleUDPAccessRestricted.py
@@ -0,0 +1,381 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.NSGRuleUDPAccessRestricted import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestNSGRuleUDPAccessRestricted(unittest.TestCase):
+
+ def test_failure1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "any"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "/0"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure4(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "/0"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure5(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "internet"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Deny"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Outbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_group" "example" {
+ name = "acceptanceTestSecurityGroup1"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ security_rule {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Tcp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_group']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_failure_rule_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_rule_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "any"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_rule_3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "/0"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_rule_4(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "/0"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_rule_5(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "internet"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_rule_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Deny"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_rule_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Outbound"
+ access = "Allow"
+ protocol = "Udp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_rule_3(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_security_rule" "example" {
+ name = "test123"
+ priority = 100
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "Tcp"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_security_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_NetworkInterfaceEnableIPForwarding.py b/tests/terraform/checks/resource/azure/test_NetworkInterfaceEnableIPForwarding.py
new file mode 100644
index 0000000000..461d1e4c48
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_NetworkInterfaceEnableIPForwarding.py
@@ -0,0 +1,69 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.NetworkInterfaceEnableIPForwarding import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestNetworkInterfaceEnableIPForwarding(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.example.id
+ private_ip_address_allocation = "Dynamic"
+ }
+ enable_ip_forwarding = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.example.id
+ private_ip_address_allocation = "Dynamic"
+ }
+ enable_ip_forwarding = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.example.id
+ private_ip_address_allocation = "Dynamic"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_NetworkInterfacePublicIPAddressId.py b/tests/terraform/checks/resource/azure/test_NetworkInterfacePublicIPAddressId.py
new file mode 100644
index 0000000000..53ce3f011f
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_NetworkInterfacePublicIPAddressId.py
@@ -0,0 +1,74 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.NetworkInterfacePublicIPAddressId import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestNetworkInterfacePublicIPAddressId(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.example.id
+ private_ip_address_allocation = "Dynamic"
+ }
+ ip_configuration {
+ name = "internal2"
+ subnet_id = azurerm_subnet.example.id2
+ private_ip_address_allocation = "Dynamic"
+ public_ip_address_id = azurerm_subnet.example2.id
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.example.id
+ private_ip_address_allocation = "Dynamic"
+ }
+ ip_configuration {
+ name = "internal2"
+ subnet_id = azurerm_subnet.example.id2
+ private_ip_address_allocation = "Dynamic"
+ }
+ enable_ip_forwarding = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_interface" "example" {
+ name = "example-nic"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_interface']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_NetworkWatcherFlowLogPeriod.py b/tests/terraform/checks/resource/azure/test_NetworkWatcherFlowLogPeriod.py
index b1c4baf946..0f8f52033b 100644
--- a/tests/terraform/checks/resource/azure/test_NetworkWatcherFlowLogPeriod.py
+++ b/tests/terraform/checks/resource/azure/test_NetworkWatcherFlowLogPeriod.py
@@ -28,6 +28,26 @@ def test_failure(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_failure_invalid_days_string(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_watcher_flow_log" "test" {
+ network_watcher_name = azurerm_network_watcher.test.name
+ resource_group_name = azurerm_resource_group.test.name
+ network_security_group_id = azurerm_network_security_group.test.id
+ storage_account_id = azurerm_storage_account.test.id
+ enabled = true
+
+ retention_policy {
+ enabled = true
+ days = var.watcher_flow_logs.days
+ }
+ }
+
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_watcher_flow_log']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
def test_success(self):
hcl_res = hcl2.loads("""
resource "azurerm_network_watcher_flow_log" "test" {
@@ -48,6 +68,46 @@ def test_success(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)
+ def test_success_with_0_days(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_watcher_flow_log" "test" {
+ network_watcher_name = azurerm_network_watcher.test.name
+ resource_group_name = azurerm_resource_group.test.name
+ network_security_group_id = azurerm_network_security_group.test.id
+ storage_account_id = azurerm_storage_account.test.id
+ enabled = true
+
+ retention_policy {
+ enabled = true
+ days = 0
+ }
+ }
+
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_watcher_flow_log']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_with_valid_day_string(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_network_watcher_flow_log" "test" {
+ network_watcher_name = azurerm_network_watcher.test.name
+ resource_group_name = azurerm_resource_group.test.name
+ network_security_group_id = azurerm_network_security_group.test.id
+ storage_account_id = azurerm_storage_account.test.id
+ enabled = true
+
+ retention_policy {
+ enabled = true
+ days = "100"
+ }
+ }
+
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_network_watcher_flow_log']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_PostgersSQLEncryptionEnaled.py b/tests/terraform/checks/resource/azure/test_PostgersSQLEncryptionEnaled.py
new file mode 100644
index 0000000000..087a26ac4d
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_PostgersSQLEncryptionEnaled.py
@@ -0,0 +1,83 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.PostgersSQLEncryptionEnaled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestPostgersSQLEncryptionEnaled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ infrastructure_encryption_enabled = false
+ auto_grow_enabled = true
+ public_network_access_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+ backup_retention_days = 7
+ auto_grow_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+ backup_retention_days = 7
+ infrastructure_encryption_enabled = true
+ auto_grow_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/azure/test_PostgreSQLServerPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_PostgreSQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..973ef4bf7e
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_PostgreSQLServerPublicAccessDisabled.py
@@ -0,0 +1,92 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.PostgreSQLServerPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestPostgreSQLServerPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ auto_grow_enabled = true
+
+ public_network_access_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ auto_grow_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ auto_grow_enabled = true
+
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_PostgresSQLGeoBackupEnabled.py b/tests/terraform/checks/resource/azure/test_PostgresSQLGeoBackupEnabled.py
new file mode 100644
index 0000000000..c9be13dd2b
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_PostgresSQLGeoBackupEnabled.py
@@ -0,0 +1,91 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.PostgressSQLGeoBackupEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestPostgressSQLGeoBackupEnabled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = false
+ auto_grow_enabled = true
+
+ public_network_access_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ auto_grow_enabled = true
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-psqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "psqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "GP_Gen5_4"
+ version = "9.6"
+ storage_mb = 640000
+
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ auto_grow_enabled = true
+
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_PostgresSQLTreatDetectionEnabled.py b/tests/terraform/checks/resource/azure/test_PostgresSQLTreatDetectionEnabled.py
new file mode 100644
index 0000000000..d73dbd0d01
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_PostgresSQLTreatDetectionEnabled.py
@@ -0,0 +1,102 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.PostgresSQLTreatDetectionEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestPostgresSQLTreatDetectionEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+
+ threat_detection_policy {
+ enabled = false
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_empty(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = false
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_postgresql_server" "example" {
+ name = "example-mysqlserver"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+
+ administrator_login = "mysqladminun"
+ administrator_login_password = "H@Sh1CoR3!"
+
+ sku_name = "B_Gen5_2"
+ storage_mb = 5120
+ version = "5.7"
+
+ auto_grow_enabled = true
+ backup_retention_days = 7
+ geo_redundant_backup_enabled = true
+ infrastructure_encryption_enabled = true
+ public_network_access_enabled = false
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_2"
+ threat_detection_policy {
+ enabled = true
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_postgresql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_RedisCacheEnableNonSSLPort.py b/tests/terraform/checks/resource/azure/test_RedisCacheEnableNonSSLPort.py
new file mode 100644
index 0000000000..ebfe85308a
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_RedisCacheEnableNonSSLPort.py
@@ -0,0 +1,73 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.RedisCacheEnableNonSSLPort import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRedisCacheEnableNonSSLPort(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ enable_non_ssl_port = true
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = true
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ enable_non_ssl_port = false
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = true
+
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ minimum_tls_version = "1.2"
+
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_RedisCachePublicNetworkAccessEnabled.py b/tests/terraform/checks/resource/azure/test_RedisCachePublicNetworkAccessEnabled.py
new file mode 100644
index 0000000000..0e854e0da0
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_RedisCachePublicNetworkAccessEnabled.py
@@ -0,0 +1,74 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.RedisCachePublicNetworkAccessEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRedisCachePublicNetworkAccessEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ enable_non_ssl_port = false
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = true
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_no_param(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ enable_non_ssl_port = false
+ minimum_tls_version = "1.2"
+
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_redis_cache" "example" {
+ name = "example-cache"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ capacity = 2
+ family = "C"
+ sku_name = "Standard"
+ enable_non_ssl_port = false
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = false
+
+ redis_configuration {
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_redis_cache']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_RemoteDebuggingNotEnabled.py b/tests/terraform/checks/resource/azure/test_RemoteDebuggingNotEnabled.py
new file mode 100644
index 0000000000..cf4180f729
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_RemoteDebuggingNotEnabled.py
@@ -0,0 +1,99 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.RemoteDebggingNotEnabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestRemoteDebuggingNotEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ remote_debugging_enabled = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_app_service" "example" {
+ name = "example-app-service"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ app_service_plan_id = azurerm_app_service_plan.example.id
+
+ site_config {
+ dotnet_framework_version = "v4.0"
+ scm_type = "LocalGit"
+ }
+
+ app_settings = {
+ "SOME_KEY" = "some-value"
+ }
+
+ connection_string {
+ name = "Database"
+ type = "SQLServer"
+ value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
+ }
+ remote_debugging_enabled = false
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_app_service']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_SQLServerAuditPolicyRetentionPeriod.py b/tests/terraform/checks/resource/azure/test_SQLServerAuditPolicyRetentionPeriod.py
new file mode 100644
index 0000000000..0aac00fc6d
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_SQLServerAuditPolicyRetentionPeriod.py
@@ -0,0 +1,53 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.SQLServerAuditPolicyRetentionPeriod import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSQLServerAuditingEnabled(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+resource "azurerm_mssql_database_extended_auditing_policy" "example" {
+ database_id = azurerm_mssql_database.examplea.id
+ storage_endpoint = azurerm_storage_account.examplea.primary_blob_endpoint
+ storage_account_access_key = azurerm_storage_account.examplea.primary_access_key
+ storage_account_access_key_is_secondary = false
+ retention_in_days = 89
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_database_extended_auditing_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_missingisfailure(self):
+ hcl_res = hcl2.loads("""
+resource "azurerm_mssql_database_extended_auditing_policy" "example" {
+ database_id = azurerm_mssql_database.examplea.id
+ storage_endpoint = azurerm_storage_account.examplea.primary_blob_endpoint
+ storage_account_access_key = azurerm_storage_account.examplea.primary_access_key
+ storage_account_access_key_is_secondary = false
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_database_extended_auditing_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_database_extended_auditing_policy" "example" {
+ database_id = azurerm_mssql_database.examplea.id
+ storage_endpoint = azurerm_storage_account.examplea.primary_blob_endpoint
+ storage_account_access_key = azurerm_storage_account.examplea.primary_access_key
+ storage_account_access_key_is_secondary = false
+ retention_in_days = 90
+}
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_database_extended_auditing_policy']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_SQLServerNoPublicAccess.py b/tests/terraform/checks/resource/azure/test_SQLServerNoPublicAccess.py
index 098b4fc65c..67657d4c67 100644
--- a/tests/terraform/checks/resource/azure/test_SQLServerNoPublicAccess.py
+++ b/tests/terraform/checks/resource/azure/test_SQLServerNoPublicAccess.py
@@ -22,6 +22,20 @@ def test_failure(self):
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)
+ def test_success_allow_azure_services(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mysql_firewall_rule" "example" {
+ name = "office"
+ resource_group_name = azurerm_resource_group.example.name
+ server_name = azurerm_mysql_server.example.name
+ start_ip_address = "0.0.0.0"
+ end_ip_address = "0.0.0.0"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mysql_firewall_rule']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
def test_success(self):
hcl_res = hcl2.loads("""
resource "azurerm_mysql_firewall_rule" "example" {
diff --git a/tests/terraform/checks/resource/azure/test_SQLServerPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_SQLServerPublicAccessDisabled.py
new file mode 100644
index 0000000000..683504e497
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_SQLServerPublicAccessDisabled.py
@@ -0,0 +1,75 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.SQLServerPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSQLServerPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_server" "example" {
+ name = "mssqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+ administrator_login = "missadministrator"
+ administrator_login_password = "thisIsKat11"
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = true
+ azuread_administrator {
+ login_username = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_server" "example" {
+ name = "mssqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+ administrator_login = "missadministrator"
+ administrator_login_password = "thisIsKat11"
+ minimum_tls_version = "1.2"
+ azuread_administrator {
+ login_username = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_mssql_server" "example" {
+ name = "mssqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+ administrator_login = "missadministrator"
+ administrator_login_password = "thisIsKat11"
+ minimum_tls_version = "1.2"
+ public_network_access_enabled = false
+ azuread_administrator {
+ login_username = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_mssql_server']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_SecretContentType.py b/tests/terraform/checks/resource/azure/test_SecretContentType.py
new file mode 100644
index 0000000000..c416a6f992
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_SecretContentType.py
@@ -0,0 +1,47 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.SecretContentType import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSecretContentType(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault_secret" "example" {
+ name = "secret-sauce"
+ value = "szechuan"
+ key_vault_id = azurerm_key_vault.example.id
+
+ tags = {
+ environment = "Production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault_secret']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_key_vault_secret" "example" {
+ name = "secret-sauce"
+ value = "szechuan"
+ key_vault_id = azurerm_key_vault.example.id
+ content_type = "text"
+
+ tags = {
+ environment = "Production"
+ }
+ expiration_date = "2020-12-30T20:00:00Z"
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_key_vault_secret']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_SecurityCenterContactEmails.py b/tests/terraform/checks/resource/azure/test_SecurityCenterContactEmails.py
new file mode 100644
index 0000000000..b5ce98f9ae
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_SecurityCenterContactEmails.py
@@ -0,0 +1,40 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.SecurityCenterContactEmails import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSecurityCenterContactEmails(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_contact" "example" {
+ phone = "+1-555-555-5555"
+
+ alert_notifications = true
+ alerts_to_admins = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_contact']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_security_center_contact" "example" {
+ email = "contact@example.com"
+ phone = "+1-555-555-5555"
+
+ alert_notifications = true
+ alerts_to_admins = true
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_security_center_contact']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_StorageAccountDisablePublicAccess.py b/tests/terraform/checks/resource/azure/test_StorageAccountDisablePublicAccess.py
new file mode 100644
index 0000000000..5b4cac52fc
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_StorageAccountDisablePublicAccess.py
@@ -0,0 +1,69 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.StorageAccountDisablePublicAccess import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestStorageAccountDisablePublicAccess(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+ enable_https_traffic_only = false
+ allow_blob_public_access = true
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+ allow_blob_public_access = false
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_StorageAccountEnablesSecureTransfer.py b/tests/terraform/checks/resource/azure/test_StorageAccountEnablesSecureTransfer.py
new file mode 100644
index 0000000000..cfb2bb6a9c
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_StorageAccountEnablesSecureTransfer.py
@@ -0,0 +1,67 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.StorageAccountEnablesSecureTransfer import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestStorageAccountEnablesSecureTransfer(unittest.TestCase):
+
+ def test_failure(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+ enable_https_traffic_only = false
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+ enable_https_traffic_only = true
+
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+ def test_success_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_account" "example" {
+ name = "storageaccountname"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+ tags = {
+ environment = "staging"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_account']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_StorageSyncPublicAccessDisabled.py b/tests/terraform/checks/resource/azure/test_StorageSyncPublicAccessDisabled.py
new file mode 100644
index 0000000000..68dfdd8a92
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_StorageSyncPublicAccessDisabled.py
@@ -0,0 +1,60 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.StorageSyncPublicAccessDisabled import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestStorageSyncPublicAccessDisabled(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_sync" "test" {
+ name = "example-storage-sync"
+ resource_group_name = azurerm_resource_group.test.name
+ location = azurerm_resource_group.test.location
+ tags = {
+ foo = "bar"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_sync']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_sync" "test" {
+ name = "example-storage-sync"
+ resource_group_name = azurerm_resource_group.test.name
+ location = azurerm_resource_group.test.location
+ incoming_traffic_policy = "AllowAllTraffic"
+ tags = {
+ foo = "bar"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_sync']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_storage_sync" "test" {
+ name = "example-storage-sync"
+ resource_group_name = azurerm_resource_group.test.name
+ location = azurerm_resource_group.test.location
+ incoming_traffic_policy = AllowVirtualNetworksOnly
+ tags = {
+ foo = "bar"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_storage_sync']['test']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_SynapseWorkspaceEnablesManagedVirtualNetworks.py b/tests/terraform/checks/resource/azure/test_SynapseWorkspaceEnablesManagedVirtualNetworks.py
new file mode 100644
index 0000000000..0f929a8caf
--- /dev/null
+++ b/tests/terraform/checks/resource/azure/test_SynapseWorkspaceEnablesManagedVirtualNetworks.py
@@ -0,0 +1,87 @@
+import unittest
+
+import hcl2
+
+from checkov.terraform.checks.resource.azure.SynapseWorkspaceEnablesManagedVirtualNetworks import check
+from checkov.common.models.enums import CheckResult
+
+
+class TestSynapseWorkspaceEnablesManagedVirtualNetworks(unittest.TestCase):
+
+ def test_failure_1(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_synapse_workspace" "example" {
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.example.id
+ sql_administrator_login = "sqladminuser"
+ sql_administrator_login_password = "H@Sh1CoR3!"
+ managed_virtual_network_enabled = false
+ aad_admin {
+ login = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ tenant_id = "00000000-0000-0000-0000-000000000000"
+ }
+
+ tags = {
+ Env = "production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_synapse_workspace']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_failure_2(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_synapse_workspace" "example" {
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.example.id
+ sql_administrator_login = "sqladminuser"
+ sql_administrator_login_password = "H@Sh1CoR3!"
+ aad_admin {
+ login = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ tenant_id = "00000000-0000-0000-0000-000000000000"
+ }
+
+ tags = {
+ Env = "production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_synapse_workspace']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.FAILED, scan_result)
+
+ def test_success(self):
+ hcl_res = hcl2.loads("""
+ resource "azurerm_synapse_workspace" "example" {
+ name = "example"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.example.id
+ sql_administrator_login = "sqladminuser"
+ sql_administrator_login_password = "H@Sh1CoR3!"
+ managed_virtual_network_enabled = true
+ aad_admin {
+ login = "AzureAD Admin"
+ object_id = "00000000-0000-0000-0000-000000000000"
+ tenant_id = "00000000-0000-0000-0000-000000000000"
+ }
+
+ tags = {
+ Env = "production"
+ }
+ }
+ """)
+ resource_conf = hcl_res['resource'][0]['azurerm_synapse_workspace']['example']
+ scan_result = check.scan_resource_conf(conf=resource_conf)
+ self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/checks/resource/azure/test_VMCredsInCustomData.py b/tests/terraform/checks/resource/azure/test_VMCredsInCustomData.py
index 583300840d..8517b3f88b 100644
--- a/tests/terraform/checks/resource/azure/test_VMCredsInCustomData.py
+++ b/tests/terraform/checks/resource/azure/test_VMCredsInCustomData.py
@@ -12,7 +12,7 @@
os_profile {
computer_name = "hostname"
custom_data = < None:
+ self.check_list_before = resource_registry.checks.copy() # copy
+ super().setUp()
+
+ def tearDown(self) -> None:
+ super().tearDown()
+ resource_registry.checks = self.check_list_before
+ self.check_list_before = None
diff --git a/tests/terraform/context_parsers/test_locals_parser.py b/tests/terraform/context_parsers/test_locals_parser.py
new file mode 100644
index 0000000000..3e912c1316
--- /dev/null
+++ b/tests/terraform/context_parsers/test_locals_parser.py
@@ -0,0 +1,44 @@
+import unittest
+from checkov.terraform.parser import Parser
+from checkov.terraform.context_parsers.registry import parser_registry
+import os
+
+
+class TestLocalsContextParser(unittest.TestCase):
+
+ def setup_dir(self, rel_path):
+ test_root_dir = os.path.dirname(os.path.realpath(__file__)) + rel_path
+ tf_definitions = {}
+ parsing_errors = {}
+ definitions_context = {}
+ Parser().parse_directory(directory=test_root_dir,
+ out_definitions=tf_definitions,
+ out_parsing_errors=parsing_errors)
+ for definition in tf_definitions.items():
+ definitions_context = parser_registry.enrich_definitions_context(definition)
+ return definitions_context
+
+ def test_assignments_exists(self):
+ definitions_context = self.setup_dir('/../evaluation/resources/default_evaluation/')
+ assignments = definitions_context[os.path.dirname(os.path.realpath(__file__)) + '/../evaluation/resources/default_evaluation/main.tf']['locals']['assignments']
+ self.assertIsNotNone(assignments)
+
+ expected_assignments = {'dummy_with_dash': '${format("-%s",var.dummy_1)}', 'dummy_with_comma': '${format(":%s",var.dummy_1)}', 'bucket_name': '${var.bucket_name}'}
+
+ for k, v in assignments.items():
+ self.assertEqual(expected_assignments[k], v)
+
+ def test_assignment_value(self):
+ definitions_context = self.setup_dir('/../evaluation/resources/locals_evaluation/')
+ assignments = definitions_context[
+ os.path.dirname(os.path.realpath(__file__)) + '/../evaluation/resources/locals_evaluation/main.tf'][
+ 'locals'].get('assignments')
+ self.assertIsNotNone(assignments)
+ self.assertEqual(1, len(assignments.items()))
+ for k, v in assignments.items():
+ self.assertEqual(k, 'common_tags')
+ self.assertIsInstance(v, dict)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/terraform/context_parsers/test_variable_context_parser.py b/tests/terraform/context_parsers/test_variable_context_parser.py
index 67c8c0edc0..9f50b62a15 100644
--- a/tests/terraform/context_parsers/test_variable_context_parser.py
+++ b/tests/terraform/context_parsers/test_variable_context_parser.py
@@ -25,7 +25,7 @@ def test_assignments_exists(self):
'assignments'))
def test_assignment_value(self):
- self.assertEqual(
+ self.assertIs(
self.definitions_context[os.path.dirname(
os.path.realpath(__file__)) + '/../evaluation/resources/default_evaluation/variables.tf'][
'variable'].get(
diff --git a/tests/terraform/context_parsers/test_variable_context_parser2.py b/tests/terraform/context_parsers/test_variable_context_parser2.py
index 72196be280..98ebb3450e 100644
--- a/tests/terraform/context_parsers/test_variable_context_parser2.py
+++ b/tests/terraform/context_parsers/test_variable_context_parser2.py
@@ -26,7 +26,7 @@ def test_assignments_exists(self):
'assignments'))
def test_assignment_value(self):
- self.assertEqual(
+ self.assertIs(
self.definitions_context[os.path.dirname(
os.path.realpath(__file__)) + '/../evaluation/resources/default_evaluation/variables.tf'][
'variable'].get(
diff --git a/tests/terraform/evaluation/resources/default_evaluation/variables.tf b/tests/terraform/evaluation/resources/default_evaluation/variables.tf
index 2dc44c2e17..3fdabe617f 100644
--- a/tests/terraform/evaluation/resources/default_evaluation/variables.tf
+++ b/tests/terraform/evaluation/resources/default_evaluation/variables.tf
@@ -43,6 +43,6 @@ variable "dummy_1" {
default = "dummy_1"
}
-variable "user_exists" {
+variable"user_exists"{
default = false
}
\ No newline at end of file
diff --git a/tests/terraform/evaluation/resources/locals_evaluation/main.tf b/tests/terraform/evaluation/resources/locals_evaluation/main.tf
new file mode 100644
index 0000000000..db2030d666
--- /dev/null
+++ b/tests/terraform/evaluation/resources/locals_evaluation/main.tf
@@ -0,0 +1,5 @@
+locals = {
+ common_tags = {
+ Env = "dev"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/__init__.py b/tests/terraform/graph/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks/__init__.py b/tests/terraform/graph/checks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks/resources/ALBProtectedByWAF/expected.yaml b/tests/terraform/graph/checks/resources/ALBProtectedByWAF/expected.yaml
new file mode 100644
index 0000000000..eee4a2cccf
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/ALBProtectedByWAF/expected.yaml
@@ -0,0 +1,7 @@
+pass:
+ - "aws_lb.lb_good_1"
+ - "aws_lb.ignore"
+
+fail:
+ - "aws_lb.lb_bad_1"
+ - "aws_lb.lb_bad_2"
diff --git a/tests/terraform/graph/checks/resources/ALBProtectedByWAF/main.tf b/tests/terraform/graph/checks/resources/ALBProtectedByWAF/main.tf
new file mode 100644
index 0000000000..169377e689
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/ALBProtectedByWAF/main.tf
@@ -0,0 +1,24 @@
+resource "aws_lb" "lb_good_1" {
+ internal= false
+}
+
+
+resource "aws_wafregional_web_acl_association" "foo" {
+ resource_arn = aws_lb.lb_good_1.arn
+ web_acl_id = aws_wafregional_web_acl.foo.id
+}
+
+//public no WAF
+resource "aws_lb" "lb_bad_1" {
+ internal=false
+}
+
+//internal should ignore
+resource "aws_lb" "ignore" {
+ internal= true
+}
+
+//public internal not set (takes default - public)
+resource "aws_lb" "lb_bad_2" {
+}
+
diff --git a/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/expected.yaml b/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/expected.yaml
new file mode 100644
index 0000000000..adb62b7315
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/expected.yaml
@@ -0,0 +1,7 @@
+pass:
+ - "aws_lb.lb_good_1"
+ - "aws_lb.lb_good_2"
+ - "aws_lb.lb_good_3"
+fail:
+ - "aws_lb.lb_bad_1"
+ - "aws_lb.lb_bad_2"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/main.tf b/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/main.tf
new file mode 100644
index 0000000000..1a8314084d
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/ALBRedirectsHTTPToHTTPS/main.tf
@@ -0,0 +1,80 @@
+resource "aws_lb" "lb_good_1" {
+}
+
+resource "aws_lb" "lb_good_2" {
+}
+
+resource "aws_lb" "lb_good_3" {
+}
+
+resource "aws_lb" "lb_bad_1" {
+}
+
+resource "aws_lb" "lb_bad_2" {
+}
+
+
+resource "aws_lb_listener" "listener_good_1" {
+ load_balancer_arn = aws_lb.lb_good_1.arn
+ port = "443"
+ protocol = "HTTPS"
+
+ default_action {
+ type = "action"
+ }
+}
+
+resource "aws_lb_listener" "listener_good_2" {
+ load_balancer_arn = aws_lb.lb_good_2.arn
+ port = "80"
+ protocol = "HTTP"
+
+ default_action {
+ type = "redirect"
+
+ redirect {
+ port = "443"
+ protocol = "HTTPS"
+ status_code = "HTTP_301"
+ }
+
+ }
+}
+
+resource "aws_lb_listener" "listener_good_3" {
+ load_balancer_arn = aws_lb.lb_good_3.arn
+ port = 80 #as an int
+ protocol = "HTTP"
+
+ default_action {
+ type = "redirect"
+
+ redirect {
+ port = "443"
+ protocol = "HTTPS"
+ status_code = "HTTP_301"
+ }
+
+ }
+}
+
+resource "aws_lb_listener" "listener_bad_1" {
+ load_balancer_arn = aws_lb.lb_bad_1.arn
+ port = "80"
+ protocol = "HTTP"
+
+ default_action {
+ type = "some-action"
+ }
+}
+
+resource "aws_lb_listener" "listener_bad_2" {
+ load_balancer_arn = aws_lb.lb_bad_2.arn
+ port = 80
+ protocol = "HTTP"
+
+ default_action {
+ type = "some-action"
+ }
+}
+
diff --git a/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/expected.yaml b/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/expected.yaml
new file mode 100644
index 0000000000..fc3b75f9e6
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "aws_emr_cluster.cluster_ok"
+fail:
+ - "aws_emr_cluster.cluster_not_connected"
+ - "aws_emr_cluster.cluster_connected_to_wrong_group"
diff --git a/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/main.tf b/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/main.tf
new file mode 100644
index 0000000000..93f9f98511
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AMRClustersNotOpenToInternet/main.tf
@@ -0,0 +1,72 @@
+resource "aws_emr_cluster" "cluster_ok" {
+ name = "emr-test-arn"
+ release_label = "emr-4.6.0"
+ applications = ["Spark"]
+
+ ec2_attributes {
+ emr_managed_master_security_group = aws_security_group.block_access_ok.id
+ emr_managed_slave_security_group = aws_security_group.block_access_ok.id
+ instance_profile = "connected_to_aws_iam_instance_profile"
+ }
+}
+
+resource "aws_security_group" "block_access_ok" {
+ name = "block_access"
+ description = "Block all traffic"
+
+ ingress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["10.0.0.1/10"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["10.0.0.10/10"]
+ }
+}
+
+resource "aws_emr_cluster" "cluster_not_connected" {
+ name = "emr-test-arn"
+ release_label = "emr-4.6.0"
+ applications = ["Spark"]
+
+ ec2_attributes {
+ instance_profile = "connected_to_aws_iam_instance_profile"
+ }
+}
+
+
+resource "aws_emr_cluster" "cluster_connected_to_wrong_group" {
+ name = "emr-test-arn"
+ release_label = "emr-4.6.0"
+ applications = ["Spark"]
+
+ ec2_attributes {
+ emr_managed_master_security_group = aws_security_group.block_access_not_ok.id
+ emr_managed_slave_security_group = aws_security_group.block_access_not_ok.id
+ instance_profile = "connected_to_aws_iam_instance_profile"
+ }
+}
+
+resource "aws_security_group" "block_access_not_ok" {
+ name = "block_access"
+ description = "Block all traffic"
+
+ ingress {
+ from_port = 0
+ to_port = 65535
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
diff --git a/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/expected.yaml b/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/expected.yaml
new file mode 100644
index 0000000000..0a3cb07492
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_api_gateway_stage.ok_example"
+fail:
+ - "aws_api_gateway_stage.not_connected"
diff --git a/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/main.tf b/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/main.tf
new file mode 100644
index 0000000000..91c7a96a1b
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/APIGWLoggingLevelsDefinedProperly/main.tf
@@ -0,0 +1,105 @@
+resource "aws_api_gateway_rest_api" "ok_example" {
+ body = jsonencode({
+ openapi = "3.0.1"
+ info = {
+ title = "ok_example"
+ version = "1.0"
+ }
+ paths = {
+ "/path1" = {
+ get = {
+ x-amazon-apigateway-integration = {
+ httpMethod = "GET"
+ payloadFormatVersion = "1.0"
+ type = "HTTP_PROXY"
+ uri = "https://ip-ranges.amazonaws.com/ip-ranges.json"
+ }
+ }
+ }
+ }
+ })
+
+ name = "ok_example"
+}
+
+resource "aws_api_gateway_deployment" "ok_example" {
+ rest_api_id = aws_api_gateway_rest_api.ok_example.id
+
+ triggers = {
+ redeployment = sha1(jsonencode(aws_api_gateway_rest_api.ok_example.body))
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_api_gateway_stage" "ok_example" {
+ deployment_id = aws_api_gateway_deployment.ok_example.id
+ rest_api_id = aws_api_gateway_rest_api.ok_example.id
+ stage_name = "ok_example"
+}
+
+resource "aws_api_gateway_method_settings" "all" {
+ rest_api_id = aws_api_gateway_rest_api.ok_example.id
+ stage_name = aws_api_gateway_stage.ok_example.stage_name
+ method_path = "*/*"
+
+ settings {
+ metrics_enabled = true
+ logging_level = "ERROR"
+ }
+}
+
+resource "aws_api_gateway_method_settings" "path_specific" {
+ rest_api_id = aws_api_gateway_rest_api.ok_example.id
+ stage_name = aws_api_gateway_stage.ok_example.stage_name
+ method_path = "path1/GET"
+
+ settings {
+ metrics_enabled = true
+ logging_level = "INFO"
+ }
+}
+
+# Bad Example 1 - Not connected or connected with wrong logs errors
+
+
+resource "aws_api_gateway_deployment" "not_connected" {
+ rest_api_id = aws_api_gateway_rest_api.not_connected.id
+
+ triggers = {
+ redeployment = sha1(jsonencode(aws_api_gateway_rest_api.not_connected.body))
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_api_gateway_stage" "not_connected" {
+ deployment_id = aws_api_gateway_deployment.not_connected.id
+ rest_api_id = aws_api_gateway_rest_api.not_connected.id
+ stage_name = "not_connected"
+}
+
+resource "aws_api_gateway_method_settings" "all" {
+ rest_api_id = aws_api_gateway_rest_api.not_connected.id
+ method_path = "*/*"
+
+ settings {
+ metrics_enabled = true
+ logging_level = "ERROR"
+ }
+}
+
+resource "aws_api_gateway_method_settings" "path_specific" {
+ rest_api_id = aws_api_gateway_rest_api.not_connected.id
+ stage_name = aws_api_gateway_stage.not_connected.stage_name
+ method_path = "path1/GET"
+
+ settings {
+ metrics_enabled = true
+ logging_level = "DEBUG"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/APIProtectedByWAF/expected.yaml b/tests/terraform/graph/checks/resources/APIProtectedByWAF/expected.yaml
new file mode 100644
index 0000000000..9d43143025
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/APIProtectedByWAF/expected.yaml
@@ -0,0 +1,6 @@
+pass:
+ - "aws_api_gateway_stage.pass"
+ - "aws_api_gateway_stage.no_api"
+ - "aws_api_gateway_stage.private"
+fail:
+ - "aws_api_gateway_stage.no_assoc"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/APIProtectedByWAF/main.tf b/tests/terraform/graph/checks/resources/APIProtectedByWAF/main.tf
new file mode 100644
index 0000000000..25e921d3fd
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/APIProtectedByWAF/main.tf
@@ -0,0 +1,68 @@
+resource "aws_api_gateway_rest_api" "pass" {
+ name = var.name
+
+ policy = ""
+
+ endpoint_configuration {
+ types = ["REGIONAL"]
+ }
+}
+
+resource "aws_api_gateway_rest_api" "private" {
+ name = var.name
+
+ policy = ""
+
+ endpoint_configuration {
+ types = ["PRIVATE"]
+ }
+}
+
+resource "aws_api_gateway_rest_api" "no_stage" {
+ name = var.name
+
+ policy = ""
+
+ endpoint_configuration {
+ types = ["REGIONAL"]
+ }
+}
+
+resource "aws_api_gateway_rest_api" "no_assoc" {
+ name = var.name
+
+ policy = ""
+
+ endpoint_configuration {
+ types = ["REGIONAL"]
+ }
+}
+
+resource "aws_api_gateway_stage" "no_assoc" {
+ deployment_id = aws_api_gateway_deployment.example.id
+ rest_api_id = aws_api_gateway_rest_api.no_assoc.id
+ stage_name = "example"
+}
+
+resource "aws_api_gateway_stage" "private" {
+ deployment_id = aws_api_gateway_deployment.example.id
+ rest_api_id = aws_api_gateway_rest_api.private.id
+ stage_name = "example"
+}
+
+resource "aws_api_gateway_stage" "no_api" {
+ deployment_id = aws_api_gateway_deployment.example.id
+ rest_api_id = aws_api_gateway_rest_api.no_api.id
+ stage_name = "example"
+}
+
+resource "aws_api_gateway_stage" "pass" {
+ deployment_id = aws_api_gateway_deployment.example.id
+ rest_api_id = aws_api_gateway_rest_api.pass.id
+ stage_name = "example"
+}
+
+resource "aws_wafregional_web_acl_association" "pass" {
+ resource_arn = aws_api_gateway_stage.pass.arn
+ web_acl_id = aws_wafregional_web_acl.foo.id
+}
diff --git a/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/expected.yaml b/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/expected.yaml
new file mode 100644
index 0000000000..61343d943c
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_sql_server.sql_server_good"
+fail:
+ - "azurerm_sql_server.sql_server_bad"
diff --git a/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/main.tf b/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/main.tf
new file mode 100644
index 0000000000..19e9ff48a0
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AccessToPostgreSQLFromAzureServicesIsDisabled/main.tf
@@ -0,0 +1,39 @@
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_sql_server" "sql_server_good" {
+ name = "mysqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = "West US"
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_sql_server" "sql_server_bad" {
+ name = "mysqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = "West US"
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+
+resource "azurerm_sql_firewall_rule" "firewall_rule_good" {
+ name = "FirewallRule1"
+ resource_group_name = azurerm_resource_group.example.name
+ server_name = azurerm_sql_server.sql_server_good.name
+ start_ip_address = "10.0.17.62"
+ end_ip_address = "10.0.17.62"
+}
+
+resource "azurerm_sql_firewall_rule" "firewall_rule_bad" {
+ name = "FirewallRule1"
+ resource_group_name = azurerm_resource_group.example.name
+ server_name = azurerm_sql_server.sql_server_bad.name
+ start_ip_address = "0.0.0.0"
+ end_ip_address = "0.0.0.0"
+}
diff --git a/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/expected.yaml b/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/expected.yaml
new file mode 100644
index 0000000000..aa2e4a5ba4
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/expected.yaml
@@ -0,0 +1,6 @@
+pass:
+ - "aws_dynamodb_table.pass"
+ - "aws_dynamodb_table.pass_on_demand"
+fail:
+ - "aws_dynamodb_table.fail"
+ - "aws_dynamodb_table.fail_no_policy"
diff --git a/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/main.tf b/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/main.tf
new file mode 100644
index 0000000000..79be7596f0
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AutoScalingEnableOnDynamoDBTables/main.tf
@@ -0,0 +1,116 @@
+# pass
+
+resource "aws_dynamodb_table" "pass" {
+ name = "user"
+ hash_key = "user-id"
+ billing_mode = "PROVISIONED"
+ read_capacity = 10
+ write_capacity = 10
+
+ attribute {
+ name = "user-id"
+ type = "S"
+ }
+}
+
+resource "aws_appautoscaling_target" "pass" {
+ resource_id = "table/${aws_dynamodb_table.pass.name}"
+ scalable_dimension = "dynamodb:table:ReadCapacityUnits"
+ service_namespace = "dynamodb"
+ min_capacity = 1
+ max_capacity = 15
+}
+
+resource "aws_appautoscaling_policy" "pass" {
+ name = "rcu-auto-scaling"
+ service_namespace = aws_appautoscaling_target.pass.service_namespace
+ scalable_dimension = aws_appautoscaling_target.pass.scalable_dimension
+ resource_id = aws_appautoscaling_target.pass.resource_id
+ policy_type = "TargetTrackingScaling"
+
+ target_tracking_scaling_policy_configuration {
+ predefined_metric_specification {
+ predefined_metric_type = "DynamoDBReadCapacityUtilization"
+ }
+
+ target_value = 75
+ scale_in_cooldown = 300
+ scale_out_cooldown = 300
+ }
+}
+
+resource "aws_dynamodb_table" "pass_on_demand" {
+ name = "user"
+ hash_key = "user-id"
+ billing_mode = "PAY_PER_REQUEST"
+
+ attribute {
+ name = "user-id"
+ type = "S"
+ }
+}
+
+# fail
+
+resource "aws_dynamodb_table" "fail" {
+ name = "user"
+ hash_key = "user-id"
+ billing_mode = "PROVISIONED"
+ read_capacity = 10
+ write_capacity = 10
+
+ attribute {
+ name = "user-id"
+ type = "S"
+ }
+}
+
+resource "aws_dynamodb_table" "fail_no_policy" {
+ name = "user"
+ hash_key = "user-id"
+ billing_mode = "PROVISIONED"
+ read_capacity = 10
+ write_capacity = 10
+
+ attribute {
+ name = "user-id"
+ type = "S"
+ }
+}
+
+resource "aws_appautoscaling_target" "fail_no_policy" {
+ resource_id = "table/${aws_dynamodb_table.fail_no_policy.name}"
+ scalable_dimension = "dynamodb:table:ReadCapacityUnits"
+ service_namespace = "dynamodb"
+ min_capacity = 1
+ max_capacity = 15
+}
+
+# unknown
+
+resource "aws_appautoscaling_target" "ecs" {
+ max_capacity = 4
+ min_capacity = 1
+ resource_id = "service/clusterName/serviceName"
+ scalable_dimension = "ecs:service:DesiredCount"
+ service_namespace = "ecs"
+}
+
+resource "aws_appautoscaling_policy" "ecs" {
+ name = "scale-down"
+ policy_type = "StepScaling"
+ resource_id = aws_appautoscaling_target.ecs.resource_id
+ scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
+ service_namespace = aws_appautoscaling_target.ecs.service_namespace
+
+ step_scaling_policy_configuration {
+ adjustment_type = "ChangeInCapacity"
+ cooldown = 60
+ metric_aggregation_type = "Maximum"
+
+ step_adjustment {
+ metric_interval_upper_bound = 0
+ scaling_adjustment = -1
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/expected.yaml b/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/expected.yaml
new file mode 100644
index 0000000000..cd9a25b9ce
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_autoscaling_attachment.test_ok_attachment"
+fail:
+ - "aws_autoscaling_attachment.test_bad_attachment"
diff --git a/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/main.tf b/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/main.tf
new file mode 100644
index 0000000000..81b05d407f
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AutoScallingEnabledELB/main.tf
@@ -0,0 +1,115 @@
+resource "aws_autoscaling_group" "autoscalling_ok" {
+ max_size = 5
+ min_size = 2
+ health_check_grace_period = 300
+ health_check_type = "ELB"
+ desired_capacity = 4
+ force_delete = true
+
+ lifecycle {
+ ignore_changes = [load_balancers, target_group_arns]
+ }
+}
+
+resource "aws_autoscaling_attachment" "test_ok_attachment" {
+ autoscaling_group_name = aws_autoscaling_group.autoscalling_ok.id
+ elb = aws_elb.test_ok.id
+}
+
+resource "aws_elb" "test_ok" {
+ name = "foobar-terraform-elb"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+
+ access_logs {
+ bucket = "foo"
+ bucket_prefix = "bar"
+ interval = 60
+ }
+
+ listener {
+ instance_port = 8000
+ instance_protocol = "http"
+ lb_port = 80
+ lb_protocol = "http"
+ }
+
+ listener {
+ instance_port = 8000
+ instance_protocol = "http"
+ lb_port = 443
+ lb_protocol = "https"
+ ssl_certificate_id = "arn:aws:iam::123456789012:server-certificate/certName"
+ }
+
+ health_check {
+ healthy_threshold = 2
+ unhealthy_threshold = 2
+ timeout = 3
+ target = "HTTP:8000/"
+ interval = 30
+ }
+
+ instances = [aws_instance.foo.id]
+ cross_zone_load_balancing = true
+ idle_timeout = 400
+ connection_draining = true
+ connection_draining_timeout = 400
+
+ tags = {
+ Name = "foobar-terraform-elb"
+ }
+}
+
+resource "aws_autoscaling_group" "autoscalling_bad" {
+ max_size = 5
+ min_size = 2
+ health_check_grace_period = 300
+ health_check_type = "ELB"
+ desired_capacity = 4
+ force_delete = true
+
+ lifecycle {
+ ignore_changes = [load_balancers, target_group_arns]
+ }
+}
+
+resource "aws_autoscaling_attachment" "test_bad_attachment" {
+ autoscaling_group_name = aws_autoscaling_group.autoscalling_bad.id
+ elb = aws_elb.test_bad.id
+}
+
+resource "aws_elb" "test_bad" {
+ name = "foobar-terraform-elb"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+
+ access_logs {
+ bucket = "foo"
+ bucket_prefix = "bar"
+ interval = 60
+ }
+
+ listener {
+ instance_port = 8000
+ instance_protocol = "http"
+ lb_port = 80
+ lb_protocol = "http"
+ }
+
+ listener {
+ instance_port = 8000
+ instance_protocol = "http"
+ lb_port = 443
+ lb_protocol = "https"
+ ssl_certificate_id = "arn:aws:iam::123456789012:server-certificate/certName"
+ }
+
+ instances = [aws_instance.foo.id]
+ cross_zone_load_balancing = true
+ idle_timeout = 400
+ connection_draining = true
+ connection_draining_timeout = 400
+
+ tags = {
+ Name = "foobar-terraform-elb"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/expected.yaml b/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/expected.yaml
new file mode 100644
index 0000000000..61343d943c
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_sql_server.sql_server_good"
+fail:
+ - "azurerm_sql_server.sql_server_bad"
diff --git a/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/main.tf b/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/main.tf
new file mode 100644
index 0000000000..3c0c2407d2
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureActiveDirectoryAdminIsConfigured/main.tf
@@ -0,0 +1,32 @@
+data "azurerm_client_config" "current" {}
+
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_sql_server" "sql_server_good" {
+ name = "mysqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_sql_server" "sql_server_bad" {
+ name = "mysqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_sql_active_directory_administrator" "example" {
+ server_name = azurerm_sql_server.sql_server_good.name
+ resource_group_name = azurerm_resource_group.example.name
+ login = "sqladmin"
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+}
diff --git a/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/expected.yaml b/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/expected.yaml
new file mode 100644
index 0000000000..6cf0f649b8
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/expected.yaml
@@ -0,0 +1,6 @@
+pass:
+ - "azurerm_virtual_machine.virtual_machine_good_1"
+fail:
+ - "azurerm_virtual_machine.virtual_machine_bad_1"
+ - "azurerm_virtual_machine.virtual_machine_bad_2"
+ - "azurerm_virtual_machine.virtual_machine_bad_3"
diff --git a/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/main.tf b/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/main.tf
new file mode 100644
index 0000000000..648ba4f6be
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs/main.tf
@@ -0,0 +1,87 @@
+resource "azurerm_virtual_machine" "virtual_machine_good_1" {
+ name = "acctvm"
+ location = "location"
+ resource_group_name = "group"
+ network_interface_ids = ["id"]
+ vm_size = "Standard_F2"
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_bad_1" {
+ name = "acctvm"
+ location = "location"
+ resource_group_name = "group"
+ network_interface_ids = ["id"]
+ vm_size = "Standard_F2"
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_bad_2" {
+ name = "acctvm"
+ location = "location"
+ resource_group_name = "group"
+ network_interface_ids = ["id"]
+ vm_size = "Standard_F2"
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_bad_3" {
+ name = "acctvm"
+ location = "location"
+ resource_group_name = "group"
+ network_interface_ids = ["id"]
+ vm_size = "Standard_F2"
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ }
+}
+
+resource "azurerm_virtual_machine_extension" "extension_good_1" {
+ name = "hostname"
+ virtual_machine_id = azurerm_virtual_machine.virtual_machine_good_1.id
+ publisher = "Microsoft.Azure.Security"
+ type = "IaaSAntimalware"
+ type_handler_version = "2.0"
+ auto_upgrade_minor_version = true
+}
+
+resource "azurerm_virtual_machine_extension" "extension_bad_1" {
+ name = "hostname"
+ virtual_machine_id = azurerm_virtual_machine.virtual_machine_bad_1.id
+ publisher = "Microsoft.Azure.Extensions"
+ type = "IaaSAntimalware"
+ type_handler_version = "2.0"
+ auto_upgrade_minor_version = true
+}
+
+resource "azurerm_virtual_machine_extension" "extension_bad_2" {
+ name = "hostname"
+ virtual_machine_id = azurerm_virtual_machine.virtual_machine_bad_2.id
+ publisher = "Microsoft.Azure.Security"
+ type = "IaaSAntimalware"
+ type_handler_version = "2.0"
+ auto_upgrade_minor_version = false
+}
+
+resource "azurerm_virtual_machine_extension" "extension_bad_3" {
+ name = "hostname"
+ virtual_machine_id = azurerm_virtual_machine.virtual_machine_bad_3.id
+ publisher = "Microsoft.Azure.Security"
+ type = "CustomScript"
+ type_handler_version = "2.0"
+ auto_upgrade_minor_version = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/expected.yaml b/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/expected.yaml
new file mode 100644
index 0000000000..43ad90c0ab
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_data_factory.data_factory_good"
+fail:
+ - "azurerm_data_factory.data_factory_bad"
diff --git a/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/main.tf b/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/main.tf
new file mode 100644
index 0000000000..1f0ad3a6cd
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureDataFactoriesEncryptedWithCustomerManagedKey/main.tf
@@ -0,0 +1,18 @@
+resource "azurerm_data_factory" "data_factory_good" {
+ name = "example"
+ location = "location"
+ resource_group_name = "group"
+}
+
+resource "azurerm_data_factory" "data_factory_bad" {
+ name = "example"
+ location = "location"
+ resource_group_name = "group"
+}
+
+resource "azurerm_data_factory_linked_service_key_vault" "factory_good" {
+ name = "example"
+ resource_group_name = "example"
+ data_factory_name = azurerm_data_factory.data_factory_good.name
+ key_vault_id = "123456"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/expected.yaml b/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/expected.yaml
new file mode 100644
index 0000000000..ee9a887ecd
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "azurerm_sql_server.sql_server_good_1"
+ - "azurerm_sql_server.sql_server_good_2"
+fail:
+ - "azurerm_sql_server.sql_server_bad_1"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/main.tf b/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/main.tf
new file mode 100644
index 0000000000..a211e84c04
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureMSSQLServerHasSecurityAlertPolicy/main.tf
@@ -0,0 +1,40 @@
+resource "azurerm_sql_server" "sql_server_good_1" {
+ name = "mysqlserver"
+ resource_group_name = "group"
+ location = "location"
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_sql_server" "sql_server_good_2" {
+ name = "mysqlserver"
+ resource_group_name = "group"
+ location = "location"
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_sql_server" "sql_server_bad_1" {
+ name = "mysqlserver"
+ resource_group_name = "group"
+ location = "location"
+ version = "12.0"
+ administrator_login = "4dm1n157r470r"
+ administrator_login_password = "4-v3ry-53cr37-p455w0rd"
+}
+
+resource "azurerm_mssql_server_security_alert_policy" "alert_policy_good" {
+ resource_group_name = "group"
+ server_name = azurerm_sql_server.sql_server_good_1.name
+ state = "Enabled"
+ retention_days = 20
+}
+
+resource "azurerm_mssql_server_security_alert_policy" "alert_policy_bad" {
+ resource_group_name = "group"
+ server_name = azurerm_sql_server.sql_server_bad_1.name
+ state = "Disabled"
+ retention_days = 20
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/expected.yaml b/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/expected.yaml
new file mode 100644
index 0000000000..972aa318de
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "azurerm_storage_account.storage_account_good_1"
+fail:
+ - "azurerm_storage_account.storage_account_bad_1"
+ - "azurerm_storage_account.storage_account_bad_2"
diff --git a/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/main.tf b/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/main.tf
new file mode 100644
index 0000000000..d5abccf726
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureStorageAccountsUseCustomerManagedKeyForEncryption/main.tf
@@ -0,0 +1,71 @@
+data "azurerm_client_config" "current" {}
+
+resource "azurerm_key_vault" "example" {
+ name = "examplekv"
+ location = "location"
+ resource_group_name = "group"
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ sku_name = "standard"
+
+ purge_protection_enabled = true
+}
+
+resource "azurerm_key_vault_key" "example" {
+ name = "tfex-key"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 2048
+ key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]
+}
+
+
+resource "azurerm_storage_account" "storage_account_good_1" {
+ name = "examplestor"
+ resource_group_name = "group"
+ location = "location"
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_storage_account" "storage_account_bad_1" {
+ name = "examplestor"
+ resource_group_name = "group"
+ location = "location"
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_storage_account" "storage_account_bad_2" {
+ name = "examplestor"
+ resource_group_name = "group"
+ location = "location"
+ account_tier = "Standard"
+ account_replication_type = "GRS"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_storage_account_customer_managed_key" "managed_key_good" {
+ storage_account_id = azurerm_storage_account.storage_account_good_1.id
+ key_vault_id = azurerm_key_vault.example.id
+ key_name = azurerm_key_vault_key.example.name
+ key_version = "1"
+}
+
+
+resource "azurerm_storage_account_customer_managed_key" "managed_key_bad_1" {
+ storage_account_id = azurerm_storage_account.storage_account_bad_1.id
+ key_vault_id = ""
+ key_name = azurerm_key_vault_key.example.name
+ key_version = "1"
+}
diff --git a/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/expected.yaml b/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/expected.yaml
new file mode 100644
index 0000000000..2903e8ec7a
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_synapse_workspace.workspace_good"
+fail:
+ - "azurerm_synapse_workspace.workspace_bad"
diff --git a/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/main.tf b/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/main.tf
new file mode 100644
index 0000000000..89faa85cb6
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached/main.tf
@@ -0,0 +1,30 @@
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_synapse_workspace" "workspace_good" {
+ name = "example"
+ sql_administrator_login = "sqladminuser"
+ sql_administrator_login_password = "H@Sh1CoR3!"
+ managed_virtual_network_enabled = true
+ tags = {
+ Env = "production"
+ }
+}
+
+resource "azurerm_synapse_workspace" "workspace_bad" {
+ name = "example"
+ sql_administrator_login = "sqladminuser"
+ sql_administrator_login_password = "H@Sh1CoR3!"
+ tags = {
+ Env = "production"
+ }
+}
+
+resource "azurerm_synapse_firewall_rule" "firewall_rule" {
+ name = "AllowAll"
+ synapse_workspace_id = azurerm_synapse_workspace.workspace_bad.id
+ start_ip_address = "0.0.0.0"
+ end_ip_address = "255.255.255.255"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/expected.yaml b/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/expected.yaml
new file mode 100644
index 0000000000..60f88e5292
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/expected.yaml
@@ -0,0 +1,7 @@
+pass:
+ - "azurerm_virtual_machine.virtual_machine_good_1"
+ - "azurerm_virtual_machine.virtual_machine_good_2"
+ - "azurerm_virtual_machine.virtual_machine_good_3"
+fail:
+ - "azurerm_virtual_machine.virtual_machine_bad_1"
+ - "azurerm_virtual_machine.virtual_machine_bad_2"
diff --git a/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/main.tf b/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/main.tf
new file mode 100644
index 0000000000..8a32d2c8cc
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/AzureUnattachedDisksAreEncrypted/main.tf
@@ -0,0 +1,178 @@
+resource "azurerm_resource_group" "group" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_managed_disk" "managed_disk_good_1" {
+ name = "acctestmd"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.group.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+
+ encryption_settings {
+ enabled = true
+ }
+ tags = {
+ environment = "staging"
+ }
+}
+
+resource "azurerm_managed_disk" "managed_disk_good_2" {
+ name = "acctestmd"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.group.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ disk_encryption_set_id = "12345"
+ tags = {
+ environment = "staging"
+ }
+}
+
+resource "azurerm_managed_disk" "managed_disk_good_3" {
+ name = "acctestmd"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.group.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ tags = {
+ environment = "staging"
+ }
+
+ encryption_settings {
+ enabled = true
+ }
+}
+
+resource "azurerm_managed_disk" "managed_disk_bad_1" {
+ name = "acctestmd"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.group.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ tags = {
+ environment = "staging"
+ }
+}
+
+resource "azurerm_managed_disk" "managed_disk_bad_2" {
+ name = "acctestmd"
+ location = "West US 2"
+ resource_group_name = azurerm_resource_group.group.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+ encryption_settings {
+ enabled = false
+ }
+ tags = {
+ environment = "staging"
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_good_1" {
+ name = "$vm"
+ location = "location"
+ resource_group_name = azurerm_resource_group.group.name
+ network_interface_ids = ["id"]
+ vm_size = "Standard_DS1_v2"
+ storage_image_reference {
+ publisher = "Canonical"
+ offer = "UbuntuServer"
+ sku = "16.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ managed_disk_id = azurerm_managed_disk.managed_disk_good_1.id
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_good_2" {
+ name = "$vm"
+ location = "location"
+ resource_group_name = azurerm_resource_group.group.name
+ network_interface_ids = ["id"]
+ vm_size = "Standard_DS1_v2"
+ storage_image_reference {
+ publisher = "Canonical"
+ offer = "UbuntuServer"
+ sku = "16.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ managed_disk_id = azurerm_managed_disk.managed_disk_good_2.id
+ }
+}
+
+
+resource "azurerm_virtual_machine" "virtual_machine_good_3" {
+ name = "$vm"
+ location = "location"
+ resource_group_name = azurerm_resource_group.group.name
+ network_interface_ids = ["id"]
+ vm_size = "Standard_DS1_v2"
+ storage_image_reference {
+ publisher = "Canonical"
+ offer = "UbuntuServer"
+ sku = "16.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ managed_disk_type = "managed"
+ }
+}
+
+
+resource "azurerm_virtual_machine" "virtual_machine_bad_1" {
+ name = "$vm"
+ location = "location"
+ resource_group_name = azurerm_resource_group.group.name
+ network_interface_ids = ["id"]
+ vm_size = "Standard_DS1_v2"
+ storage_image_reference {
+ publisher = "Canonical"
+ offer = "UbuntuServer"
+ sku = "16.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ managed_disk_type = azurerm_managed_disk.managed_disk_bad_1.id
+ }
+}
+
+resource "azurerm_virtual_machine" "virtual_machine_bad_2" {
+ name = "$vm"
+ location = "location"
+ resource_group_name = azurerm_resource_group.group.name
+ network_interface_ids = ["id"]
+ vm_size = "Standard_DS1_v2"
+ storage_image_reference {
+ publisher = "Canonical"
+ offer = "UbuntuServer"
+ sku = "16.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ name = "myosdisk1"
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ managed_disk_type = azurerm_managed_disk.managed_disk_bad_2.id
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/expected.yaml b/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/expected.yaml
new file mode 100644
index 0000000000..3c503b3e16
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/expected.yaml
@@ -0,0 +1,4 @@
+fail:
+ - "aws_cloudtrail.aws_cloudtrail_not_ok"
+pass:
+ - "aws_cloudtrail.aws_cloudtrail_ok"
diff --git a/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/main.tf b/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/main.tf
new file mode 100644
index 0000000000..e405da1560
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/CloudtrailHasCloudwatch/main.tf
@@ -0,0 +1,12 @@
+resource "aws_cloudwatch_log_group" "example" {
+ name = "Example"
+}
+
+resource "aws_cloudtrail" "aws_cloudtrail_ok" {
+ name = "tf-trail-foobar"
+ cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.example.arn}:*"
+}
+
+resource "aws_cloudtrail" "aws_cloudtrail_not_ok" {
+ name = "tf-trail-foobar"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/expected.yaml b/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/expected.yaml
new file mode 100644
index 0000000000..34f5e6c615
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_kusto_cluster.cluster_ok"
+fail:
+ - "azurerm_kusto_cluster.cluster_ok_not_ok"
diff --git a/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/main.tf b/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/main.tf
new file mode 100644
index 0000000000..f2a30682b7
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/DataExplorerEncryptionUsesCustomKey/main.tf
@@ -0,0 +1,37 @@
+resource "azurerm_kusto_cluster" "cluster_ok" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_kusto_cluster_customer_managed_key" "example" {
+ cluster_id = azurerm_kusto_cluster.cluster_ok.id
+ key_vault_id = azurerm_key_vault.example.id
+ key_name = azurerm_key_vault_key.example.name
+ key_version = azurerm_key_vault_key.example.version
+}
+
+
+resource "azurerm_kusto_cluster" "cluster_ok_not_ok" {
+ name = "kustocluster"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+
+ sku {
+ name = "Standard_D13_v2"
+ capacity = 2
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/expected.yaml b/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/expected.yaml
new file mode 100644
index 0000000000..6eb034a956
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "google_sql_database_instance.db_instance_good_1"
+ - "google_sql_database_instance.db_instance_good_2"
+fail:
+ - "google_sql_database_instance.db_instance_bad"
diff --git a/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/main.tf b/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/main.tf
new file mode 100644
index 0000000000..c4bcaa86a0
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/DisableAccessToSqlDBInstanceForRootUsersWithoutPassword/main.tf
@@ -0,0 +1,42 @@
+resource "random_id" "db_name_suffix" {
+ byte_length = 4
+}
+
+resource "google_sql_database_instance" "db_instance_good_1" {
+ name = "master-instance-${random_id.db_name_suffix.hex}"
+
+ settings {
+ tier = "db-f1-micro"
+ }
+}
+
+resource "google_sql_database_instance" "db_instance_good_2" {
+ name = "master-instance-${random_id.db_name_suffix.hex}"
+
+ settings {
+ tier = "db-f1-micro"
+ }
+}
+
+
+resource "google_sql_database_instance" "db_instance_bad" {
+ name = "master-instance-${random_id.db_name_suffix.hex}"
+
+ settings {
+ tier = "db-f1-micro"
+ }
+}
+
+
+resource "google_sql_user" "root_good" {
+ name = "root"
+ instance = google_sql_database_instance.db_instance_good_1.name
+ host = "me.com"
+ password = "1234"
+}
+
+resource "google_sql_user" "root_bad" {
+ name = "root@#"
+ instance = google_sql_database_instance.db_instance_bad.name
+ host = "me.com"
+}
diff --git a/tests/terraform/graph/checks/resources/EBSAddedBackup/expected.yaml b/tests/terraform/graph/checks/resources/EBSAddedBackup/expected.yaml
new file mode 100644
index 0000000000..08c31b7b4d
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EBSAddedBackup/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_ebs_volume.ebs_good"
+fail:
+ - "aws_ebs_volume.ebs_bad"
diff --git a/tests/terraform/graph/checks/resources/EBSAddedBackup/main.tf b/tests/terraform/graph/checks/resources/EBSAddedBackup/main.tf
new file mode 100644
index 0000000000..59faeddd8f
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EBSAddedBackup/main.tf
@@ -0,0 +1,36 @@
+resource "aws_ebs_volume" "ebs_good" {
+ availability_zone = "us-west-2a"
+ size = 40
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_ebs_volume" "ebs_bad" {
+ availability_zone = "us-west-2a"
+ size = 40
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_backup_selection" "backup_good" {
+ iam_role_arn = "arn"
+ name = "tf_example_backup_selection"
+ plan_id = "123456"
+
+ resources = [
+ aws_ebs_volume.ebs_good.arn
+ ]
+}
+
+resource "aws_backup_selection" "backup_bad" {
+ iam_role_arn = "arn"
+ name = "tf_example_backup_selection"
+ plan_id = "123456"
+
+ resources = [
+ ]
+}
diff --git a/tests/terraform/graph/checks/resources/EFSAddedBackup/expected.yaml b/tests/terraform/graph/checks/resources/EFSAddedBackup/expected.yaml
new file mode 100644
index 0000000000..2c3175f1fe
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EFSAddedBackup/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_efs_file_system.ok_efs"
+fail:
+ - "aws_efs_file_system.not_ok_efs"
diff --git a/tests/terraform/graph/checks/resources/EFSAddedBackup/main.tf b/tests/terraform/graph/checks/resources/EFSAddedBackup/main.tf
new file mode 100644
index 0000000000..e986e4a355
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EFSAddedBackup/main.tf
@@ -0,0 +1,63 @@
+resource "aws_backup_plan" "example" {
+ name = "tf_example_backup_plan"
+
+ rule {
+ rule_name = "tf_example_backup_rule"
+ target_vault_name = aws_backup_vault.test.name
+ schedule = "cron(0 12 * * ? *)"
+ }
+
+ advanced_backup_setting {
+ backup_options = {
+ WindowsVSS = "enabled"
+ }
+ resource_type = "EC2"
+ }
+}
+
+resource "aws_backup_selection" "ok_backup" {
+ iam_role_arn = aws_iam_role.example.arn
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_db_instance.example.arn,
+ aws_ebs_volume.example.arn,
+ aws_efs_file_system.ok_efs.arn,
+ ]
+}
+
+resource "aws_efs_file_system" "ok_efs" {
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
+
+resource "aws_backup_selection" "not_ok_backup" {
+ iam_role_arn = aws_iam_role.example.arn
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_db_instance.example.arn,
+ aws_ebs_volume.example.arn
+ ]
+}
+
+resource "aws_efs_file_system" "ok_efs" {
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
+
+resource "aws_efs_file_system" "not_ok_efs" {
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/expected.yaml b/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/expected.yaml
new file mode 100644
index 0000000000..e29c1c971f
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_efs_file_system.ok_efs"
+skip:
+ - "aws_efs_file_system.not_ok_efs"
diff --git a/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/main.tf b/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/main.tf
new file mode 100644
index 0000000000..9bf8ca241a
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EFSAddedBackupSuppress/main.tf
@@ -0,0 +1,64 @@
+resource "aws_backup_plan" "example" {
+ name = "tf_example_backup_plan"
+
+ rule {
+ rule_name = "tf_example_backup_rule"
+ target_vault_name = aws_backup_vault.test.name
+ schedule = "cron(0 12 * * ? *)"
+ }
+
+ advanced_backup_setting {
+ backup_options = {
+ WindowsVSS = "enabled"
+ }
+ resource_type = "EC2"
+ }
+}
+
+resource "aws_backup_selection" "ok_backup" {
+ iam_role_arn = aws_iam_role.example.arn
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_db_instance.example.arn,
+ aws_ebs_volume.example.arn,
+ aws_efs_file_system.ok_efs.arn,
+ ]
+}
+
+resource "aws_efs_file_system" "ok_efs" {
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
+
+resource "aws_backup_selection" "not_ok_backup" {
+ iam_role_arn = aws_iam_role.example.arn
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_db_instance.example.arn,
+ aws_ebs_volume.example.arn
+ ]
+}
+
+resource "aws_efs_file_system" "ok_efs" {
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
+
+resource "aws_efs_file_system" "not_ok_efs" {
+ # checkov:skip=CKV2_AWS_18:Skip test
+ creation_token = "my-product"
+
+ tags = {
+ Name = "MyProduct"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/expected.yaml b/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/expected.yaml
new file mode 100644
index 0000000000..17e9357ca5
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/expected.yaml
@@ -0,0 +1,8 @@
+pass:
+ - "aws_eip.ok_eip"
+ - "aws_eip.ok_eip_assoc"
+ - "aws_eip.ok_eip_nat"
+ - "aws_eip.ok_eip_module"
+ - "aws_eip.ok_eip_data"
+fail:
+ - "aws_eip.not_ok_eip"
diff --git a/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/main.tf b/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/main.tf
new file mode 100644
index 0000000000..68dc48f6e8
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EIPAllocatedToVPCAttachedEC2/main.tf
@@ -0,0 +1,63 @@
+resource "aws_eip" "ok_eip" {
+ instance = aws_instance.ec2.id
+ vpc = true
+}
+
+resource "aws_instance" "ec2" {
+ ami = "ami-21f78e11"
+ availability_zone = "us-west-2a"
+ instance_type = "t2.micro"
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_eip" "not_ok_eip" {
+ vpc = true
+ network_interface = aws_network_interface.multi-ip.id
+ associate_with_private_ip = "10.0.0.10"
+}
+
+# via aws_eip_association
+
+resource "aws_eip_association" "eip_assoc" {
+ instance_id = aws_instance.ec2_assoc.id
+ allocation_id = aws_eip.ok_eip_assoc.id
+}
+
+resource "aws_instance" "ec2_assoc" {
+ ami = "ami-21f78e11"
+ availability_zone = "us-west-2a"
+ instance_type = "t2.micro"
+
+ tags = {
+ Name = "Assoc"
+ }
+}
+
+resource "aws_eip" "ok_eip_assoc" {
+ vpc = true
+}
+
+# via aws_nat_gateway
+
+resource "aws_eip" "ok_eip_nat" {
+ vpc = true
+}
+
+resource "aws_nat_gateway" "ok_eip_nat" {
+ allocation_id = aws_eip.ok_eip_nat.id
+ subnet_id = "aws_subnet.public.id"
+}
+
+resource "aws_eip" "ok_eip_module" {
+ count = 1
+ instance = module.example[count.index].instance_id
+ vpc = true
+}
+
+resource "aws_eip" "ok_eip_data" {
+ instance = data.aws_instance.id
+ vpc = true
+}
diff --git a/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/expected.yaml b/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/expected.yaml
new file mode 100644
index 0000000000..6712143862
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/expected.yaml
@@ -0,0 +1,8 @@
+pass:
+ - "aws_ebs_volume.ok_ebs1"
+ - "aws_ebs_volume.ok_ebs2"
+
+
+fail:
+ - "aws_ebs_volume.not_ok_ebs1"
+ - "aws_ebs_volume.not_ok_ebs2"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/main.tf b/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/main.tf
new file mode 100644
index 0000000000..e03e5f0ea6
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/EncryptedEBSVolumeOnlyConnectedToEC2s/main.tf
@@ -0,0 +1,52 @@
+resource "aws_instance" "web" {
+ ami = "ami-21f78e11"
+ availability_zone = "us-west-2a"
+ instance_type = "t2.micro"
+
+ tags = {
+ Name = "HelloWorld"
+ }
+}
+
+resource "aws_volume_attachment" "not_ok_attachment1" {
+ device_name = "/dev/sdh"
+ volume_id = aws_ebs_volume.not_ok_ebs1.id
+ instance_id = aws_instance.web.id
+}
+
+resource "aws_volume_attachment" "not_ok_attachment2" {
+ device_name = "/dev/sdh2"
+ volume_id = aws_ebs_volume.not_ok_ebs2.id
+ instance_id = aws_instance.web.id
+}
+
+resource "aws_volume_attachment" "ok_attachment1" {
+ device_name = "/dev/sdh3"
+ volume_id = aws_ebs_volume.ok_ebs2.id
+ instance_id = aws_instance.web.id
+}
+
+resource "aws_ebs_volume" "not_ok_ebs1" {
+ availability_zone = ""
+}
+
+resource "aws_ebs_volume" "not_ok_ebs2" {
+ availability_zone = ""
+ encrypted = false
+}
+
+resource "aws_ebs_volume" "ok_ebs1" {
+ availability_zone = ""
+}
+
+resource "aws_ebs_volume" "ok_ebs2" {
+ availability_zone = ""
+ encrypted = true
+}
+
+
+resource "aws_volume_attachment" "ebs_at1" {
+ device_name = "/dev/sdh"
+ volume_id = aws_ebs_volume.not_ok_ebs1.id
+ instance_id = aws_instance.web.id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/expected.yaml b/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/expected.yaml
new file mode 100644
index 0000000000..1d7ebbfe01
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/expected.yaml
@@ -0,0 +1,6 @@
+pass:
+ - "google_project.project_good"
+fail:
+ - "google_project.project_bad_1"
+ - "google_project.project_bad_2"
+ - "google_project.project_bad_3"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/main.tf b/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/main.tf
new file mode 100644
index 0000000000..caf7e7ac73
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPAuditLogsConfiguredForAllServicesAndUsers/main.tf
@@ -0,0 +1,55 @@
+resource "google_project" "project_good" {
+ name = "good"
+ project_id = "123456"
+}
+
+resource "google_project" "project_bad_1" {
+ name = "bad1"
+ project_id = "123456"
+}
+
+resource "google_project" "project_bad_2" {
+ name = "bad2"
+ project_id = "123456"
+}
+
+resource "google_project" "project_bad_3" {
+ name = "bad3"
+ project_id = "123456"
+}
+
+resource "google_project_iam_audit_config" "project_good_audit" {
+ project = google_project.project_good.id
+ service = "allServices"
+ audit_log_config {
+ log_type = "ADMIN_READ"
+ }
+ audit_log_config {
+ log_type = "DATA_READ"
+ }
+}
+
+resource "google_project_iam_audit_config" "project_bad_audit_1" {
+ project = google_project.project_bad_1.id
+ service = "allServices"
+ audit_log_config {
+ log_type = "ADMIN_READ"
+ }
+ audit_log_config {
+ log_type = "DATA_READ"
+ exempted_members = [
+ "user:joebloggs@hashicorp.com",
+ ]
+ }
+}
+
+resource "google_project_iam_audit_config" "project_bad_audit_2" {
+ project = google_project.project_bad_2.id
+ service = "someService"
+ audit_log_config {
+ log_type = "ADMIN_READ"
+ }
+ audit_log_config {
+ log_type = "DATA_READ"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/expected.yaml b/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/expected.yaml
new file mode 100644
index 0000000000..8ea6836ddc
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/expected.yaml
@@ -0,0 +1,8 @@
+pass:
+ - "google_kms_crypto_key.key_good_1"
+ - "google_kms_crypto_key.key_good_2"
+fail:
+ - "google_kms_crypto_key.key_bad_1"
+ - "google_kms_crypto_key.key_bad_2"
+ - "google_kms_crypto_key.key_bad_3"
+ - "google_kms_crypto_key.key_bad_4"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/main.tf b/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/main.tf
new file mode 100644
index 0000000000..1293733412
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPKMSCryptoKeysAreNotPubliclyAccessible/main.tf
@@ -0,0 +1,121 @@
+resource "google_kms_key_ring" "keyring" {
+ name = "keyring-example"
+ location = "global"
+}
+
+
+resource "google_kms_crypto_key" "key_good_1" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key" "key_bad_1" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key" "key_bad_2" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key_iam_member" "crypto_key_good" {
+ crypto_key_id = google_kms_crypto_key.key_good_1.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+ member = "user:jane@example.com"
+}
+
+resource "google_kms_crypto_key_iam_member" "crypto_key_bad_1" {
+ crypto_key_id = google_kms_crypto_key.key_bad_1.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+ member = "allUsers"
+}
+
+resource "google_kms_crypto_key_iam_member" "crypto_key_bad_2" {
+ crypto_key_id = google_kms_crypto_key.key_bad_2.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+ member = "allAuthenticatedUsers"
+}
+
+resource "google_kms_crypto_key" "key_bad_2" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key" "key_good_2" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key" "key_bad_3" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+resource "google_kms_crypto_key" "key_bad_4" {
+ name = "crypto-key-example"
+ key_ring = google_kms_key_ring.keyring.id
+ rotation_period = "100000s"
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
+
+
+resource "google_kms_crypto_key_iam_binding" "crypto_key" {
+ crypto_key_id = google_kms_crypto_key.key_good_2.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+
+ members = [
+ "user:jane@example.com",
+ ]
+}
+
+resource "google_kms_crypto_key_iam_binding" "crypto_key" {
+ crypto_key_id = google_kms_crypto_key.key_bad_3.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+
+ members = [
+ "allUsers",
+ ]
+}
+
+resource "google_kms_crypto_key_iam_binding" "crypto_key" {
+ crypto_key_id = google_kms_crypto_key.key_bad_4.id
+ role = "roles/cloudkms.cryptoKeyEncrypter"
+
+ members = [
+ "allAuthenticatedUsers",
+ ]
+}
diff --git a/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/expected.yaml b/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/expected.yaml
new file mode 100644
index 0000000000..828cb65647
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/expected.yaml
@@ -0,0 +1,11 @@
+pass:
+ - "google_logging_organization_sink.org_sink_good_1"
+ - "google_logging_folder_sink.folder_sink_good_1"
+ - "google_logging_project_sink.project_sink_good_1"
+fail:
+ - "google_logging_organization_sink.org_sink_bad_1"
+ - "google_logging_organization_sink.org_sink_bad_2"
+ - "google_logging_folder_sink.folder_sink_bad_1"
+ - "google_logging_folder_sink.folder_sink_bad_2"
+ - "google_logging_project_sink.project_sink_bad_1"
+ - "google_logging_project_sink.project_sink_bad_2"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/main.tf b/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/main.tf
new file mode 100644
index 0000000000..ffead8b7de
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPLogBucketsConfiguredUsingLock/main.tf
@@ -0,0 +1,96 @@
+resource "google_logging_organization_sink" "org_sink_good_1" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ org_id = "123456789"
+ destination = google_storage_bucket.log_bucket_good.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+}
+
+resource "google_logging_folder_sink" "folder_sink_good_1" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ folder = "folder-name"
+ destination = google_storage_bucket.log_bucket_good.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+}
+
+resource "google_logging_folder_sink" "folder_sink_bad_1" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ folder = "folder-name"
+ destination = google_storage_bucket.log_bucket_bad_1.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+}
+
+resource "google_logging_folder_sink" "folder_sink_bad_2" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ folder = "folder-name"
+ destination = google_storage_bucket.log_bucket_bad_2.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+}
+
+resource "google_logging_project_sink" "project_sink_good_1" {
+ name = "my-pubsub-instance-sink"
+ destination = google_storage_bucket.log_bucket_good.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+ unique_writer_identity = true
+}
+
+resource "google_logging_project_sink" "project_sink_bad_1" {
+ name = "my-pubsub-instance-sink"
+ destination = google_storage_bucket.log_bucket_bad_1.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+ unique_writer_identity = true
+}
+
+resource "google_logging_project_sink" "project_sink_bad_2" {
+ name = "my-pubsub-instance-sink"
+ destination = google_storage_bucket.log_bucket_bad_2.name
+ filter = "resource.type = gce_instance AND severity >= WARNING"
+ unique_writer_identity = true
+}
+
+
+resource "google_logging_organization_sink" "org_sink_bad_1" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ org_id = "123456789"
+
+ destination = google_storage_bucket.log_bucket_bad_1.name
+}
+
+resource "google_logging_organization_sink" "org_sink_bad_2" {
+ name = "my-sink"
+ description = "some explaination on what this is"
+ org_id = "123456789"
+
+ destination = google_storage_bucket.log_bucket_bad_2.name
+}
+
+resource "google_storage_bucket" "log_bucket_good" {
+ name = "organization-logging-bucket"
+
+ retention_policy {
+ retention_period = 1000
+ is_locked = true
+ }
+}
+
+
+resource "google_storage_bucket" "log_bucket_bad_1" {
+ name = "organization-logging-bucket"
+
+ retention_policy {
+ retention_period = 1000
+ is_locked = false
+ }
+}
+
+resource "google_storage_bucket" "log_bucket_bad_2" {
+ name = "organization-logging-bucket"
+
+ retention_policy {
+ retention_period = 1000
+ }
+}
diff --git a/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/expected.yaml b/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/expected.yaml
new file mode 100644
index 0000000000..d75072266e
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "google_project.project_good_1"
+ - "google_project.project_good_2"
+fail:
+ - "google_project.project_bad_1"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/main.tf b/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/main.tf
new file mode 100644
index 0000000000..68640a74c3
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GCPProjectHasNoLegacyNetworks/main.tf
@@ -0,0 +1,34 @@
+resource "google_project" "project_good_1" {
+ name = "My Project"
+ project_id = "good"
+ org_id = "1234567"
+}
+
+resource "google_project" "project_good_2" {
+ name = "My Project"
+ project_id = "good"
+ org_id = "1234567"
+}
+
+resource "google_project" "project_bad_1" {
+ name = "My Project"
+ project_id = "bad"
+ org_id = "1234567"
+}
+
+resource "google_compute_network" "vpc_network_network" {
+ name = "vpc-legacy"
+ auto_create_subnetworks = true
+ project = google_project.project_bad_1.id
+}
+
+resource "google_compute_network" "vpc_network_1" {
+ name = "vpc-legacy"
+ project = google_project.project_good_1.id
+}
+
+resource "google_compute_network" "vpc_network_2" {
+ name = "vpc-legacy"
+ project = google_project.project_good_1.id
+ auto_create_subnetworks = false
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/expected.yaml b/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/expected.yaml
new file mode 100644
index 0000000000..2cd703ee1f
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "google_project_default_service_accounts.ok"
+fail:
+ - "google_project_default_service_accounts.not_ok"
diff --git a/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/main.tf b/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/main.tf
new file mode 100644
index 0000000000..d90207072e
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GKEClustersAreNotUsingDefaultServiceAccount/main.tf
@@ -0,0 +1,65 @@
+resource "google_service_account" "default" {
+ account_id = "service-account-id"
+ display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary_A_ok" {
+ name = "my-gke-cluster"
+ location = "us-central1"
+
+ # We can't create a cluster with no node pool defined, but we want to only use
+ # separately managed node pools. So we create the smallest possible default
+ # node pool and immediately delete it.
+ remove_default_node_pool = true
+ initial_node_count = 1
+}
+
+resource "google_container_node_pool" "primary_preemptible_nodes" {
+ name = "my-node-pool"
+ location = "us-central1"
+ cluster = google_container_cluster.primary_A.name
+ node_count = 1
+
+ node_config {
+ preemptible = true
+ machine_type = "e2-medium"
+
+ # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+ service_account = google_project_default_service_accounts.not_ok.id
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/cloud-platform"
+ ]
+ }
+}
+
+resource "google_container_cluster" "primary_B_ok" {
+ name = "marcellus-wallace"
+ location = "us-central1-a"
+ initial_node_count = 3
+ node_config {
+ # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+ service_account = google_project_default_service_accounts.not_ok.id
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/cloud-platform"
+ ]
+ labels = {
+ foo = "bar"
+ }
+ tags = ["foo", "bar"]
+ }
+ timeouts {
+ create = "30m"
+ update = "40m"
+ }
+}
+
+resource "google_project_default_service_accounts" "not_ok" {
+ project = "my-project-id"
+ action = "DELETE"
+ id="1234"
+}
+
+resource "google_project_default_service_accounts" "ok" {
+ project = "my-project-id"
+ action = "DELETE"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/expected.yaml b/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/expected.yaml
new file mode 100644
index 0000000000..3d07daf2da
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/expected.yaml
@@ -0,0 +1,5 @@
+pass:
+ - "aws_guardduty_detector.ok"
+fail:
+ - "aws_guardduty_detector.not_ok"
+ - "aws_guardduty_detector.not_ok_false"
diff --git a/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/main.tf b/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/main.tf
new file mode 100644
index 0000000000..6611ea183e
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/GuardDutyIsEnabled/main.tf
@@ -0,0 +1,21 @@
+resource "aws_guardduty_detector" "ok" {
+ enable = true
+}
+
+resource "aws_guardduty_detector" "not_ok" {
+ enable = true
+}
+
+resource "aws_guardduty_organization_configuration" "example" {
+ auto_enable = true
+ detector_id = aws_guardduty_detector.ok.id
+}
+
+resource "aws_guardduty_detector" "not_ok_false" {
+ enable = true
+}
+
+resource "aws_guardduty_organization_configuration" "example" {
+ auto_enable = false
+ detector_id = aws_guardduty_detector.not_ok_false.id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/expected.yaml b/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/expected.yaml
new file mode 100644
index 0000000000..58a0c92173
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_iam_group_membership.ok_group"
+fail:
+ - "aws_iam_group_membership.bad_group"
diff --git a/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/main.tf b/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/main.tf
new file mode 100644
index 0000000000..24258f9adf
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMGroupHasAtLeastOneUser/main.tf
@@ -0,0 +1,35 @@
+resource "aws_iam_group_membership" "ok_group" {
+ name = "tf-testing-group-membership"
+
+ users = [
+ aws_iam_user.user_one.name,
+ aws_iam_user.user_two.name,
+ ]
+
+ group = aws_iam_group.group.name
+}
+
+resource "aws_iam_group" "group" {
+ name = "test-group"
+}
+
+resource "aws_iam_user" "user_one" {
+ name = "test-user"
+}
+
+resource "aws_iam_user" "user_two" {
+ name = "test-user-two"
+}
+
+
+resource "aws_iam_group_membership" "bad_group" {
+ name = "tf-testing-group-membership"
+
+
+ group = aws_iam_group.bad_group.name
+}
+
+
+resource "aws_iam_group" "bad_group" {
+ name = "test-group"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/expected.yaml b/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/expected.yaml
new file mode 100644
index 0000000000..47f6011248
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_iam_user.pass"
+fail:
+ - "aws_iam_user.fail"
diff --git a/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/main.tf b/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/main.tf
new file mode 100644
index 0000000000..cb1f73d533
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMUserHasNoConsoleAccess/main.tf
@@ -0,0 +1,16 @@
+# pass
+
+resource "aws_iam_user" "pass" {
+ name = "tech-user"
+}
+
+# fail
+
+resource "aws_iam_user" "fail" {
+ name = "human-user"
+}
+
+resource "aws_iam_user_login_profile" "fail" {
+ user = aws_iam_user.fail.name
+ pgp_key = "keybase:human-user"
+}
diff --git a/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/expected.yaml b/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/expected.yaml
new file mode 100644
index 0000000000..58a0c92173
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_iam_group_membership.ok_group"
+fail:
+ - "aws_iam_group_membership.bad_group"
diff --git a/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/main.tf b/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/main.tf
new file mode 100644
index 0000000000..7c1005d740
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/IAMUsersAreMembersAtLeastOneGroup/main.tf
@@ -0,0 +1,32 @@
+resource "aws_iam_group_membership" "ok_group" {
+ name = "tf-testing-group-membership"
+
+ users = [
+ aws_iam_user.user_good.name,
+ ]
+
+ group = aws_iam_group.group.name
+}
+
+resource "aws_iam_group" "group" {
+ name = "test-group"
+}
+
+resource "aws_iam_user" "user_good" {
+ name = "test-user"
+}
+
+resource "aws_iam_user" "user_bad" {
+ name = "test-user-two"
+}
+
+
+resource "aws_iam_group_membership" "bad_group" {
+ name = "tf-testing-group-membership"
+ users = []
+ group = aws_iam_group.bad_group.name
+}
+
+resource "aws_iam_group" "bad_group" {
+ name = "test-group"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/expected.yaml b/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/expected.yaml
new file mode 100644
index 0000000000..8ddc348f22
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_mysql_server.ok"
+fail:
+ - "azurerm_mysql_server.not_ok"
diff --git a/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/main.tf b/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/main.tf
new file mode 100644
index 0000000000..93533b5e90
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/MSQLenablesCustomerManagedKey/main.tf
@@ -0,0 +1,80 @@
+resource "azurerm_resource_group" "ok" {
+ name = "ok-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_key_vault" "ok" {
+ name = "okkv"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ sku_name = "premium"
+ purge_protection_enabled = true
+}
+
+resource "azurerm_key_vault_access_policy" "server" {
+ key_vault_id = azurerm_key_vault.ok.id
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = azurerm_mysql_server.ok.identity.0.principal_id
+ key_permissions = ["get", "unwrapkey", "wrapkey"]
+ secret_permissions = ["get"]
+}
+
+resource "azurerm_key_vault_access_policy" "client" {
+ key_vault_id = azurerm_key_vault.ok.id
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+ key_permissions = ["get", "create", "delete", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"]
+ secret_permissions = ["get"]
+}
+
+resource "azurerm_key_vault_key" "ok" {
+ name = "tfex-key"
+ key_vault_id = azurerm_key_vault.ok.id
+ key_type = "RSA"
+ key_size = 2048
+ key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]
+ depends_on = [
+ azurerm_key_vault_access_policy.client,
+ azurerm_key_vault_access_policy.server,
+ ]
+}
+
+resource "azurerm_mysql_server" "ok" {
+ name = "ok-mysql-server"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ sku_name = "GP_Gen5_2"
+ administrator_login = "acctestun"
+ administrator_login_password = "H@Sh1CoR3!"
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_1"
+ storage_mb = 51200
+ version = "5.6"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_mysql_server_key" "ok" {
+ server_id = azurerm_mysql_server.ok.id
+ key_vault_key_id = azurerm_key_vault_key.ok.id
+}
+
+resource "azurerm_mysql_server" "not_ok" {
+ name = "ok-mysql-server"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ sku_name = "GP_Gen5_2"
+ administrator_login = "acctestun"
+ administrator_login_password = "H@Sh1CoR3!"
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_1"
+ storage_mb = 51200
+ version = "5.6"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/expected.yaml b/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/expected.yaml
new file mode 100644
index 0000000000..0c6455950b
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "azurerm_postgresql_server_key.ok"
+fail:
+ - "azurerm_postgresql_server_key.not_ok"
diff --git a/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/main.tf b/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/main.tf
new file mode 100644
index 0000000000..e942e787c7
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/PGSQLenablesCustomerManagedKey/main.tf
@@ -0,0 +1,80 @@
+resource "azurerm_resource_group" "ok" {
+ name = "ok-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_key_vault" "ok" {
+ name = "okkv"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ sku_name = "premium"
+ purge_protection_enabled = true
+}
+
+resource "azurerm_key_vault_access_policy" "server" {
+ key_vault_id = azurerm_key_vault.ok.id
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = azurerm_postgresql_server.ok.identity.0.principal_id
+ key_permissions = ["get", "unwrapkey", "wrapkey"]
+ secret_permissions = ["get"]
+}
+
+resource "azurerm_key_vault_access_policy" "client" {
+ key_vault_id = azurerm_key_vault.ok.id
+ tenant_id = data.azurerm_client_config.current.tenant_id
+ object_id = data.azurerm_client_config.current.object_id
+ key_permissions = ["get", "create", "delete", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"]
+ secret_permissions = ["get"]
+}
+
+resource "azurerm_key_vault_key" "ok" {
+ name = "tfex-key"
+ key_vault_id = azurerm_key_vault.ok.id
+ key_type = "RSA"
+ key_size = 2048
+ key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]
+ depends_on = [
+ azurerm_key_vault_access_policy.client,
+ azurerm_key_vault_access_policy.server,
+ ]
+}
+
+resource "azurerm_postgresql_server" "ok" {
+ name = "ok-pg-server"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ sku_name = "GP_Gen5_2"
+ administrator_login = "acctestun"
+ administrator_login_password = "H@Sh1CoR3!"
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_1"
+ storage_mb = 51200
+ version = "5.6"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
+
+resource "azurerm_postgresql_server_key" "ok" {
+ server_id = azurerm_postgresql_server.ok.id
+ key_vault_key_id = azurerm_key_vault_key.ok.id
+}
+
+resource "azurerm_postgresql_server_key" "not_ok" {
+ name = "ok-pg-server"
+ location = azurerm_resource_group.ok.location
+ resource_group_name = azurerm_resource_group.ok.name
+ sku_name = "GP_Gen5_2"
+ administrator_login = "acctestun"
+ administrator_login_password = "H@Sh1CoR3!"
+ ssl_enforcement_enabled = true
+ ssl_minimal_tls_version_enforced = "TLS1_1"
+ storage_mb = 51200
+ version = "5.6"
+
+ identity {
+ type = "SystemAssigned"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/expected.yaml b/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/expected.yaml
new file mode 100644
index 0000000000..ea62381221
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/expected.yaml
@@ -0,0 +1,8 @@
+pass:
+ - "aws_db_instance.pass"
+
+
+fail:
+ - "aws_db_instance.fail"
+ - "aws_db_instance.fail3"
+ - "aws_db_instance.fail4"
diff --git a/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/main.tf b/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/main.tf
new file mode 100644
index 0000000000..648108c7bf
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/PostgresRDSHasQueryLoggingEnabled/main.tf
@@ -0,0 +1,128 @@
+resource "aws_db_instance" "pass" {
+ engine = "postgres"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ parameter_group_name = aws_rds_cluster_parameter_group.pass.id
+}
+
+resource "aws_db_instance" "fail3" {
+ engine = "postgres"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ parameter_group_name = aws_rds_cluster_parameter_group.fail.id
+}
+
+resource "aws_db_instance" "fail4" {
+ engine = "postgres"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ parameter_group_name = aws_rds_cluster_parameter_group.fail2.id
+}
+
+//no parameter_group_name set
+resource "aws_db_instance" "fail" {
+ engine = "postgres"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+}
+
+//not postgres
+resource "aws_db_instance" "ignore" {
+ engine = "mysql"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+}
+
+// no postgres
+resource "aws_db_instance" "ignore2" {
+ allocated_storage = 10
+ engine = "mysql"
+ engine_version = "5.7"
+ instance_class = "db.t3.micro"
+ name = "mydb"
+ username = "foo"
+ password = "foobarbaz"
+ parameter_group_name = "default.mysql5.7"
+ skip_final_snapshot = true
+}
+
+//not correct params
+resource "aws_rds_cluster_parameter_group" "fail" {
+ name = "mysql-cluster-fail"
+ family = "mysql"
+ description = "RDS default cluster parameter group"
+
+ parameter {
+ name = "character_set_server"
+ value = "utf8"
+ }
+
+ parameter {
+ name = "character_set_client"
+ value = "utf8"
+ }
+}
+
+provider "aws" {
+ region="eu-west-2"
+}
+
+//will be correct params
+resource "aws_rds_cluster_parameter_group" "pass" {
+ name = "rds-cluster-pg-pass"
+ family = "aurora-postgresql11"
+ description = "RDS default cluster parameter group"
+
+ parameter {
+ name = "log_statement"
+ value = "all"
+ }
+
+ parameter {
+ name = "log_min_duration_statement"
+ value = "250ms"
+ }
+}
+
+resource "aws_rds_cluster_parameter_group" "fail2" {
+ name = "rds-cluster-pg-pass"
+ family = "aurora-postgresql11"
+ description = "RDS default cluster parameter group"
+
+ parameter {
+ name = "log_statement"
+ value = "all"
+ }
+}
+
+resource "aws_db_instance" "ignore3" {
+ identifier = "xxx-our-unique-id"
+ allocated_storage = 1000
+ storage_type = "gp2"
+ copy_tags_to_snapshot = true
+ engine = "sqlserver-se"
+ engine_version = "15.00.4043.16.v1"
+ license_model = "license-included"
+ instance_class = "db.r5.4xlarge"
+ name = ""
+ username = "sa"
+ password = var.password
+ port = 1433
+ publicly_accessible = false
+ security_group_names = []
+ vpc_security_group_ids = ["sg-xxxxx"]
+ db_subnet_group_name = "dbsubnet"
+ performance_insights_enabled = true
+ option_group_name = "sql-std-2019"
+ deletion_protection = true
+ max_allocated_storage = 1500
+ parameter_group_name = "sql-server-2019-std"
+ character_set_name = "SQL_Latin1_General_CP1_CS_AS"
+ # checkov:skip=CKV_AWS_157:Web db, acceptable risk until Resize
+ multi_az = false
+ backup_retention_period = 35
+ enabled_cloudwatch_logs_exports = ["agent","error"]
+ backup_window = "11:17-11:47"
+ maintenance_window = "sat:07:13-sat:08:43"
+ final_snapshot_identifier = "xxx-unique-name-final"
+}
diff --git a/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/expected.yaml b/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/expected.yaml
new file mode 100644
index 0000000000..9060c9e909
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_rds_cluster.rds_cluster_good"
+fail:
+ - "aws_rds_cluster.rds_cluster_bad"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/main.tf b/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/main.tf
new file mode 100644
index 0000000000..771ee4e40c
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/RDSClusterHasBackupPlan/main.tf
@@ -0,0 +1,49 @@
+resource "aws_rds_cluster" "rds_cluster_good" {
+ cluster_identifier = "aurora-cluster-demo"
+ engine = "aurora-mysql"
+ engine_version = "5.7.mysql_aurora.2.03.2"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+}
+
+resource "aws_rds_cluster" "rds_cluster_bad" {
+ cluster_identifier = "aurora-cluster-demo"
+ engine = "aurora-mysql"
+ engine_version = "5.7.mysql_aurora.2.03.2"
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "bar"
+}
+
+resource "aws_backup_plan" "example" {
+ name = "tf_example_backup_plan"
+
+ rule {
+ rule_name = "tf_example_backup_rule"
+ target_vault_name = "vault-name"
+ schedule = "cron(0 12 * * ? *)"
+ }
+}
+
+resource "aws_backup_selection" "backup_good" {
+ iam_role_arn = "arn:partition:service:region:account-id:resource-id"
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_rds_cluster.rds_cluster_good.arn
+ ]
+}
+
+resource "aws_backup_selection" "backup_bad" {
+ iam_role_arn = "arn:partition:service:region:account-id:resource-id"
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_rds_cluster.rds_cluster_good.arn
+ ]
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/expected.yaml b/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/expected.yaml
new file mode 100644
index 0000000000..366d81230b
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/expected.yaml
@@ -0,0 +1,4 @@
+pass:
+ - "aws_redshift_cluster.redshift_good"
+fail:
+ - "aws_redshift_cluster.redshift_bad"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/main.tf b/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/main.tf
new file mode 100644
index 0000000000..4d44c95587
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/RedshiftClusterHasBackupPlan/main.tf
@@ -0,0 +1,46 @@
+resource "aws_redshift_cluster" "redshift_good" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+}
+
+resource "aws_redshift_cluster" "redshift_bad" {
+ cluster_identifier = "tf-redshift-cluster"
+ database_name = "mydb"
+ master_username = "foo"
+ master_password = "Mustbe8characters"
+ node_type = "dc1.large"
+ cluster_type = "single-node"
+}
+
+resource "aws_backup_plan" "example" {
+ name = "tf_example_backup_plan"
+
+ rule {
+ rule_name = "tf_example_backup_rule"
+ target_vault_name = "vault-name"
+ schedule = "cron(0 12 * * ? *)"
+ }
+}
+
+resource "aws_backup_selection" "backup_good" {
+ iam_role_arn = "arn:partition:service:region:account-id:resource-id"
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ aws_redshift_cluster.redshift_good.arn
+ ]
+}
+
+resource "aws_backup_selection" "backup_bad" {
+ iam_role_arn = "arn:partition:service:region:account-id:resource-id"
+ name = "tf_example_backup_selection"
+ plan_id = aws_backup_plan.example.id
+
+ resources = [
+ ]
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/expected.yaml b/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/expected.yaml
new file mode 100644
index 0000000000..3dc3c167d1
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/expected.yaml
@@ -0,0 +1,13 @@
+pass:
+ - "aws_route53_record.pass"
+ - "aws_route53_record.pass2"
+ - "aws_route53_record.pass3"
+ - "aws_route53_record.pass4"
+ - "aws_route53_record.pass5"
+ - "aws_route53_record.legacy-tf"
+
+fail:
+ - "aws_route53_record.fail"
+ignore:
+ - "aws_route53_record.ignore"
+ - "aws_route53_record.ignore2"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/main.tf b/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/main.tf
new file mode 100644
index 0000000000..9d4e62f8a7
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/Route53ARecordAttachedResource/main.tf
@@ -0,0 +1,199 @@
+resource "aws_eip" "fixed" {
+ # checkov:skip=CKV2_AWS_19: ADD REASON
+}
+
+resource "aws_route53_record" "pass" {
+ zone_id = data.aws_route53_zone.primary.zone_id
+ name = "dns.freebeer.site"
+ type = "A"
+ ttl = "300"
+ records = [aws_eip.fixed.public_ip]
+}
+
+resource "aws_api_gateway_domain_name" "example" {
+ certificate_arn = aws_acm_certificate_validation.example.certificate_arn
+ domain_name = "api.example.com"
+}
+
+resource "aws_route53_record" "pass2" {
+ name = aws_api_gateway_domain_name.example.domain_name
+ type = "A"
+ zone_id = aws_route53_zone.example.id
+
+ alias {
+ evaluate_target_health = true
+ name = aws_api_gateway_domain_name.example.cloudfront_domain_name
+ zone_id = aws_api_gateway_domain_name.example.cloudfront_zone_id
+ }
+}
+
+
+
+resource "aws_route53_record" "fail" {
+ zone_id = data.aws_route53_zone.primary.zone_id
+ name = "dns.freebeer.site"
+ type = "A"
+ ttl = "300"
+ records = ["1.1.1.1"]
+}
+
+resource "aws_route53_record" "ignore" {
+ zone_id = data.aws_route53_zone.parent.id
+ name = "Some name abcd"
+ type = "CNAME"
+ ttl = 60
+ records = [module.controller.loadbalancer_dns_name]
+}
+
+resource "aws_route53_record" "ignore2" {
+ # it is possible to have a plan with a route53 record that has no type. I am not sure how, but this is a test
+ # of that case.
+ zone_id = data.aws_route53_zone.primary.zone_id
+ name = "dns.freebeer.site"
+ ttl = "300"
+ records = ["1.1.1.1"]
+}
+
+resource "aws_route53_record" "pass3" {
+ zone_id = var.zone_id
+ name = "test.example.com"
+ type = "A"
+ alias {
+ name = module.alb.lb_dns_name
+ zone_id = module.alb.lb_zone_id
+ evaluate_target_health = true
+ }
+}
+
+resource "aws_route53_record" "pass4" {
+ zone_id = data.aws_route53_zone.example.zone_id
+ name = "example"
+ type = "A"
+
+ alias {
+ name = data.aws_lb.example.dns_name
+ zone_id = data.aws_lb.example.zone_id
+ evaluate_target_health = true
+ }
+}
+
+resource "aws_route53_record" "pass5" {
+ zone_id = data.aws_route53_zone.selected.zone_id
+ name = var.fqdn
+ type = "A"
+ alias {
+ evaluate_target_health = false
+ name = aws_cloudfront_distribution.website.domain_name
+ zone_id = aws_cloudfront_distribution.website.hosted_zone_id
+ }
+}
+
+resource "aws_cloudfront_distribution" "website" {
+ provider = aws.useastone
+ origin {
+ domain_name = aws_s3_bucket.website.bucket_regional_domain_name
+ origin_id = "${aws_s3_bucket.website.id}-origin"
+ s3_origin_config {
+ origin_access_identity = aws_cloudfront_origin_access_identity.website.cloudfront_access_identity_path
+ }
+ }
+ web_acl_id = var.web_acl_id
+ enabled = true
+ is_ipv6_enabled = true
+ default_root_object = "index.html"
+ custom_error_response {
+ error_caching_min_ttl = 300
+ error_code = 404
+ response_code = 200
+ response_page_path = "/error.html"
+ }
+ aliases = [
+ var.fqdn
+ ]
+ logging_config {
+ bucket = aws_s3_bucket.logging.bucket_domain_name
+ include_cookies = false
+ prefix = "cloudfront/"
+ }
+ default_cache_behavior {
+ allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
+ cached_methods = [
+ "GET",
+ "HEAD",
+ ]
+ forwarded_values {
+ query_string = false
+ cookies {
+ forward = "none"
+ }
+ }
+ min_ttl = var.min_ttl
+ default_ttl = var.default_ttl
+ max_ttl = var.max_ttl
+ target_origin_id = "${aws_s3_bucket.website.id}-origin"
+ viewer_protocol_policy = "redirect-to-https"
+ }
+ ordered_cache_behavior {
+ path_pattern = "/content/immutable/*"
+ allowed_methods = ["GET", "HEAD", "OPTIONS"]
+ cached_methods = ["GET", "HEAD", "OPTIONS"]
+ target_origin_id = local.s3_origin_id
+ forwarded_values {
+ query_string = false
+ headers = ["Origin"]
+ cookies {
+ forward = "none"
+ }
+ }
+ min_ttl = 0
+ default_ttl = 86400
+ max_ttl = 31536000
+ compress = true
+ viewer_protocol_policy = "redirect-to-https"
+ }
+ # Cache behaviour with precedence 1
+ ordered_cache_behavior {
+ path_pattern = "/content/*"
+ allowed_methods = ["GET", "HEAD", "OPTIONS"]
+ cached_methods = ["GET", "HEAD"]
+ target_origin_id = local.s3_origin_id
+ forwarded_values {
+ query_string = false
+ cookies {
+ forward = "none"
+ }
+ }
+ min_ttl = 0
+ default_ttl = 3600
+ max_ttl = 86400
+ compress = true
+ viewer_protocol_policy = "redirect-to-https"
+ }
+ price_class = var.price_class
+ restrictions {
+ geo_restriction {
+ restriction_type = var.restriction_type
+ locations = var.locations
+ }
+ }
+ viewer_certificate {
+ cloudfront_default_certificate = var.cloudfront_default_certificate
+ acm_certificate_arn = aws_acm_certificate.cert.arn
+ ssl_support_method = "sni-only"
+ # tfsec:ignore:AWS021
+ minimum_protocol_version = "TLSv1.2_2018"
+ }
+ retain_on_delete = var.retain
+ tags = var.common_tags
+}
+
+resource "aws_route53_record" "legacy-tf" {
+ count = var.instance_count
+ zone_id = data.aws_route53_zone.dns_zone.zone_id
+ name = "brochureworker-${count.index + 1}.${data.aws_route53_zone.dns_zone.name}"
+ type = "A"
+ records = ["${aws_instance.brochureworker.*.private_ip[count.index]}"]
+ ttl = "300"
+}
+
+resource "aws_instance" "brochureworker" {}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/expected.yaml b/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/expected.yaml
new file mode 100644
index 0000000000..4e03ecd5ed
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/expected.yaml
@@ -0,0 +1,7 @@
+pass:
+ - "aws_s3_bucket.bucket_good_1"
+fail:
+ - "aws_s3_bucket.bucket_bad_1"
+ - "aws_s3_bucket.bucket_bad_2"
+ - "aws_s3_bucket.bucket_bad_3"
+ - "aws_s3_bucket.bucket_bad_4"
diff --git a/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/main.tf b/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/main.tf
new file mode 100644
index 0000000000..ade7d0dfb2
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/S3BucketHasPublicAccessBlock/main.tf
@@ -0,0 +1,44 @@
+resource "aws_s3_bucket" "bucket_good_1" {
+ bucket = "bucket_good"
+}
+
+resource "aws_s3_bucket" "bucket_bad_1" {
+ bucket = "bucket_bad_1"
+}
+
+resource "aws_s3_bucket" "bucket_bad_2" {
+ bucket = "bucket_bad_2"
+}
+
+resource "aws_s3_bucket" "bucket_bad_3" {
+ bucket = "bucket_bad_3"
+}
+
+resource "aws_s3_bucket" "bucket_bad_4" {
+ bucket = "bucket_bad_4"
+}
+
+resource "aws_s3_bucket_public_access_block" "access_good_1" {
+ bucket = aws_s3_bucket.bucket_good_1.id
+
+ block_public_acls = true
+ block_public_policy = true
+}
+
+resource "aws_s3_bucket_public_access_block" "access_bad_1" {
+ bucket = aws_s3_bucket.bucket_bad_1.id
+}
+
+resource "aws_s3_bucket_public_access_block" "access_bad_2" {
+ bucket = aws_s3_bucket.bucket_bad_2.id
+
+ block_public_acls = false
+ block_public_policy = false
+}
+
+resource "aws_s3_bucket_public_access_block" "access_bad_3" {
+ bucket = aws_s3_bucket.bucket_bad_3.id
+
+ block_public_acls = false
+ block_public_policy = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml b/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml
new file mode 100644
index 0000000000..4daa68a72b
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml
@@ -0,0 +1,33 @@
+pass:
+ - "aws_security_group.pass_alb"
+ - "aws_security_group.pass_batch"
+ - "aws_security_group.pass_cloudwatch_event"
+ - "aws_security_group.pass_codebuild"
+ - "aws_security_group.pass_dms"
+ - "aws_security_group.pass_docdb"
+ - "aws_security_group.pass_ec2"
+ - "aws_security_group.pass_ec2_client_vpn"
+ - "aws_security_group.pass_ec2_launch_config"
+ - "aws_security_group.pass_ec2_launch_template"
+ - "aws_security_group.pass_emr"
+ - "aws_security_group.pass_ecs"
+ - "aws_security_group.pass_efs"
+ - "aws_security_group.pass_eks"
+ - "aws_security_group.pass_eks_node"
+ - "aws_security_group.pass_elasticache"
+ - "aws_security_group.pass_elb"
+ - "aws_security_group.pass_eni"
+ - "aws_security_group.pass_es"
+ - "aws_security_group.pass_lambda"
+ - "aws_security_group.pass_lb"
+ - "aws_security_group.pass_mq"
+ - "aws_security_group.pass_msk"
+ - "aws_security_group.pass_mwaa"
+ - "aws_security_group.pass_neptune"
+ - "aws_security_group.pass_rds"
+ - "aws_security_group.pass_rds_cluster"
+ - "aws_security_group.pass_redshift"
+ - "aws_security_group.pass_sagemaker"
+ - "aws_security_group.pass_vpc_endpoint"
+fail:
+ - "aws_security_group.fail"
diff --git a/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf b/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf
new file mode 100644
index 0000000000..003e5ea72a
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf
@@ -0,0 +1,637 @@
+# pass
+
+# Batch
+
+resource "aws_security_group" "pass_batch" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_batch_compute_environment" "pass_batch" {
+ service_role = "aws_iam_role.batch.arn"
+ type = "MANAGED"
+
+ compute_resources {
+ max_vcpus = 16
+ security_group_ids = [aws_security_group.pass_batch.id]
+ subnets = ["aws_subnet.this.id"]
+ type = "FARGATE"
+ }
+}
+
+# CodeBuild
+
+resource "aws_security_group" "pass_codebuild" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_codebuild_project" "pass_codebuild" {
+ name = "build"
+ service_role = "aws_iam_role.codebuild.arn"
+
+ artifacts {
+ type = "NO_ARTIFACTS"
+ }
+ environment {
+ compute_type = "BUILD_GENERAL1_SMALL"
+ image = "aws/codebuild/standard:5.0"
+ type = "LINUX_CONTAINER"
+ }
+ source {
+ type = "S3"
+ }
+ vpc_config {
+ security_group_ids = [aws_security_group.pass_codebuild.id]
+ subnets = ["aws_subnet.public_a.id"]
+ vpc_id = "aws_vpc.vpc.id"
+ }
+}
+
+# DMS
+
+resource "aws_security_group" "pass_dms" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_dms_replication_instance" "pass_dms" {
+ replication_instance_class = "dms.t3.micro"
+ replication_instance_id = "dms"
+ vpc_security_group_ids = [aws_security_group.pass_dms.id]
+}
+
+# DocDB
+
+resource "aws_security_group" "pass_docdb" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_docdb_cluster" "pass_docdb" {
+ vpc_security_group_ids = [aws_security_group.pass_docdb.id]
+}
+
+# EC2
+
+resource "aws_security_group" "pass_ec2" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_instance" "pass_ec2" {
+ ami = "data.aws_ami.ubuntu.id"
+ instance_type = "t3.micro"
+ security_groups = [aws_security_group.pass_ec2.id]
+}
+
+resource "aws_security_group" "pass_ec2_client_vpn" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_ec2_client_vpn_network_association" "pass_ec2_client_vpn" {
+ client_vpn_endpoint_id = "aws_ec2_client_vpn_endpoint.this.id"
+ subnet_id = "aws_subnet.this.id"
+ security_groups = [aws_security_group.pass_ec2_client_vpn.id]
+}
+
+resource "aws_security_group" "pass_ec2_launch_config" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_launch_configuration" "pass_ec2_launch_config" {
+ image_id = "data.aws_ami.ubuntu.id"
+ instance_type = "t3.micro"
+ security_groups = [aws_security_group.pass_ec2_launch_config.id]
+}
+
+resource "aws_security_group" "pass_ec2_launch_template" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_launch_template" "pass_ec2_launch_template" {
+ image_id = "data.aws_ami.ubuntu.id"
+ instance_type = "t3.micro"
+ vpc_security_group_ids = [aws_security_group.pass_ec2_launch_template.id]
+}
+
+# ECS
+
+resource "aws_security_group" "pass_ecs" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_ecs_service" "pass_ecs" {
+ name = "service"
+
+ network_configuration {
+ subnets = ["aws_subnet.public_a.id"]
+ security_groups = [aws_security_group.pass_ecs.id]
+ }
+}
+
+# EFS
+
+resource "aws_security_group" "pass_efs" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_efs_mount_target" "pass_efs" {
+ file_system_id = "aws_efs_file_system.efs.id"
+ subnet_id = "aws_subnet.public_a.id"
+ security_groups = [aws_security_group.pass_efs.id]
+}
+
+# EKS
+
+resource "aws_security_group" "pass_eks" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_eks_cluster" "pass_eks" {
+ name = "eks"
+ role_arn = "aws_iam_role.eks.arn"
+ vpc_config {
+ security_group_ids = [aws_security_group.pass_eks.id]
+ subnet_ids = ["aws_subnet.public_a.id"]
+ }
+}
+
+resource "aws_security_group" "pass_eks_node" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_eks_node_group" "pass_eks_node" {
+ cluster_name = "eks"
+ node_group_name = "eks"
+ node_role_arn = "aws_iam_role.eks.arn"
+ subnet_ids = ["aws_subnet.public_a.id"]
+
+ remote_access {
+ ec2_ssh_key = "ec2_ssh_key"
+ source_security_group_ids = [aws_security_group.pass_eks_node.id]
+ }
+ scaling_config {
+ desired_size = 2
+ max_size = 3
+ min_size = 1
+ }
+}
+
+# Elasticache
+
+resource "aws_security_group" "pass_elasticache" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_elasticache_cluster" "pass_elasticache" {
+ cluster_id = "cache"
+ security_group_ids = [aws_security_group.pass_elasticache.id]
+}
+
+# ELB
+
+resource "aws_security_group" "pass_alb" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_lb" "pass_alb" {
+ load_balancer_type = "application"
+ security_groups = [aws_security_group.pass_alb.id]
+}
+
+resource "aws_security_group" "pass_elb" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_elb" "pass_elb" {
+ security_groups = [aws_security_group.pass_elb.id]
+
+ listener {
+ instance_port = 80
+ instance_protocol = "HTTP"
+ lb_port = 443
+ lb_protocol = "HTTPS"
+ }
+}
+
+resource "aws_security_group" "pass_lb" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_lb" "pass_lb" {
+ load_balancer_type = "application"
+ security_groups = [aws_security_group.pass_lb.id]
+}
+
+# ENI
+
+resource "aws_security_group" "pass_eni" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_network_interface" "pass_eni" {
+ subnet_id = "aws_subnet.public_a.id"
+ security_groups = [aws_security_group.pass_eni.id]
+}
+
+# ES
+
+resource "aws_security_group" "pass_es" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_elasticsearch_domain" "pass_es" {
+ domain_name = "es"
+
+ vpc_options {
+ security_group_ids = [aws_security_group.pass_es.id]
+ }
+}
+
+# Lambda
+
+resource "aws_security_group" "pass_lambda" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_lambda_function" "pass_lambda" {
+ function_name = "lambda"
+ handler = "lambda.handler"
+ role = "aws_iam_role.lambda.arn"
+ runtime = "python3.8"
+
+ vpc_config {
+ security_group_ids = [aws_security_group.pass_lambda.id]
+ subnet_ids = ["aws_subnet.public_a.id"]
+ }
+}
+
+# MQ
+
+resource "aws_security_group" "pass_mq" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_mq_broker" "pass_mq" {
+ broker_name = "mq"
+ engine_type = "ActiveMQ"
+ engine_version = "5.15.15"
+ host_instance_type = "mq.t3.micro"
+ security_groups = [aws_security_group.pass_mq.id]
+
+ user {
+ password = "pass"
+ username = "user"
+ }
+}
+
+# MSK
+
+resource "aws_security_group" "pass_msk" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_msk_cluster" "pass_msk" {
+ cluster_name = "msk"
+ kafka_version = "2.8.0"
+ number_of_broker_nodes = 1
+
+ broker_node_group_info {
+ client_subnets = ["aws_subnet.public_a.id"]
+ ebs_volume_size = 50
+ instance_type = "kafka.m5.large"
+ security_groups = [aws_security_group.pass_msk.id]
+ }
+}
+
+# MWAA
+
+resource "aws_security_group" "pass_mwaa" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_mwaa_environment" "pass_mwaa" {
+ dag_s3_path = "dags/"
+ execution_role_arn = "aws_iam_role.mwaa.arn"
+ name = "mwaa"
+ source_bucket_arn = "aws_s3_bucket.mwaa.arn"
+
+ network_configuration {
+ security_group_ids = [aws_security_group.pass_mwaa.id]
+ subnet_ids = ["aws_subnet.public_a.id"]
+ }
+
+}
+
+# Neptune
+
+resource "aws_security_group" "pass_neptune" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_neptune_cluster" "pass_neptune" {
+ vpc_security_group_ids = [aws_security_group.pass_neptune.id]
+}
+
+# RDS
+
+resource "aws_security_group" "pass_rds" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_db_instance" "pass_rds" {
+ instance_class = "db.t3.micro"
+ vpc_security_group_ids = [aws_security_group.pass_rds.id]
+}
+
+resource "aws_security_group" "pass_rds_cluster" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_rds_cluster" "pass_rds_cluster" {
+ vpc_security_group_ids = [aws_security_group.pass_rds_cluster.id]
+}
+
+# Redshift
+
+resource "aws_security_group" "pass_redshift" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_redshift_cluster" "pass_redshift" {
+ cluster_identifier = "redshift"
+ node_type = "dc2.large"
+ vpc_security_group_ids = [aws_security_group.pass_redshift.id]
+}
+
+# Sagemaker
+
+resource "aws_security_group" "pass_sagemaker" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_sagemaker_notebook_instance" "pass_sagemaker" {
+ instance_type = "ml.t3.medium"
+ name = "sagemaker"
+ role_arn = "aws_iam_role.sagemaker.arn"
+ security_groups = [aws_security_group.pass_sagemaker.id]
+}
+
+# VPC
+
+resource "aws_security_group" "pass_vpc_endpoint" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_vpc_endpoint" "pass_vpc_endpoint" {
+ vpc_id = "aws_vpc.this.id"
+ service_name = "com.amazonaws.us-west-2.s3"
+ vpc_endpoint_type = "Interface"
+ security_group_ids = [aws_security_group.pass_vpc_endpoint.id]
+}
+
+# fail
+
+resource "aws_security_group" "fail" {
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = 0.0.0.0/0
+ }
+}
+
+resource "aws_emr_cluster" "pass_emr" {
+ name = var.cluster_name
+ release_label = var.release_label
+ security_configuration = aws_emr_security_configuration.examplea.name
+
+ ec2_attributes {
+ subnet_id = var.subnet_id
+ emr_managed_master_security_group = aws_security_group.pass_emr.id
+ emr_managed_slave_security_group = aws_security_group.pass_emr.id
+ instance_profile = aws_iam_instance_profile.examplea.arn
+ }
+
+ service_role = aws_iam_role.emr_service.arn
+}
+
+resource "aws_security_group" "pass_emr" {
+ //todo
+ name = "block_access"
+ description = "Block all traffic"
+
+ ingress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["10.0.0.0/16"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["10.0.0.0/16"]
+ }
+}
+
+resource "aws_cloudwatch_event_target" "pass_cloudwatch_event" {
+ target_id = var.target_id
+ arn = var.arn
+ rule = var.rule
+ role_arn = var.role_arn
+
+ ecs_target {
+ launch_type = var.launch_type
+ task_count = var.task_count
+ task_definition_arn = var.task_definition_arn
+
+ network_configuration {
+ subnets = [var.subnet_id]
+ security_groups = [aws_security_group.pass_cloudwatch_event.id]
+ assign_public_ip = false
+ }
+ }
+
+ input = < None:
+ os.environ['UNIQUE_TAG'] = ''
+ warnings.filterwarnings("ignore", category=ResourceWarning)
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+ def test_VPCHasFlowLog(self):
+ self.go("VPCHasFlowLog")
+
+ def test_VPCHasRestrictedSG(self):
+ self.go("VPCHasRestrictedSG")
+
+ def test_APIGWLoggingLevelsDefinedProperly(self):
+ self.go("APIGWLoggingLevelsDefinedProperly")
+
+ def test_GuardDutyIsEnabled(self):
+ self.go("GuardDutyIsEnabled")
+
+ def test_SGAttachedToResource(self):
+ self.go("SGAttachedToResource")
+
+ def test_StorageContainerActivityLogsNotPublic(self):
+ self.go("StorageContainerActivityLogsNotPublic")
+
+ def test_StorageCriticalDataEncryptedCMK(self):
+ self.go("StorageCriticalDataEncryptedCMK")
+
+ def test_VAconfiguredToSendReports(self):
+ self.go("VAconfiguredToSendReports")
+
+ def test_VAconfiguredToSendReportsToAdmins(self):
+ self.go("VAconfiguredToSendReportsToAdmins")
+
+ def test_VAisEnabledInStorageAccount(self):
+ self.go("VAisEnabledInStorageAccount")
+
+ def test_VAsetPeriodicScansOnSQL(self):
+ self.go("VAsetPeriodicScansOnSQL")
+
+ def test_CloudtrailHasCloudwatch(self):
+ self.go("CloudtrailHasCloudwatch")
+
+ def test_S3BucketHasPublicAccessBlock(self):
+ self.go("S3BucketHasPublicAccessBlock")
+
+ def test_AccessToPostgreSQLFromAzureServicesIsDisabled(self):
+ self.go("AccessToPostgreSQLFromAzureServicesIsDisabled")
+
+ def test_AzureActiveDirectoryAdminIsConfigured(self):
+ self.go("AzureActiveDirectoryAdminIsConfigured")
+
+ def test_DisableAccessToSqlDBInstanceForRootUsersWithoutPassword(self):
+ self.go("DisableAccessToSqlDBInstanceForRootUsersWithoutPassword")
+
+ def test_GCPProjectHasNoLegacyNetworks(self):
+ self.go("GCPProjectHasNoLegacyNetworks")
+
+ def test_AzureDataFactoriesEncryptedWithCustomerManagedKey(self):
+ self.go("AzureDataFactoriesEncryptedWithCustomerManagedKey")
+
+ def test_AzureUnattachedDisksAreEncrypted(self):
+ self.go("AzureUnattachedDisksAreEncrypted")
+
+ def test_AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs(self):
+ self.go("AzureAntimalwareIsConfiguredWithAutoUpdatesForVMs")
+
+ def test_ALBRedirectsHTTPToHTTPS(self):
+ self.go("ALBRedirectsHTTPToHTTPS")
+
+ def test_GCPLogBucketsConfiguredUsingLock(self):
+ self.go("GCPLogBucketsConfiguredUsingLock")
+
+ def test_GCPAuditLogsConfiguredForAllServicesAndUsers(self):
+ self.go("GCPAuditLogsConfiguredForAllServicesAndUsers")
+
+ def test_GCPKMSCryptoKeysAreNotPubliclyAccessible(self):
+ self.go("GCPKMSCryptoKeysAreNotPubliclyAccessible")
+
+ def test_VirtualMachinesUtilizingManagedDisks(self):
+ self.go("VirtualMachinesUtilizingManagedDisks")
+
+ def test_RDSClusterHasBackupPlan(self):
+ self.go("RDSClusterHasBackupPlan")
+
+ def test_RedshiftClusterHasBackupPlan(self):
+ self.go("RedshiftClusterHasBackupPlan")
+
+ def test_EBSAddedBackup(self):
+ self.go("EBSAddedBackup")
+
+ def test_AMRClustersNotOpenToInternet(self):
+ self.go("AMRClustersNotOpenToInternet")
+
+ def test_AutoScallingEnabledELB(self):
+ self.go("AutoScallingEnabledELB")
+
+ def test_IAMGroupHasAtLeastOneUser(self):
+ self.go("IAMGroupHasAtLeastOneUser")
+
+ def test_IAMUserHasNoConsoleAccess(self):
+ self.go("IAMUserHasNoConsoleAccess")
+
+ def test_IAMUsersAreMembersAtLeastOneGroup(self):
+ self.go("IAMUsersAreMembersAtLeastOneGroup")
+
+ def test_DataExplorerEncryptionUsesCustomKey(self):
+ self.go("DataExplorerEncryptionUsesCustomKey")
+
+ def test_MSQLenablesCustomerManagedKey(self):
+ self.go("MSQLenablesCustomerManagedKey")
+
+ def test_PGSQLenablesCustomerManagedKey(self):
+ self.go("PGSQLenablesCustomerManagedKey")
+
+ def test_StorageLoggingIsEnabledForBlobService(self):
+ self.go("StorageLoggingIsEnabledForBlobService")
+
+ def test_StorageLoggingIsEnabledForTableService(self):
+ self.go("StorageLoggingIsEnabledForTableService")
+
+ def test_VMHasBackUpMachine(self):
+ self.go("VMHasBackUpMachine")
+
+ def test_SubnetHasACL(self):
+ self.go("SubnetHasACL")
+
+ def test_GKEClustersAreNotUsingDefaultServiceAccount(self):
+ self.go("GKEClustersAreNotUsingDefaultServiceAccount")
+
+ def test_AzureStorageAccountsUseCustomerManagedKeyForEncryption(self):
+ self.go("AzureStorageAccountsUseCustomerManagedKeyForEncryption")
+
+ def test_AzureMSSQLServerHasSecurityAlertPolicy(self):
+ self.go("AzureMSSQLServerHasSecurityAlertPolicy")
+
+ def test_AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached(self):
+ self.go("AzureSynapseWorkspacesHaveNoIPFirewallRulesAttached")
+
+ def test_EncryptedEBSVolumeOnlyConnectedToEC2s(self):
+ self.go("EncryptedEBSVolumeOnlyConnectedToEC2s")
+
+ def test_ServiceAccountHasGCPmanagedKey(self):
+ self.go("ServiceAccountHasGCPmanagedKey")
+
+ def test_AutoScalingEnableOnDynamoDBTables(self):
+ self.go("AutoScalingEnableOnDynamoDBTables")
+
+ def test_EIPAllocatedToVPCAttachedEC2(self):
+ self.go("EIPAllocatedToVPCAttachedEC2")
+
+ def test_EFSAddedBackup(self):
+ self.go("EFSAddedBackup")
+
+ def test_EFSAddedBackupSuppress(self):
+ self.go("EFSAddedBackupSuppress", "EFSAddedBackup")
+
+ def test_Route53ARecordAttachedResource(self):
+ self.go("Route53ARecordAttachedResource")
+
+ def test_PostgresRDSHasQueryLoggingEnabled(self):
+ self.go("PostgresRDSHasQueryLoggingEnabled")
+
+ def test_ALBProtectedByWAF(self):
+ self.go("ALBProtectedByWAF")
+
+ def test_APIProtectedByWAF(self):
+ self.go("APIProtectedByWAF")
+
+ def test_registry_load(self):
+ registry = Registry(parser=NXGraphCheckParser(), checks_dir=str(
+ Path(__file__).parent.parent.parent.parent.parent / "checkov" / "terraform" / "checks" / "graph_checks"))
+ registry.load_checks()
+ self.assertGreater(len(registry.checks), 0)
+
+ def go(self, dir_name, check_name=None):
+ dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+ f"resources/{dir_name}")
+ assert os.path.exists(dir_path)
+ policy_dir_path = os.path.dirname(checks.__file__)
+ assert os.path.exists(policy_dir_path)
+ found = False
+ for root, d_names, f_names in os.walk(policy_dir_path):
+ for f_name in f_names:
+ check_name = dir_name if check_name is None else check_name
+ if f_name == f"{check_name}.yaml":
+ found = True
+ policy = load_yaml_data(f_name, root)
+ assert policy is not None
+ expected = load_yaml_data("expected.yaml", dir_path)
+ assert expected is not None
+ report = get_policy_results(dir_path, policy)
+ expected = load_yaml_data("expected.yaml", dir_path)
+
+ expected_to_fail = expected.get('fail', [])
+ expected_to_pass = expected.get('pass', [])
+ expected_to_skip = expected.get('skip', [])
+
+ self.assert_entities(expected_to_pass, report.passed_checks, True)
+ self.assert_entities(expected_to_fail, report.failed_checks, False)
+ self.assert_entities(expected_to_skip, report.skipped_checks, True)
+
+ assert found
+
+ def assert_entities(self, expected_entities: List[str], results: List[CheckResult], assertion: bool):
+ self.assertEqual(len(expected_entities), len(results),
+ f"mismatch in number of results in {'passed' if assertion else 'failed'}, "
+ f"expected: {len(expected_entities)}, got: {len(results)}")
+ for expected_entity in expected_entities:
+ found = False
+ for check_result in results:
+ entity_id = check_result.resource
+ if entity_id == expected_entity:
+ found = True
+ break
+ self.assertTrue(found, f"expected to find entity {expected_entity}, {'passed' if assertion else 'failed'}")
+
+
+def get_policy_results(root_folder, policy):
+ check_id = policy['metadata']['id']
+ graph_runner = Runner()
+ report = graph_runner.run(root_folder, runner_filter=RunnerFilter(checks=[check_id]))
+ return report
+
+
+def wrap_policy(policy):
+ policy['query'] = policy['definition']
+ del policy['definition']
+
+
+def load_yaml_data(source_file_name, dir_path):
+ expected_path = os.path.join(dir_path, source_file_name)
+ if not os.path.exists(expected_path):
+ return None
+
+ with open(expected_path, "r") as f:
+ expected_data = yaml.safe_load(f)
+
+ return json.loads(json.dumps(expected_data))
diff --git a/tests/terraform/graph/checks_infra/__init__.py b/tests/terraform/graph/checks_infra/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicSG.yaml
new file mode 100644
index 0000000000..7671b418a1
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "contains"
+ value: "0.0.0.0/0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicVMs.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicVMs.yaml
new file mode 100644
index 0000000000..b42bccf658
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/PublicVMs.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicVMs"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "contains"
+ value: "0.0.0.0/0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/SpecificBlockSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/SpecificBlockSG.yaml
new file mode 100644
index 0000000000..7c3c4713da
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/SpecificBlockSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "SpecificBlockSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "contains"
+ value: "10.2.2.0/24"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/TagIncludes.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/TagIncludes.yaml
new file mode 100644
index 0000000000..9754951642
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/TagIncludes.yaml
@@ -0,0 +1,11 @@
+metadata:
+ id: "TagIncludes"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "all"
+ attribute: "tags"
+ operator: "contains"
+ value: "acme"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/test_solver.py
new file mode 100644
index 0000000000..9eec7cefa9
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/contains_solver/test_solver.py
@@ -0,0 +1,47 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestContainsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestContainsSolver, self).setUp()
+
+ def test_public_virtual_machines(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "PublicVMs"
+ should_pass = ['aws_default_security_group.default_security_group_open']
+ should_fail = ['aws_default_security_group.default_security_group_closed']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_list_cidr_blocks(self):
+ root_folder = '../../../resources/security_group_list_cidr_blocks'
+ check_id = "PublicSG"
+ should_pass = []
+ should_fail = ['aws_security_group.passed_cidr_block', 'aws_security_group.failed_cidr_blocks']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_list_cidr_blocks_specific(self):
+ root_folder = '../../../resources/security_group_list_cidr_blocks'
+ check_id = "SpecificBlockSG"
+ should_pass = ['aws_security_group.passed_cidr_block']
+ should_fail = ['aws_security_group.failed_cidr_blocks']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_contains_dict(self):
+ root_folder = '../../../resources/tag_includes'
+ check_id = "TagIncludes"
+ should_pass = ['aws_instance.some_instance', 'aws_subnet.acme_subnet']
+ should_fail = ['aws_s3_bucket.acme_s3_bucket']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/AmiEndingWith.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/AmiEndingWith.yaml
new file mode 100644
index 0000000000..1c7560f2d9
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/AmiEndingWith.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "AmiEndingWith"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_instance"
+ attribute: "ami"
+ operator: "ending_with"
+ value: "-0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/test_solver.py
new file mode 100644
index 0000000000..c95a412315
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/ending_with_solver/test_solver.py
@@ -0,0 +1,20 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestEndingWithSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestEndingWithSolver, self).setUp()
+
+ def test_ami_ending_with(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "AmiEndingWith"
+ should_pass = ['aws_instance.with_open_def_security_groups']
+ should_fail = ['aws_instance.with_closed_def_security_groups', 'aws_instance.with_open_security_groups', 'aws_instance.with_subnet_public', 'aws_instance.with_subnet_not_public',]
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/EncryptedResources.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/EncryptedResources.yaml
new file mode 100644
index 0000000000..f81cb91dd6
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/EncryptedResources.yaml
@@ -0,0 +1,14 @@
+metadata:
+ id: "EncryptedResources"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_rds_cluster"
+ - "aws_neptune_cluster"
+ - "aws_s3_bucket"
+ attribute: "encryption_"
+ operator: "equals"
+ value: "ENCRYPTED"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/PublicDBSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/PublicDBSG.yaml
new file mode 100644
index 0000000000..c64bc4d7a0
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/PublicDBSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicDBSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_db_security_group"
+ attribute: "ingress.cidr"
+ operator: "equals"
+ value: "10.0.0.0/24"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/SGPorts.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/SGPorts.yaml
new file mode 100644
index 0000000000..13de4408c8
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/SGPorts.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "SGPorts"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.*.to_port"
+ operator: "equals"
+ value: "8182"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/test_solver.py
new file mode 100644
index 0000000000..a50ce6c778
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/equals_solver/test_solver.py
@@ -0,0 +1,30 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestEqualsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestEqualsSolver, self).setUp()
+
+ def test_equals_solver_simple(self):
+ root_folder = '../../../resources/public_security_groups'
+ check_id = "PublicDBSG"
+ should_pass = ['aws_db_security_group.aws_db_security_group_private']
+ should_fail = ['aws_db_security_group.aws_db_security_group_public']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_equals_solver_wildcard(self):
+ root_folder = '../../../resources/encryption_test'
+ check_id = "EncryptedResources"
+ should_pass = ['aws_rds_cluster.rds_cluster_encrypted', 'aws_s3_bucket.encrypted_bucket', 'aws_neptune_cluster.encrypted_neptune']
+ should_fail = ['aws_rds_cluster.rds_cluster_unencrypted', 'aws_s3_bucket.unencrypted_bucket', 'aws_neptune_cluster.unencrypted_neptune']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ super(TestEqualsSolver, self).run_test(root_folder=root_folder, expected_results=expected_results,
+ check_id=check_id)
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/TagEnvironmentExists.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/TagEnvironmentExists.yaml
new file mode 100644
index 0000000000..0fd0a0b9cf
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/TagEnvironmentExists.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "TagEnvironmentExists"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Environment"
+ operator: "exists"
+
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/VersioningEnabledExists.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/VersioningEnabledExists.yaml
new file mode 100644
index 0000000000..beabdec6e7
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/VersioningEnabledExists.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "VersioningEnabledExists"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "versioning.enabled"
+ operator: "exists"
+
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/test_solver.py
new file mode 100644
index 0000000000..f0346af2f8
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/exists_solver/test_solver.py
@@ -0,0 +1,29 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class ExistsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(ExistsSolver, self).setUp()
+
+ def test_nested_attribute_exists(self):
+ root_folder = '../../../resources/s3_bucket'
+ check_id = "VersioningEnabledExists"
+ should_pass = ['aws_s3_bucket.destination']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_nested_attribute_doesnt_exists(self):
+ root_folder = '../../../resources/s3_bucket'
+ check_id = "TagEnvironmentExists"
+ should_pass = []
+ should_fail = ['aws_s3_bucket.destination']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicSG.yaml
new file mode 100644
index 0000000000..c667966f5c
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "not_contains"
+ value: "0.0.0.0/0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicVMs.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicVMs.yaml
new file mode 100644
index 0000000000..22243400a5
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/PublicVMs.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicVMs"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_default_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "not_contains"
+ value: "0.0.0.0/0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/SpecificBlockSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/SpecificBlockSG.yaml
new file mode 100644
index 0000000000..a0bafe287d
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/SpecificBlockSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "SpecificBlockSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.cidr_blocks"
+ operator: "not_contains"
+ value: "10.2.2.0/24"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/test_solver.py
new file mode 100644
index 0000000000..9eb415ee3a
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_contains_solver/test_solver.py
@@ -0,0 +1,38 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNotContainsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestNotContainsSolver, self).setUp()
+
+ def test_public_virtual_machines(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "PublicVMs"
+ should_pass = ['aws_default_security_group.default_security_group_closed']
+ should_fail = ['aws_default_security_group.default_security_group_open']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_list_cidr_blocks(self):
+ root_folder = '../../../resources/security_group_list_cidr_blocks'
+ check_id = "PublicSG"
+ should_pass = ['aws_security_group.passed_cidr_block', 'aws_security_group.failed_cidr_blocks']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_list_cidr_blocks_specific(self):
+ root_folder = '../../../resources/security_group_list_cidr_blocks'
+ check_id = "SpecificBlockSG"
+ should_pass = ['aws_security_group.failed_cidr_blocks']
+ should_fail = ['aws_security_group.passed_cidr_block']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/AmiEndingWith.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/AmiEndingWith.yaml
new file mode 100644
index 0000000000..79aaf037f0
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/AmiEndingWith.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "AmiEndingWith"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_instance"
+ attribute: "ami"
+ operator: "not_ending_with"
+ value: "-0"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/test_solver.py
new file mode 100644
index 0000000000..91e7f2acf5
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_ending_with_solver/test_solver.py
@@ -0,0 +1,20 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNotEndingWithSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestNotEndingWithSolver, self).setUp()
+
+ def test_ami_ending_with(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "AmiEndingWith"
+ should_pass = ['aws_instance.with_closed_def_security_groups', 'aws_instance.with_open_security_groups', 'aws_instance.with_subnet_public', 'aws_instance.with_subnet_not_public',]
+ should_fail = ['aws_instance.with_open_def_security_groups']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/PublicDBSG.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/PublicDBSG.yaml
new file mode 100644
index 0000000000..c0e61b0e35
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/PublicDBSG.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "PublicDBSG"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_db_security_group"
+ attribute: "ingress.cidr"
+ operator: "not_equals"
+ value: "10.0.0.0/24"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/SGPorts.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/SGPorts.yaml
new file mode 100644
index 0000000000..a62c60bb96
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/SGPorts.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "SGPorts"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_security_group"
+ attribute: "ingress.*.to_port"
+ operator: "not_equals"
+ value: 8182
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/test_solver.py
new file mode 100644
index 0000000000..6d38258f60
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_equals_solver/test_solver.py
@@ -0,0 +1,29 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNotEqualsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestNotEqualsSolver, self).setUp()
+
+ def test_not_equals_solver_simple(self):
+ root_folder = '../../../resources/public_security_groups'
+ check_id = "PublicDBSG"
+ should_fail = ['aws_db_security_group.aws_db_security_group_private']
+ should_pass = ['aws_db_security_group.aws_db_security_group_public']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_not_equals_solver_wildcard(self):
+ root_folder = '../../../resources/security_group_multiple_rules'
+ check_id = "SGPorts"
+ should_pass = ['aws_security_group.sg1', 'aws_security_group.sg2']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/TagEnvironmentExists.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/TagEnvironmentExists.yaml
new file mode 100644
index 0000000000..aebd5a3016
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/TagEnvironmentExists.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "TagEnvironmentExists"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Environment"
+ operator: "not_exists"
+
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/VersioningEnabledExists.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/VersioningEnabledExists.yaml
new file mode 100644
index 0000000000..12499e8de7
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/VersioningEnabledExists.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "VersioningEnabledExists"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "versioning.enabled"
+ operator: "not_exists"
+
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/test_solver.py
new file mode 100644
index 0000000000..f03b7344f3
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_exists_solver/test_solver.py
@@ -0,0 +1,29 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNotExistsSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestNotExistsSolver, self).setUp()
+
+ def test_nested_attribute_doesnt_exists(self):
+ root_folder = '../../../resources/s3_bucket'
+ check_id = "VersioningEnabledExists"
+ should_pass = []
+ should_fail = ['aws_s3_bucket.destination']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_nested_attribute_doesnt_exists(self):
+ root_folder = '../../../resources/s3_bucket'
+ check_id = "TagEnvironmentExists"
+ should_pass = ['aws_s3_bucket.destination']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/NameStartingWith.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/NameStartingWith.yaml
new file mode 100644
index 0000000000..337133d735
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/NameStartingWith.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "NameStartingWith"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_subnet"
+ attribute: "tags.Name"
+ operator: "not_starting_with"
+ value: "first"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/test_solver.py
new file mode 100644
index 0000000000..1ca8eb349f
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/not_starting_with_solver/test_solver.py
@@ -0,0 +1,20 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNotStartingWithSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestNotStartingWithSolver, self).setUp()
+
+ def test_name_starting_with(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "NameStartingWith"
+ should_pass = ['aws_subnet.subnet_not_public_ip']
+ should_fail = ['aws_subnet.subnet_public_ip']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/NameStartingWith.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/NameStartingWith.yaml
new file mode 100644
index 0000000000..4e32c11b89
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/NameStartingWith.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "NameStartingWith"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_subnet"
+ attribute: "tags.Name"
+ operator: "starting_with"
+ value: "first"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/test_solver.py
new file mode 100644
index 0000000000..c1cae96262
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/starting_with_solver/test_solver.py
@@ -0,0 +1,20 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestStartingWithSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestStartingWithSolver, self).setUp()
+
+ def test_name_starting_with(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "NameStartingWith"
+ should_pass = ['aws_subnet.subnet_public_ip']
+ should_fail = ['aws_subnet.subnet_not_public_ip']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/NameWithin.yaml b/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/NameWithin.yaml
new file mode 100644
index 0000000000..dce89fca70
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/NameWithin.yaml
@@ -0,0 +1,14 @@
+metadata:
+ id: "NameWithin"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "aws_subnet"
+ attribute: "tags.Name"
+ operator: "within"
+ value:
+ - "first-tf-example"
+ - "third-tf-example"
+
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/__init__.py b/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/test_solver.py b/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/test_solver.py
new file mode 100644
index 0000000000..040acd330b
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/attribute_solvers/within_solver/test_solver.py
@@ -0,0 +1,20 @@
+import os
+
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestWithinSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestWithinSolver, self).setUp()
+
+ def test_name_starting_with(self):
+ root_folder = '../../../resources/public_virtual_machines'
+ check_id = "NameWithin"
+ should_pass = ['aws_subnet.subnet_public_ip']
+ should_fail = ['aws_subnet.subnet_not_public_ip']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/__init__.py b/tests/terraform/graph/checks_infra/complex_solvers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/and_solver/BucketsWithDevEnvAndPrivateACL.yaml b/tests/terraform/graph/checks_infra/complex_solvers/and_solver/BucketsWithDevEnvAndPrivateACL.yaml
new file mode 100644
index 0000000000..2bc946d882
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/complex_solvers/and_solver/BucketsWithDevEnvAndPrivateACL.yaml
@@ -0,0 +1,18 @@
+metadata:
+ id: "BucketsWithDevEnvAndPrivateACL"
+scope:
+ provider: "AWS"
+definition:
+ and:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Environment"
+ operator: "equals"
+ value: "Dev"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "acl"
+ operator: "equals"
+ value: "private"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/and_solver/__init__.py b/tests/terraform/graph/checks_infra/complex_solvers/and_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/and_solver/test_solver.py b/tests/terraform/graph/checks_infra/complex_solvers/and_solver/test_solver.py
new file mode 100644
index 0000000000..c22a1aeece
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/complex_solvers/and_solver/test_solver.py
@@ -0,0 +1,18 @@
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+import os
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestAndQuery(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestAndQuery, self).setUp()
+
+ def test_buckets_with_option_env_tag(self):
+ root_folder = '../../../resources/s3_bucket_2'
+ check_id = "BucketsWithDevEnvAndPrivateACL"
+ should_pass = ['aws_s3_bucket.private']
+ should_fail = ['aws_s3_bucket.public', 'aws_s3_bucket.non_tag']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/or_solver/BucketsWithEnvTag.yaml b/tests/terraform/graph/checks_infra/complex_solvers/or_solver/BucketsWithEnvTag.yaml
new file mode 100644
index 0000000000..69b7fee1fa
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/complex_solvers/or_solver/BucketsWithEnvTag.yaml
@@ -0,0 +1,18 @@
+metadata:
+ id: "BucketsWithEnvTag"
+scope:
+ provider: "AWS"
+definition:
+ or:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Environment"
+ operator: "equals"
+ value: "Dev"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Environment"
+ operator: "equals"
+ value: "Prod"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/or_solver/__init__.py b/tests/terraform/graph/checks_infra/complex_solvers/or_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/complex_solvers/or_solver/test_solver.py b/tests/terraform/graph/checks_infra/complex_solvers/or_solver/test_solver.py
new file mode 100644
index 0000000000..a871ca5635
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/complex_solvers/or_solver/test_solver.py
@@ -0,0 +1,18 @@
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+import os
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestOrQuery(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(TestOrQuery, self).setUp()
+
+ def test_buckets_with_option_env_tag(self):
+ root_folder = '../../../resources/s3_bucket_2'
+ check_id = "BucketsWithEnvTag"
+ should_pass = ['aws_s3_bucket.public', 'aws_s3_bucket.private']
+ should_fail = ['aws_s3_bucket.non_tag']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/__init__.py b/tests/terraform/graph/checks_infra/connection_solvers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/ALBConnectedToHTTPS.yaml b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/ALBConnectedToHTTPS.yaml
new file mode 100644
index 0000000000..807dbabc93
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/ALBConnectedToHTTPS.yaml
@@ -0,0 +1,47 @@
+metadata:
+ id: "ALBConnectedToHTTPS"
+ name: "Ensure that ALB redirects HTTP requests into HTTPS ones"
+ category: "NETWORKING"
+definition:
+ and:
+ - cond_type: "filter"
+ value:
+ - "aws_lb"
+ attribute: "resource_type"
+ operator: "within"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb"
+ attribute: "load_balancer_type"
+ operator: "equals"
+ value: "application"
+ - or:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "not_exists"
+ - and:
+ - cond_type: "connection"
+ resource_types:
+ - "aws_lb"
+ connected_resource_types:
+ - "aws_lb_listener"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "certificate_arn"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "ssl_policy"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_lb_listener"
+ attribute: "protocol"
+ operator: "equals"
+ value: "HTTPS"
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/AndComplexConnection.yaml b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/AndComplexConnection.yaml
new file mode 100644
index 0000000000..f1bdca89b1
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/AndComplexConnection.yaml
@@ -0,0 +1,36 @@
+metadata:
+ id: AndComplexConnection
+scope:
+ provider: AWS
+definition:
+ and:
+ - cond_type: filter
+ value:
+ - aws_network_interface
+ - aws_instance
+ operator: within
+ attribute: resource_type
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - aws_instance
+ connected_resource_types:
+ - aws_network_interface
+ - or:
+ - cond_type: attribute
+ attribute: tags.Env
+ operator: equals
+ value: prod
+ resource_types:
+ - aws_instance
+ - aws_network_interface
+ - aws_subnet
+ - aws_vpc
+ - cond_type: attribute
+ attribute: tags.Env
+ operator: not_exists
+ resource_types:
+ - aws_instance
+ - aws_network_interface
+ - aws_subnet
+ - aws_vpc
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/__init__.py b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/test_solver.py b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/test_solver.py
new file mode 100644
index 0000000000..164c7008b4
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/and_connection_solver/test_solver.py
@@ -0,0 +1,28 @@
+import os
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class ConnectionSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(ConnectionSolver, self).setUp()
+
+ def test_and_connection(self):
+ root_folder = '../../../resources/ec2_instance_network_interfaces'
+ check_id = "AndComplexConnection"
+ should_pass = ['aws_network_interface.network_interface_foo']
+ should_fail = ['aws_network_interface.network_interface_goo', 'aws_instance.instance_bar', 'aws_instance.instance_foo']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_multiple_connections(self):
+ root_folder = '../../../resources/lb'
+ check_id = "ALBConnectedToHTTPS"
+ should_pass = []
+ should_fail = ["aws_lb.lb_bad_1"]
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/NetworkInterfaceForInstance.yaml b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/NetworkInterfaceForInstance.yaml
new file mode 100644
index 0000000000..68a3ccd147
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/NetworkInterfaceForInstance.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "NetworkInterfaceForInstance"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "connection"
+ resource_types:
+ - "aws_instance"
+ connected_resource_types:
+ - "aws_network_interface"
+ operator: "exists"
+
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/VPCForSubnet.yaml b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/VPCForSubnet.yaml
new file mode 100644
index 0000000000..6fcb6e0e94
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/VPCForSubnet.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "VPCForSubnet"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "connection"
+ resource_types:
+ - "aws_subnet"
+ connected_resource_types:
+ - "aws_vpc"
+ operator: "exists"
+
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/__init__.py b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/test_solver.py b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/test_solver.py
new file mode 100644
index 0000000000..f0fc4dfd31
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/connection_exist_solver/test_solver.py
@@ -0,0 +1,28 @@
+import os
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class ConnectionSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(ConnectionSolver, self).setUp()
+
+ def test_connection_found(self):
+ root_folder = '../../../resources/ec2_instance_network_interfaces'
+ check_id = "NetworkInterfaceForInstance"
+ should_pass = ['aws_instance.instance_foo', 'aws_network_interface.network_interface_foo']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
+
+ def test_output_connection(self):
+ root_folder = '../../../resources/output_example'
+ check_id = "VPCForSubnet"
+ should_pass = ['aws_vpc.my_vpc','aws_subnet.my_subnet']
+ should_fail = []
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/NoNetworkInterfaceForInstance.yaml b/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/NoNetworkInterfaceForInstance.yaml
new file mode 100644
index 0000000000..772ce354d9
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/NoNetworkInterfaceForInstance.yaml
@@ -0,0 +1,12 @@
+metadata:
+ id: "NoNetworkInterfaceForInstance"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "connection"
+ resource_types:
+ - "aws_instance"
+ connected_resource_types:
+ - "aws_network_interface"
+ operator: "not_exists"
+
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/__init__.py b/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/test_solver.py b/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/test_solver.py
new file mode 100644
index 0000000000..8e1ee49c95
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/connection_not_exist_solver/test_solver.py
@@ -0,0 +1,19 @@
+import os
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class ConnectionSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(ConnectionSolver, self).setUp()
+
+ def test_connection_not_found(self):
+ root_folder = '../../../resources/ec2_instance_network_interfaces'
+ check_id = "NoNetworkInterfaceForInstance"
+ should_pass = ['aws_network_interface.network_interface_goo', 'aws_instance.bar']
+ should_fail = ['aws_instance.instance_foo', 'aws_network_interface.network_interface_foo']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
\ No newline at end of file
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/SpecificInstanceComplexConnection.yaml b/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/SpecificInstanceComplexConnection.yaml
new file mode 100644
index 0000000000..a905a8792a
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/SpecificInstanceComplexConnection.yaml
@@ -0,0 +1,33 @@
+metadata:
+ id: SpecificInstanceComplexConnection
+scope:
+ provider: AWS
+definition:
+ or:
+ - cond_type: filter
+ value:
+ - aws_network_interface
+ - aws_instance
+ operator: within
+ attribute: resource_type
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - aws_instance
+ connected_resource_types:
+ - aws_network_interface
+ - cond_type: connection
+ operator: exists
+ resource_types:
+ - aws_subnet
+ connected_resource_types:
+ - aws_vpc
+ - cond_type: attribute
+ attribute: tags.Env
+ operator: equals
+ value: prod
+ resource_types:
+ - aws_instance
+ - aws_network_interface
+ - aws_subnet
+ - aws_vpc
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/__init__.py b/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/test_solver.py b/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/test_solver.py
new file mode 100644
index 0000000000..b52a1a50c3
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/connection_solvers/or_connection_solver/test_solver.py
@@ -0,0 +1,19 @@
+import os
+from tests.terraform.graph.checks_infra.test_base import TestBaseSolver
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class ConnectionSolver(TestBaseSolver):
+ def setUp(self):
+ self.checks_dir = TEST_DIRNAME
+ super(ConnectionSolver, self).setUp()
+
+ def test_or_connection(self):
+ root_folder = '../../../resources/ec2_instance_network_interfaces'
+ check_id = "SpecificInstanceComplexConnection"
+ should_pass = ['aws_instance.instance_foo', 'aws_network_interface.network_interface_foo', 'aws_instance.instance_bar']
+ should_fail = ['aws_network_interface.network_interface_goo']
+ expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
+
+ self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
diff --git a/tests/terraform/graph/checks_infra/test_base.py b/tests/terraform/graph/checks_infra/test_base.py
new file mode 100644
index 0000000000..53516d55d5
--- /dev/null
+++ b/tests/terraform/graph/checks_infra/test_base.py
@@ -0,0 +1,45 @@
+import os
+from unittest import TestCase
+
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.checks_infra.registry import Registry
+from checkov.terraform.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestBaseSolver(TestCase):
+ checks_dir = ""
+
+ def setUp(self):
+ self.source = "Terraform"
+ self.registry = Registry(parser=NXGraphCheckParser(), checks_dir=self.checks_dir)
+ self.registry.load_checks()
+ self.runner = Runner(external_registries=[self.registry])
+
+ def run_test(self, root_folder, expected_results, check_id):
+ root_folder = os.path.realpath(os.path.join(self.checks_dir, root_folder))
+ report = self.runner.run(root_folder=root_folder, runner_filter=RunnerFilter(checks=[check_id]))
+ verification_results = verify_report(report=report, expected_results=expected_results)
+ self.assertIsNone(verification_results, verification_results)
+
+
+def verify_report(report, expected_results):
+ for check_id in expected_results:
+ found = False
+ for resource in expected_results[check_id]['should_pass']:
+ for record in report.passed_checks:
+ if record.check_id == check_id and record.resource == resource:
+ found = True
+ break
+ if not found:
+ return f"expected resource {resource} to pass in check {check_id}"
+ found = False
+ for resource in expected_results[check_id]['should_fail']:
+ for record in report.failed_checks:
+ if record.check_id == check_id and record.resource == resource:
+ found = True
+ break
+ if not found:
+ return f"expected resource {resource} to fail in check {check_id}"
+
+ return None
diff --git a/tests/terraform/graph/db_connector/__init__.py b/tests/terraform/graph/db_connector/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/db_connector/test_networkx_connector.py b/tests/terraform/graph/db_connector/test_networkx_connector.py
new file mode 100644
index 0000000000..62700424ca
--- /dev/null
+++ b/tests/terraform/graph/db_connector/test_networkx_connector.py
@@ -0,0 +1,19 @@
+import os
+from unittest import TestCase
+
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.terraform.graph_builder.local_graph import TerraformLocalGraph
+from checkov.terraform.parser import Parser
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestNetworkxConnector(TestCase):
+ def test_creating_graph(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/encryption'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir, 'AWS')
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph._create_vertices()
+ nxc = NetworkxConnector()
+ nxc.save_graph(local_graph)
diff --git a/tests/terraform/graph/graph_builder/__init__.py b/tests/terraform/graph/graph_builder/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/graph_builder/graph_components/__init__.py b/tests/terraform/graph/graph_builder/graph_components/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/graph_builder/graph_components/test_blocks.py b/tests/terraform/graph/graph_builder/graph_components/test_blocks.py
new file mode 100644
index 0000000000..3a7080b3ac
--- /dev/null
+++ b/tests/terraform/graph/graph_builder/graph_components/test_blocks.py
@@ -0,0 +1,185 @@
+from unittest import TestCase
+
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.blocks import TerraformBlock
+
+
+class TestBlocks(TestCase):
+ def test_update_inner_attribute_1(self):
+ config = {'aws_security_group': {
+ 'test': {'name': ['test'], 'vpc_id': ['${aws_vpc.vpc_main.id}'], 'tags': [{'Name': 'test'}],
+ 'description': ['test - Elasticsearch Cluster'], 'ingress': [
+ {'from_port': [443], 'to_port': [443], 'protocol': ['tcp'],
+ 'security_groups': [['${aws_security_group.test.id}', '${data.aws_security_group.test.id}']]}]}}}
+
+ block = TerraformBlock(name='aws_security_group.test', config=config, path='test_path', block_type=BlockType.RESOURCE,
+ attributes=config['aws_security_group']['test'])
+
+ block.update_inner_attribute(attribute_key='ingress.security_groups.0', nested_attributes=block.attributes,
+ value_to_update='sg-0')
+ block.update_inner_attribute(attribute_key='ingress.security_groups.1', nested_attributes=block.attributes,
+ value_to_update='sg-1')
+
+ self.assertEqual('sg-0', block.attributes['ingress.security_groups.0'],
+ f"failed to update ingress.security_groups.0, got {block.attributes['ingress.security_groups.0']}")
+ self.assertEqual('sg-1', block.attributes['ingress.security_groups.1'],
+ f"failed to update ingress.security_groups.1, got {block.attributes['ingress.security_groups.1']}")
+ self.assertEqual('sg-0', block.attributes['ingress']['security_groups'][0],
+ f"failed to update block.attributes['ingress']['security_groups'][0], got {block.attributes['ingress']['security_groups'][0]}")
+ self.assertEqual('sg-1', block.attributes['ingress']['security_groups'][1],
+ f"failed to update block.attributes['ingress']['security_groups'][1], got {block.attributes['ingress']['security_groups'][1]}")
+
+ def test_update_inner_attribute_2(self):
+ config = {'aws_security_group': {'test': {'name': ['test'], 'vpc_id': ['${aws_vpc.vpc_main.id}'], 'ingress': [
+ {'from_port': [53], 'to_port': [53], 'protocol': ['udp'], 'security_groups': [
+ ['${data.test1.id}', '${data.test2.id}', '${data.test3.id}', '${data.test4.id}', '${data.test5.id}',
+ '${data.test6.id}']],
+ 'cidr_blocks': [['test1', '${var.test2}', '${var.test4}']]},
+ {'from_port': [53], 'to_port': [53], 'protocol': ['tcp'], 'security_groups': [
+ ['${data.test1.id}', '${data.test2.id}', '${data.test3.id}', '${data.test4.id}', '${data.test5.id}',
+ '${data.test6.id}']],
+ 'cidr_blocks': [['test', '${var.test}', '${var.v3}']]}]}}}
+
+ block = TerraformBlock(name='aws_security_group.test', config=config, path='test_path', block_type=BlockType.RESOURCE,
+ attributes=config['aws_security_group']['test'])
+
+ block.update_inner_attribute(attribute_key='ingress.0.cidr_blocks.1', nested_attributes=block.attributes,
+ value_to_update='sg-1')
+
+ self.assertEqual('sg-1', block.attributes['ingress.0.cidr_blocks.1'],
+ f"failed to update ingress.0.cidr_blocks.1, got {block.attributes['ingress.0.cidr_blocks.1']}")
+ self.assertEqual('sg-1', block.attributes['ingress'][0]['cidr_blocks'][1],
+ f"failed to update block.attributes['ingress'][0]['cidr_blocks'][1], got {block.attributes['ingress'][0]['cidr_blocks'][1]}")
+
+ def test_update_inner_attribute_3(self):
+ config = {'aws_iam_policy_document': {'vcs_webhook_step_function_execution_policy': {'statement': [
+ {'actions': [['events:DescribeRule', 'events:PutRule', 'events:PutTargets']], 'effect': ['Allow'],
+ 'resources': [[
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule']]},
+ {'actions': [['states:StartExecution']], 'effect': ['Allow'], 'resources': [[
+ 'arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:${module.consts.bc_checkov_scanner_step_function_name}*']]},
+ {'actions': [['lambda:InvokeFunction']], 'effect': ['Allow'], 'resources': [
+ '${formatlist("%s%s","arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:",concat([\'${local.vcs_webhook_lambda_name}\', \'${local.customer_api_lambda}\']))}']}]}}}
+ block = TerraformBlock(name='aws_iam_policy_document.vcs_webhook_step_function_execution_policy', config=config,
+ path='test_path', block_type=BlockType.DATA,
+ attributes=config['aws_iam_policy_document']['vcs_webhook_step_function_execution_policy'])
+ block.update_inner_attribute(attribute_key='statement.1.resources.0',
+ nested_attributes={'statement': [{'actions': ['events:DescribeRule',
+ 'events:PutRule',
+ 'events:PutTargets'],
+ 'effect': 'Allow', 'resources': [
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule']},
+ {'actions': 'states:StartExecution',
+ 'effect': 'Allow',
+ 'resources': 'arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:bc-vcs-scanner-sfn*'},
+ {'actions': 'lambda:InvokeFunction',
+ 'effect': 'Allow',
+ 'resources': '${formatlist("%s%s","arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:",concat([\'${local.vcs_webhook_lambda_name}\', \'${local.customer_api_lambda}\']))}'}],
+ 'statement.0': {
+ 'actions': ['events:DescribeRule', 'events:PutRule',
+ 'events:PutTargets'], 'effect': 'Allow',
+ 'resources': [
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule']},
+ 'statement.0.actions': ['events:DescribeRule',
+ 'events:PutRule',
+ 'events:PutTargets'],
+ 'statement.0.actions.0': 'events:DescribeRule',
+ 'statement.0.actions.1': 'events:PutRule',
+ 'statement.0.actions.2': 'events:PutTargets',
+ 'statement.0.effect': 'Allow', 'statement.0.resources': [
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule'],
+ 'statement.0.resources.0': 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'statement.0.resources.1': 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule',
+ 'statement.1': {
+ 'resources': 'arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:bc-vcs-scanner-sfn*'},
+ 'statement.1.actions': 'states:StartExecution',
+ 'statement.1.actions.0': 'states:StartExecution',
+ 'statement.1.effect': 'Allow',
+ 'statement.1.resources': 'arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:bc-vcs-scanner-sfn*',
+ 'statement.1.resources.0': 'arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:bc-vcs-scanner-sfn*',
+ 'statement.2': {'actions': 'lambda:InvokeFunction',
+ 'effect': 'Allow',
+ 'resources': '${formatlist("%s%s","arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:",concat([\'${local.vcs_webhook_lambda_name}\', \'${local.customer_api_lambda}\']))}'},
+ 'statement.2.actions': 'lambda:InvokeFunction',
+ 'statement.2.actions.0': 'lambda:InvokeFunction',
+ 'statement.2.effect': 'Allow',
+ 'statement.2.resources': '${formatlist("%s%s","arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:",concat([\'${local.vcs_webhook_lambda_name}\', \'${local.customer_api_lambda}\']))}'},
+ value_to_update='arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:bc-vcs-scanner-sfn*')
+ self.assertIn(block.attributes['statement.0.resources.1'],
+ [
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule']
+ )
+ self.assertIn(block.attributes['statement.0.resources.0'],
+ [
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForECSTaskRule',
+ 'arn:aws:events:${var.region}:${data.aws_caller_identity.current.account_id}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule']
+ )
+
+ def test_update_complex_key(self):
+ config = {'labels': [{'app.kubernetes.io/name': '${local.name}', 'app.kubernetes.io/instance': 'hpa',
+ 'app.kubernetes.io/version': '1.0.0', 'app.kubernetes.io/managed-by': 'terraform'}]}
+ attributes = {'labels': {'app.kubernetes.io/name': '${local.name}', 'app.kubernetes.io/instance': 'hpa',
+ 'app.kubernetes.io/version': '1.0.0', 'app.kubernetes.io/managed-by': 'terraform'},
+ 'labels.app.kubernetes.io/name': '${local.name}', 'labels.app.kubernetes.io/instance': 'hpa',
+ 'labels.app.kubernetes.io/version': '1.0.0', 'labels.app.kubernetes.io/managed-by': 'terraform'}
+ block = TerraformBlock(name='test_local_name', config=config, path='', block_type=BlockType.LOCALS,
+ attributes=attributes)
+
+ block.update_inner_attribute(attribute_key="labels.app.kubernetes.io/name", nested_attributes=attributes,
+ value_to_update="dummy value")
+ self.assertEquals("dummy value", block.attributes["labels.app.kubernetes.io/name"])
+
+ def test_update_complex_key2(self):
+ config = {}
+ attributes = {'var.owning_account': {'route_to': None, 'route_to_cidr_blocks': '${local.allowed_cidrs}',
+ 'static_routes': None, 'subnet_ids': '${local.own_vpc.private_subnet_ids}',
+ 'subnet_route_table_ids': '${local.own_vpc.private_route_table_ids}',
+ 'transit_gateway_vpc_attachment_id': None,
+ 'vpc_cidr': '${local.own_vpc.vpc_cidr}',
+ 'vpc_id': '${local.own_vpc.vpc_id}'}}
+ block = TerraformBlock(name='test_local_name', config=config, path='', block_type=BlockType.LOCALS,
+ attributes=attributes)
+ value_to_update = "test"
+ block.update_inner_attribute(attribute_key="var.owning_account.vpc_cidr", nested_attributes=attributes,
+ value_to_update=value_to_update)
+ self.assertDictEqual({'var.owning_account': block.attributes["var.owning_account"]},
+ {'var.owning_account': {'route_to': None, 'route_to_cidr_blocks': '${local.allowed_cidrs}',
+ 'static_routes': None,
+ 'subnet_ids': '${local.own_vpc.private_subnet_ids}',
+ 'subnet_route_table_ids': '${local.own_vpc.private_route_table_ids}',
+ 'transit_gateway_vpc_attachment_id': None,
+ 'vpc_cidr': 'test',
+ 'vpc_id': '${local.own_vpc.vpc_id}'}})
+
+ def test_update_inner_attribute_bad_index(self):
+ config = {'aws_security_group': {
+ 'test': {}}}
+
+ nested_attributes = {'provisioner/remote-exec.connection': {'private_key': '${file(var.ssh_key_path)}', 'user': 'ec2-user'}, 'provisioner/remote-exec.connection.private_key': '${file(var.ssh_key_path)}', 'provisioner/remote-exec.connection.user': 'ec2-user', 'provisioner/remote-exec.inline': ['command'], 'provisioner/remote-exec.inline.0': 'command0', 'provisioner/remote-exec.inline.1': 'command1', 'provisioner/remote-exec.inline.2': 'command2', 'provisioner/remote-exec.inline.3': 'command3', 'provisioner/remote-exec.inline.4': 'command4'}
+ block = TerraformBlock(name='aws_security_group.test', config=config, path='test_path', block_type=BlockType.RESOURCE,
+ attributes=nested_attributes)
+
+ block.update_inner_attribute(attribute_key='provisioner/remote-exec.inline.3', nested_attributes=nested_attributes,
+ value_to_update='new_command_3')
+
+ self.assertEqual('new_command_3', block.attributes['provisioner/remote-exec.inline.3'],
+ f"failed to update provisioner/remote-exec.inline.3, got {block.attributes['provisioner/remote-exec.inline.3']}")
+
+ def test_update_inner_attribute_bad_map_entry(self):
+ config = {'aws_security_group': {
+ 'test': {}}}
+
+ nested_attributes = {'triggers': {'change_endpoint_name': '${md5("my_dev_endpoint")}', 'change_extra_jars_s3_path': '${md5()}', 'change_extra_python_libs_s3_path': '${md5()}', 'change_number_of_nodes': '${md5("2")}', 'change_public_keys': '${md5("${var.glue_endpoint_public_keys}")}', 'change_region': '${md5("us-east-1")}', 'change_role': '${md5("arn:aws:iam::111111111111:role/my_role")}', 'change_security_configuration': '${md5()}', 'change_security_group_ids': '${md5("${var.glue_endpoint_security_group_ids}")}', 'change_subnet_id': '${md5()}'}, 'provisioner/local-exec': {'command': "echo 'info: destroy ignored because part of apply'", 'when': 'destroy'}, 'provisioner/local-exec.command': "echo 'info: destroy ignored because part of apply'", 'provisioner/local-exec.environment': {'endpoint_name': '${var.glue_endpoint_name}', 'extra_jars_s3_path': '${var.glue_endpoint_extra_jars_libraries}', 'extra_python_libs_s3_path': '${var.glue_endpoint_extra_python_libraries}', 'number_of_nodes': '${var.glue_endpoint_number_of_dpus}', 'public_keys': '${join(",",var.glue_endpoint_public_keys)}', 'region': '${var.aws_region}', 'role_arn': '${var.glue_endpoint_role}', 'security_configuration': '${var.glue_endpoint_security_configuration}', 'security_group_ids': '${join(",",var.glue_endpoint_security_group_ids)}', 'subnet_id': '${var.glue_endpoint_subnet_id}'}, 'provisioner/local-exec.environment.endpoint_name': 'my_dev_endpoint', 'provisioner/local-exec.environment.extra_jars_s3_path': '', 'provisioner/local-exec.environment.extra_python_libs_s3_path': '', 'provisioner/local-exec.environment.number_of_nodes': 2, 'provisioner/local-exec.environment.public_keys': '${join(",",var.glue_endpoint_public_keys)}', 'provisioner/local-exec.environment.region': 'us-east-1', 'provisioner/local-exec.environment.role_arn': 'arn:aws:iam::111111111111:role/my_role', 'provisioner/local-exec.environment.security_configuration': '', 'provisioner/local-exec.environment.security_group_ids': '${join(",",var.glue_endpoint_security_group_ids)}', 'provisioner/local-exec.environment.subnet_id': '', 'provisioner/local-exec.when': 'destroy', 'resource_type': ['null_resource'], 'triggers.change_endpoint_name': '${md5("my_dev_endpoint")}', 'triggers.change_extra_jars_s3_path': '${md5()}', 'triggers.change_extra_python_libs_s3_path': '${md5()}', 'triggers.change_number_of_nodes': '${md5("2")}', 'triggers.change_public_keys': '${md5("${var.glue_endpoint_public_keys}")}', 'triggers.change_region': '${md5("us-east-1")}', 'triggers.change_role': '${md5("arn:aws:iam::111111111111:role/my_role")}', 'triggers.change_security_configuration': '${md5()}', 'triggers.change_security_group_ids': '${md5("${var.glue_endpoint_security_group_ids}")}', 'triggers.change_subnet_id': '${md5()}'}
+ block = TerraformBlock(name='null_resource.glue_endpoint_apply', config=config, path='test_path', block_type=BlockType.RESOURCE,
+ attributes=nested_attributes)
+ attribute_key = 'provisioner/local-exec.environment.security_configuration'
+ block.update_inner_attribute(attribute_key=attribute_key, nested_attributes=nested_attributes,
+ value_to_update='')
+
+ self.assertEqual('', block.attributes[attribute_key],
+ f"failed to update provisioner/remote-exec.inline.3, got {block.attributes[attribute_key]}")
diff --git a/tests/terraform/graph/graph_builder/test_graph_builder.py b/tests/terraform/graph/graph_builder/test_graph_builder.py
new file mode 100644
index 0000000000..3262890b46
--- /dev/null
+++ b/tests/terraform/graph/graph_builder/test_graph_builder.py
@@ -0,0 +1,194 @@
+import os
+import shutil
+from unittest import TestCase
+
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_to_tf_definitions import convert_graph_vertices_to_tf_definitions
+from checkov.terraform.graph_manager import TerraformGraphManager
+from checkov.terraform.parser import external_modules_download_path
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestGraphBuilder(TestCase):
+
+ def test_build_graph(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/general_example')
+
+ graph_manager = TerraformGraphManager(db_connector=NetworkxConnector())
+ graph, tf_definitions = graph_manager.build_graph_from_source_directory(resources_dir)
+
+ expected_num_of_var_nodes = 3
+ expected_num_of_locals_nodes = 1
+ expected_num_of_resources_nodes = 1
+ expected_num_of_provider_nodes = 1
+ vertices_by_block_type = graph.vertices_by_block_type
+ self.assertEqual(expected_num_of_var_nodes, len(vertices_by_block_type[BlockType.VARIABLE]))
+ self.assertEqual(expected_num_of_locals_nodes, len(vertices_by_block_type[BlockType.LOCALS]))
+ self.assertEqual(expected_num_of_resources_nodes, len(vertices_by_block_type[BlockType.RESOURCE]))
+ self.assertEqual(expected_num_of_provider_nodes, len(vertices_by_block_type[BlockType.PROVIDER]))
+
+ provider_node = graph.vertices[vertices_by_block_type[BlockType.PROVIDER][0]]
+ resource_node = graph.vertices[vertices_by_block_type[BlockType.RESOURCE][0]]
+ local_node = graph.vertices[vertices_by_block_type[BlockType.LOCALS][0]]
+
+ var_bucket_name_node = None
+ var_region_node = None
+ var_aws_profile_node = None
+ for index in vertices_by_block_type[BlockType.VARIABLE]:
+ var_node = graph.vertices[index]
+ if var_node.name == 'aws_profile':
+ var_aws_profile_node = var_node
+ if var_node.name == 'bucket_name':
+ var_bucket_name_node = var_node
+ if var_node.name == 'region':
+ var_region_node = var_node
+
+ self.check_edge(graph, resource_node, local_node, 'bucket')
+ self.check_edge(graph, resource_node, provider_node, 'provider')
+ self.check_edge(graph, resource_node, var_region_node, 'region')
+ self.check_edge(graph, provider_node, var_aws_profile_node, 'profile')
+ self.check_edge(graph, local_node, var_bucket_name_node, 'bucket_name')
+
+ def check_edge(self, graph, node_from, node_to, expected_label):
+ hashed_from = node_from.get_hash()
+ hashed_to = node_to.get_hash()
+ matching_edges = []
+ for edge in graph.edges:
+ if graph.vertices[edge.origin].get_hash() == hashed_from and graph.vertices[edge.dest].get_hash() == hashed_to:
+ matching_edges.append(edge)
+ self.assertGreater(len(matching_edges), 0,
+ f'expected to find edge from [{node_from.block_type} {node_from.name}] to [{node_to.block_type} {node_to.name}] with label [{expected_label}]')
+ if not any(e.label == expected_label for e in matching_edges):
+ self.fail(
+ f'expected to find edge from [{node_from.block_type} {node_from.name}] to [{node_to.block_type} {node_to.name}] with label [{expected_label}], found edges: {[str(e) for e in matching_edges]}')
+
+ @staticmethod
+ def get_vertex_by_name_and_type(local_graph, block_type, name, multiple=False):
+ vertices = [local_graph.vertices[i] for i in local_graph.vertices_block_name_map[block_type][name]]
+ if multiple:
+ return vertices
+ return vertices[0]
+
+ def test_update_vertices_configs_deep_nesting(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_deep_nesting')
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+ expected_config = {'aws_s3_bucket': {'default': {'server_side_encryption_configuration': [
+ {'rule': [{'apply_server_side_encryption_by_default': [
+ {'sse_algorithm': ['AES256'], 'kms_master_key_id': ['']}]}]}]}}}
+ actual_config = local_graph.vertices[local_graph.vertices_by_block_type.get(BlockType.RESOURCE)[0]].config
+ self.assertDictEqual(expected_config, actual_config)
+ print('')
+
+ def test_build_graph_with_linked_modules(self):
+ # see the image to view the expected graph in tests/resources/modules/linked_modules/expected_graph.png
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/linked_modules'))
+
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, tf_definitions = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=False)
+
+ vertices_by_block_type = local_graph.vertices_by_block_type
+
+ expected_vertices_num_by_type = {
+ BlockType.VARIABLE: 5,
+ BlockType.RESOURCE: 5,
+ BlockType.OUTPUT: 3,
+ BlockType.MODULE: 2,
+ BlockType.DATA: 1,
+ }
+
+ for block_type, count in expected_vertices_num_by_type.items():
+ self.assertEqual(count, len(vertices_by_block_type[block_type]))
+
+ output_this_lambda_func_arn = self.get_vertex_by_name_and_type(local_graph, BlockType.OUTPUT,
+ 'this_lambda_function_arn')
+ output_this_lambda_func_name = self.get_vertex_by_name_and_type(local_graph, BlockType.OUTPUT,
+ 'this_lambda_function_name')
+ output_this_s3_bucket_id = self.get_vertex_by_name_and_type(local_graph, BlockType.OUTPUT, 'this_s3_bucket_id')
+ resource_aws_lambda_function = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'aws_lambda_function.this')
+ resource_aws_s3_bucket_policy = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'aws_s3_bucket_policy.this')
+ resource_aws_s3_bucket = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE, 'aws_s3_bucket.this')
+
+ self.check_edge(local_graph, node_from=output_this_lambda_func_arn, node_to=resource_aws_lambda_function,
+ expected_label='value')
+ self.check_edge(local_graph, node_from=output_this_lambda_func_name, node_to=resource_aws_lambda_function,
+ expected_label='value')
+ self.check_edge(local_graph, node_from=output_this_s3_bucket_id, node_to=resource_aws_s3_bucket_policy,
+ expected_label='value')
+ self.check_edge(local_graph, node_from=output_this_s3_bucket_id, node_to=resource_aws_s3_bucket,
+ expected_label='value')
+
+ def test_build_graph_with_linked_registry_modules(self):
+ resources_dir = os.path.realpath(
+ os.path.join(TEST_DIRNAME, '../resources/modules/registry_security_group_inner_module'))
+
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, tf_definitions = graph_manager.build_graph_from_source_directory(resources_dir,
+ render_variables=True,
+ download_external_modules=True)
+
+ outputs_vpcs = self.get_vertex_by_name_and_type(local_graph, BlockType.OUTPUT, 'security_group_vpc_id',
+ multiple=True)
+ resource_flow_log = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'aws_flow_log.related_flow_log')
+ resource_security_group_this = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'aws_security_group.this')
+ resource_security_group_this_name_prefix = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'aws_security_group.this_name_prefix')
+
+ output_this_security_group_vpc_id_inner = [o for o in outputs_vpcs if 'http-80' in o.path][0]
+ output_this_security_group_vpc_id_outer = [o for o in outputs_vpcs if 'http-80' not in o.path][0]
+
+ self.check_edge(local_graph, node_from=resource_flow_log, node_to=output_this_security_group_vpc_id_inner,
+ expected_label='vpc_id')
+ self.check_edge(local_graph, node_from=output_this_security_group_vpc_id_outer,
+ node_to=resource_security_group_this, expected_label='value')
+ self.check_edge(local_graph, node_from=output_this_security_group_vpc_id_outer,
+ node_to=resource_security_group_this_name_prefix, expected_label='value')
+
+ # cleanup
+ if os.path.exists(os.path.join(resources_dir, external_modules_download_path)):
+ shutil.rmtree(os.path.join(resources_dir, external_modules_download_path))
+
+ def test_build_graph_with_deep_nested_edges(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/k8_service'))
+
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, tf = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ resource_kubernetes_deployment = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
+ 'kubernetes_deployment.bazel_remote_cache')
+ locals_name = self.get_vertex_by_name_and_type(local_graph, BlockType.LOCALS, 'name')
+ locals_labels = self.get_vertex_by_name_and_type(local_graph, BlockType.LOCALS, 'labels')
+
+ self.check_edge(local_graph, node_from=locals_labels, node_to=locals_name,
+ expected_label="labels.app.kubernetes.io/name")
+ self.check_edge(local_graph, node_from=resource_kubernetes_deployment, node_to=locals_name,
+ expected_label="metadata.name")
+ self.check_edge(local_graph, node_from=resource_kubernetes_deployment, node_to=locals_name,
+ expected_label="spec.template.metadata.name")
+ self.check_edge(local_graph, node_from=resource_kubernetes_deployment, node_to=locals_name,
+ expected_label="spec.template.spec.container.name")
+ self.check_edge(local_graph, node_from=resource_kubernetes_deployment, node_to=locals_name,
+ expected_label="spec.template.spec.volume.1.config_map.name")
+
+ def test_blocks_from_local_graph_module(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/stacks'))
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, tf = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+ tf, _ = convert_graph_vertices_to_tf_definitions(local_graph.vertices, resources_dir)
+ found_results = 0
+ for key, value in tf.items():
+ if key.startswith(os.path.join(os.path.dirname(resources_dir), 's3_inner_modules', 'inner', 'main.tf')):
+ conf = value['resource'][0]['aws_s3_bucket']['inner_s3']
+ if 'stage/main' in key or 'prod/main' in key:
+ self.assertTrue(conf['versioning'][0]['enabled'][0])
+ found_results += 1
+ elif 'test/main' in key:
+ self.assertFalse(conf['versioning'][0]['enabled'][0])
+ found_results += 1
+ self.assertEqual(found_results, 3)
diff --git a/tests/terraform/graph/graph_builder/test_local_graph.py b/tests/terraform/graph/graph_builder/test_local_graph.py
new file mode 100644
index 0000000000..50ab0f8db8
--- /dev/null
+++ b/tests/terraform/graph/graph_builder/test_local_graph.py
@@ -0,0 +1,212 @@
+import os
+from unittest import TestCase
+
+from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
+from checkov.common.graph.graph_builder import EncryptionValues, EncryptionTypes
+from checkov.common.graph.graph_builder.utils import calculate_hash
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_components.blocks import TerraformBlock
+from checkov.terraform.graph_builder.graph_components.generic_resource_encryption import ENCRYPTION_BY_RESOURCE_TYPE
+from checkov.terraform.graph_builder.graph_to_tf_definitions import convert_graph_vertices_to_tf_definitions
+from checkov.terraform.parser import Parser
+from checkov.terraform.graph_builder.local_graph import TerraformLocalGraph
+from checkov.terraform.graph_manager import TerraformGraphManager
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestLocalGraph(TestCase):
+
+ def setUp(self) -> None:
+ self.source = "TERRAFORM"
+
+ def test_update_vertices_configs_attribute_like_resource_name(self):
+ config = {"resource_type": {"resource_name": {"attribute1": 1, "attribute2": 2, "resource_name": ["caution!"]}}}
+ attributes = {"attribute1": 1, "attribute2": 2, "resource_name": "ok"}
+ local_graph = TerraformLocalGraph(None, {})
+ vertex = TerraformBlock(name="resource_type.resource_name", config=config, path='', block_type=BlockType.RESOURCE, attributes=attributes)
+ vertex.changed_attributes["resource_name"] = ""
+ local_graph.vertices.append(vertex)
+ local_graph.update_vertices_configs()
+ expected_config = {"resource_type": {"resource_name": {"attribute1": 1, "attribute2": 2, "resource_name": ["ok"]}}}
+ self.assertDictEqual(expected_config, vertex.config)
+
+ def test_single_edge_with_same_label(self):
+ resources_dir = os.path.realpath(
+ os.path.join(TEST_DIRNAME, '../resources/k8_service'))
+
+ graph_manager = TerraformGraphManager(NetworkxConnector())
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir,
+ render_variables=True)
+ edges_hash = []
+ for e in local_graph.edges:
+ edge_hash = calculate_hash({"origin": e.origin, "dest": e.dest, "label": e.label})
+ if edge_hash in edges_hash:
+ origin = local_graph.vertices[e.origin]
+ dest = local_graph.vertices[e.dest]
+ self.fail(f'edge {e} == [{origin} - {e.label} -> {dest}] appears more than once in the graph')
+ else:
+ edges_hash.append(edge_hash)
+
+ def test_set_variables_values_from_modules(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME,
+ '../resources/variable_rendering/render_from_module_vpc'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, tf_definitions = hcl_config_parser.parse_hcl_module(resources_dir,
+ source=self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph._create_vertices()
+
+ variables_before_module_definitions = {
+ "cidr": "0.0.0.0/0",
+ "private_subnets": [],
+ "public_subnets": [],
+ "enable_nat_gateway": False,
+ "single_nat_gateway": False,
+ "enable_dns_hostnames": False,
+ "public_subnet_tags": {},
+ "private_subnet_tags": {},
+ }
+
+ for var_name, var_value in variables_before_module_definitions.items():
+ vertex_index = local_graph.vertices_block_name_map[BlockType.VARIABLE].get(var_name)[0]
+ vertex = local_graph.vertices[vertex_index]
+ default_val = vertex.attributes['default']
+ if type(default_val) == list:
+ self.assertEqual(var_value, default_val[0])
+ else:
+ self.assertEqual(var_value, default_val)
+
+ local_graph.build_graph(resources_dir)
+
+ expected_variables_after = {
+ "cidr": "172.16.0.0/16",
+ "private_subnets": ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"],
+ "public_subnets": ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"],
+ "enable_nat_gateway": True,
+ "single_nat_gateway": True,
+ "enable_dns_hostnames": True,
+ "public_subnet_tags": {"kubernetes.io/cluster/${local.cluster_name}": "shared",
+ "kubernetes.io/role/elb": "1"},
+ "private_subnet_tags": {"kubernetes.io/cluster/${local.cluster_name}" : "shared",
+ "kubernetes.io/role/internal-elb": "1"}
+ }
+
+ for var_name, var_value in expected_variables_after.items():
+ vertex_index = local_graph.vertices_block_name_map[BlockType.VARIABLE].get(var_name)[0]
+ vertex = local_graph.vertices[vertex_index]
+ default_val = vertex.attributes['default']
+ if type(default_val) == list:
+ self.assertEqual(var_value, default_val[0])
+ else:
+ self.assertEqual(var_value, default_val)
+
+ def test_encryption_aws(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/encryption'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir,
+ self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph._create_vertices()
+ local_graph.calculate_encryption_attribute()
+ all_attributes = [vertex.get_attribute_dict() for vertex in local_graph.vertices]
+ for attribute_dict in all_attributes:
+ [resource_type, resource_name] = attribute_dict[CustomAttributes.ID].split(".")
+ if resource_type in ENCRYPTION_BY_RESOURCE_TYPE:
+ is_encrypted = attribute_dict[CustomAttributes.ENCRYPTION]
+ details = attribute_dict[CustomAttributes.ENCRYPTION_DETAILS]
+ self.assertEqual(is_encrypted, EncryptionValues.ENCRYPTED.value if resource_name.startswith("encrypted")
+ else EncryptionValues.UNENCRYPTED.value, f'failed for "{resource_type}.{resource_name}"')
+ if is_encrypted == EncryptionValues.ENCRYPTED.value:
+ if 'kms_key_id' in attribute_dict or 'kms_master_key_id' in attribute_dict:
+ self.assertEqual(details, EncryptionTypes.KMS_VALUE.value, f'Bad encryption details for "{resource_type}.{resource_name}"')
+ else:
+ self.assertIn(details, [EncryptionTypes.AES256.value, EncryptionTypes.KMS_VALUE.value, EncryptionTypes.NODE_TO_NODE.value, EncryptionTypes.DEFAULT_KMS.value], f'Bad encryption details for "{resource_type}.{resource_name}"')
+ else:
+ self.assertEqual(details, "")
+ else:
+ self.assertIsNone(attribute_dict.get(CustomAttributes.ENCRYPTION))
+ self.assertIsNone(attribute_dict.get(CustomAttributes.ENCRYPTION_DETAILS))
+
+ def test_vertices_from_local_graph(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME,
+ '../resources/variable_rendering/render_from_module_vpc'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir,
+ self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph._create_vertices()
+ tf_definitions, breadcrumbs = convert_graph_vertices_to_tf_definitions(local_graph.vertices, resources_dir)
+ self.assertIsNotNone(tf_definitions)
+ self.assertIsNotNone(breadcrumbs)
+
+ def test_module_dependencies(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/stacks'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir, self.source)
+ self.assertEqual(module_dependency_map[f'{resources_dir}/prod'], [[]])
+ self.assertEqual(module_dependency_map[f'{resources_dir}/stage'], [[]])
+ self.assertEqual(module_dependency_map[f'{resources_dir}/test'], [[]])
+ self.assertEqual(module_dependency_map[f'{resources_dir}/prod/sub-prod'], [[f'{resources_dir}/prod/main.tf']])
+ expected_inner_modules = [
+ [f'{resources_dir}/prod/main.tf', f'{resources_dir}/prod/sub-prod/main.tf'],
+ [f'{resources_dir}/stage/main.tf'],
+ [f'{resources_dir}/test/main.tf']
+ ]
+ self.assertEqual(module_dependency_map[f'{os.path.dirname(resources_dir)}/s3_inner_modules'], expected_inner_modules)
+ self.assertEqual(module_dependency_map[f'{os.path.dirname(resources_dir)}/s3_inner_modules/inner'],
+ list(map(lambda dep_list: dep_list + [f'{os.path.dirname(resources_dir)}/s3_inner_modules/main.tf'],
+ expected_inner_modules)))
+
+ def test_blocks_from_local_graph_module(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/stacks'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir,
+ self.source)
+ self.assertEqual(len(list(filter(lambda block: block.block_type == BlockType.RESOURCE and block.name == 'aws_s3_bucket.inner_s3', module.blocks))), 3)
+ self.assertEqual(len(list(filter(lambda block: block.block_type == BlockType.MODULE and block.name == 'inner_module_call', module.blocks))), 3)
+ self.assertEqual(len(list(filter(lambda block: block.block_type == BlockType.MODULE and block.name == 's3', module.blocks))), 3)
+ self.assertEqual(len(list(filter(lambda block: block.block_type == BlockType.MODULE and block.name == 'sub-module', module.blocks))), 1)
+
+ def test_vertices_from_local_graph_module(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/stacks'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir,
+ self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph.build_graph(render_variables=True)
+
+ self.assertEqual(12, len(local_graph.edges))
+
+ def test_variables_same_name_different_modules(self):
+ resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/modules/same_var_names'))
+ hcl_config_parser = Parser()
+ module, module_dependency_map, _ = hcl_config_parser.parse_hcl_module(resources_dir,
+ self.source)
+ local_graph = TerraformLocalGraph(module, module_dependency_map)
+ local_graph.build_graph(render_variables=True)
+ print(local_graph.edges)
+ self.assertEqual(12, len(local_graph.edges))
+ self.assertEqual(13, len(local_graph.vertices))
+
+ module_variable_edges = [
+ e for e in local_graph.edges
+ if local_graph.vertices[e.dest].block_type == "module" and local_graph.vertices[e.dest].path.endswith(
+ 'same_var_names/module2/main.tf')
+ ]
+
+ # Check they point to 2 different modules
+ self.assertEqual(2, len(module_variable_edges))
+ self.assertNotEqual(local_graph.vertices[module_variable_edges[0].origin],
+ local_graph.vertices[module_variable_edges[1].origin])
+
+
+ module_variable_edges = [
+ e for e in local_graph.edges
+ if local_graph.vertices[e.dest].block_type == "module" and local_graph.vertices[e.dest].path.endswith('same_var_names/module1/main.tf')
+ ]
+
+ # Check they point to 2 different modules
+ self.assertEqual(2, len(module_variable_edges))
+ self.assertNotEqual(local_graph.vertices[module_variable_edges[0].origin], local_graph.vertices[module_variable_edges[1].origin])
diff --git a/tests/terraform/graph/graph_builder/test_terraform_graph_parser.py b/tests/terraform/graph/graph_builder/test_terraform_graph_parser.py
new file mode 100644
index 0000000000..cd8b9efaf1
--- /dev/null
+++ b/tests/terraform/graph/graph_builder/test_terraform_graph_parser.py
@@ -0,0 +1,106 @@
+import os
+from unittest import TestCase
+
+from lark import Tree
+
+from checkov.terraform.parser import Parser
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestParser(TestCase):
+ def test_bool_parsing_avoid_remove_non_existing(self):
+ conf = {'test': ['Bool'], 'variable': ['aws:SecureTransport'], 'values': [['false']]}
+ config_parser = Parser()
+ actual = config_parser._clean_parser_types(conf)
+ expected = {'test': ['Bool'], 'variable': ['aws:SecureTransport'], 'values': [[False]]}
+ self.assertDictEqual(expected, actual)
+
+ def test_bool_parsing_sort_only_lists(self):
+ conf = {'enabled_metrics': [['a', 'c', 'b'], 'b', 'a', 'c']}
+ config_parser = Parser()
+ actual = config_parser._clean_parser_types(conf)
+ expected = {'enabled_metrics': [['a', 'b', 'c'], 'a', 'b', 'c']}
+ self.assertDictEqual(expected, actual)
+
+ def test_bool_parsing_sort_only_lists_with_bools(self):
+ conf = {'enabled_metrics': [['a', 'true', 'false'], 'b', 'true', 'false']}
+ config_parser = Parser()
+ actual = config_parser._clean_parser_types(conf)
+ expected = {'enabled_metrics': [[True, False, 'a'], True, False, 'b']}
+ self.assertDictEqual(expected, actual)
+
+ def test_set_parsing_to_list(self):
+ conf = {'enabled_metrics': [['a', 'true', 'false'], 'b', 'true', 'false'], 'example_set': [{'1', '2', '3'}]}
+ config_parser = Parser()
+ actual = config_parser._clean_parser_types(conf)
+ expected = {'enabled_metrics': [[True, False, 'a'], True, False, 'b'], 'example_set': [['1', '2', '3']]}
+ self.assertDictEqual(expected, actual)
+
+ def test_tree_parsing_to_str(self):
+ conf = {'enabled_metrics': [['a', 'true', 'false'], 'b', 'true', 'false'], 'example_set': Tree("data", ["child1", "child2"])}
+ config_parser = Parser()
+ actual = config_parser._clean_parser_types(conf)
+ expected = {'enabled_metrics': [[True, False, 'a'], True, False, 'b'], 'example_set': 'Tree(\'data\', [\'child1\', \'child2\'])'}
+ self.assertDictEqual(expected, actual)
+
+ def test_hcl_parsing_consistent_old_new(self):
+ cur_dir = os.path.dirname(os.path.realpath(__file__))
+ tf_dir = f'{cur_dir}/../resources/tf_parsing_comparison/tf_regular'
+ old_tf_dir = f'{cur_dir}/../resources/tf_parsing_comparison/tf_old'
+ _, _, tf_definitions = Parser().parse_hcl_module(tf_dir, 'AWS')
+ _, _, old_tf_definitions = Parser().parse_hcl_module(old_tf_dir, 'AWS')
+ self.assertDictEqual(tf_definitions[f'{tf_dir}/main.tf'], old_tf_definitions[f'{old_tf_dir}/main.tf'])
+
+ def test_hcl_parsing_old_booleans_correctness(self):
+ cur_dir = os.path.dirname(os.path.realpath(__file__))
+ tf_dir = f'{cur_dir}/../resources/tf_parsing_comparison/tf_regular'
+ _, _, tf_definitions = Parser().parse_hcl_module(tf_dir, 'AWS')
+ expected = [
+ {'aws_cloudtrail': {
+ 'tfer--cashdash_trail': {'enable_log_file_validation': [True], 'enable_logging': [True], 'include_global_service_events': [True],
+ 'is_multi_region_trail': [True], 'is_organization_trail': [False],
+ 'kms_key_id': ['arn:aws:kms:us-east-1:098885917934:key/5e7c4a79-bd63-42ca-9ae0-8f8e41f9c2f1'],
+ 'name': ['cashdash_trail'], 's3_bucket_name': ['cashdash-trail'],
+ 'sns_topic_name': ['arn:aws:sns:us-east-1:098885917934:clodtrail-sns-topic']}}},
+ {'google_compute_instance': {'tfer--sentry-002D-v1': {'attached_disk': [{'device_name': ['sentry'], 'mode': ['READ_WRITE'], 'source': [
+ 'https://www.googleapis.com/compute/v1/projects/be-base-wksp-v1/zones/us-west3-b/disks/sentry-data-v1']}], 'boot_disk': [
+ {'auto_delete': [True], 'device_name': ['persistent-disk-0'], 'initialize_params': [
+ {'image': ['https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-10-buster-v20200910'], 'size': ['10'],
+ 'type': ['pd-standard']}],
+ 'kms_key_self_link': ['projects/acme-project/locations/global/keyRings/global-v1/cryptoKeys/global-disk-key'], 'mode': ['READ_WRITE'],
+ 'source': ['https://www.googleapis.com/compute/v1/projects/acme-project/zones/us-west3-b/disks/sentry-v1']}],
+ 'can_ip_forward': [False], 'deletion_protection': [False], 'enable_display': [False],
+ 'machine_type': ['n1-standard-2'],
+ 'metadata': [{'block-project-ssh-keys': True, 'some-other-attribute': False}],
+ 'name': ['sentry-v1'], 'network_interface': [
+ {'access_config': [{'nat_ip': ['34.106.48.192'], 'network_tier': ['PREMIUM']}],
+ 'network': ['https://www.googleapis.com/compute/v1/projects/acme-project/global/networks/acme'], 'network_ip': ['10.40.0.53'],
+ 'subnetwork': ['https://www.googleapis.com/compute/v1/projects/acme-project/regions/us-west3/subnetworks/sentry'],
+ 'subnetwork_project': ['acme-project']}], 'project': ['acme-project'], 'scheduling': [
+ {'automatic_restart': [True], 'on_host_maintenance': ['MIGRATE'], 'preemptible': [False]}], 'service_account': [
+ {'email': ['sentry-vm@acme-project.iam.gserviceaccount.com'], 'scopes': [
+ ['https://www.googleapis.com/auth/devstorage.read_only', 'https://www.googleapis.com/auth/logging.write',
+ 'https://www.googleapis.com/auth/monitoring.write', 'https://www.googleapis.com/auth/userinfo.email']]}],
+ 'shielded_instance_config': [
+ {'enable_integrity_monitoring': [True], 'enable_secure_boot': [False],
+ 'enable_vtpm': [True]}], 'tags': [['allow-sentry', 'allow-ssh']],
+ 'zone': ['us-west3-b']}}}
+ ]
+ tf_definitions_resources = tf_definitions[f'{tf_dir}/main.tf']['resource']
+ for index in range(len(tf_definitions_resources)):
+ self.assertDictEqual(
+ tf_definitions_resources[index],
+ expected[index]
+ )
+
+ def test_hcl_parsing_sorting(self):
+ source_dir = os.path.realpath(os.path.join(TEST_DIRNAME,
+ '../resources/tf_parsing_comparison/modifications_diff'))
+ config_parser = Parser()
+ res = config_parser.parse_hcl_module(source_dir, 'AWS')
+ expected = ['https://www.googleapis.com/auth/devstorage.read_only', 'https://www.googleapis.com/auth/logging.write',
+ 'https://www.googleapis.com/auth/monitoring.write', 'https://www.googleapis.com/auth/service.management.readonly',
+ 'https://www.googleapis.com/auth/servicecontrol', 'https://www.googleapis.com/auth/trace.append']
+ result_resource = res[2][source_dir + '/main.tf']['resource'][0]['google_compute_instance']['tfer--test3']['service_account'][0]['scopes'][0]
+ self.assertListEqual(result_resource, expected)
diff --git a/tests/terraform/graph/resources/ec2_instance_network_interfaces/main.tf b/tests/terraform/graph/resources/ec2_instance_network_interfaces/main.tf
new file mode 100644
index 0000000000..6d1ad5dd27
--- /dev/null
+++ b/tests/terraform/graph/resources/ec2_instance_network_interfaces/main.tf
@@ -0,0 +1,74 @@
+resource "aws_vpc" "my_vpc" {
+ cidr_block = "172.16.0.0/16"
+
+ tags = {
+ Name = "tf-example"
+ Env = "prod"
+ }
+}
+
+resource "aws_subnet" "my_subnet" {
+ vpc_id = aws_vpc.my_vpc.id
+ cidr_block = "172.16.10.0/24"
+ availability_zone = "us-west-2a"
+
+ tags = {
+ Name = "tf-example"
+ Env = "prod"
+ }
+}
+
+resource "aws_network_interface" "network_interface_foo" {
+ subnet_id = aws_subnet.my_subnet.id
+ private_ips = ["172.16.10.100"]
+
+ tags = {
+ Name = "primary_network_interface"
+ Env = "prod"
+ }
+}
+
+resource "aws_network_interface" "network_interface_goo" {
+ subnet_id = aws_subnet.my_subnet.id
+ private_ips = ["172.16.10.100"]
+
+ tags = {
+ Name = "secondary_network_interface"
+ Env = "dev"
+ }
+}
+
+resource "aws_instance" "instance_foo" {
+ ami = "ami-005e54dee72cc1d00" # us-west-2
+ instance_type = "t2.micro"
+
+ network_interface {
+ network_interface_id = aws_network_interface.network_interface_foo.id
+ device_index = 0
+ }
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+}
+
+resource "aws_instance" "instance_bar" {
+ ami = "ami-005e54dee72cc1d00" # us-west-2
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ tags = {
+ Env = "prod"
+ }
+}
+
+resource "aws_vpc" "other_vpc" {
+ cidr_block = "124.16.0.0/16"
+
+ tags = {
+ Name = "not_connected"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/encryption/main.tf b/tests/terraform/graph/resources/encryption/main.tf
new file mode 100644
index 0000000000..83eebfe705
--- /dev/null
+++ b/tests/terraform/graph/resources/encryption/main.tf
@@ -0,0 +1,406 @@
+# Resource names in this file are **important**
+# Encrypted resources _must_ start their name with the word "encrypted"
+resource aws_ecr_repository "encrypted_repo" {
+ name = "nimtest-repo"
+ encryption_configuration {
+ encryption_type = "AES256"
+ }
+}
+
+resource aws_ecr_repository "unencrypted_repo" {
+ name = "nimtest-repo-unencrypted"
+}
+
+resource "aws_neptune_cluster" "encrypted_neptune" {
+ storage_encrypted = true
+ skip_final_snapshot = true
+}
+
+resource "aws_neptune_cluster" "unencrypted_neptune" {
+ storage_encrypted = false
+ skip_final_snapshot = true
+}
+
+resource "aws_efs_file_system" "encrypted_file_system" {
+ encrypted = true
+}
+
+resource "aws_efs_file_system" "unencrypted_file_system" {
+}
+
+resource "aws_ebs_volume" "encrypted_volume" {
+ availability_zone = "us-east-1a"
+ encrypted = true
+ size = 8
+}
+
+resource "aws_ebs_volume" "unencrypted_volume" {
+ availability_zone = "us-east-1a"
+ size = 8
+}
+
+resource "aws_ebs_volume" "unencrypted_volume2" {
+ availability_zone = "us-east-1a"
+ encrypted = false
+ size = 8
+}
+
+resource "aws_elasticache_replication_group" "encrypted_replication_group" {
+ replication_group_description = "nimtest replication group"
+ replication_group_id = "nimtest"
+ at_rest_encryption_enabled = true
+ cluster_mode {
+ num_node_groups = 0
+ replicas_per_node_group = 0
+ }
+}
+
+resource "aws_elasticache_replication_group" "unencrypted_replication_group" {
+ replication_group_description = "nimtest replication group"
+ replication_group_id = "nimtest"
+ cluster_mode {
+ num_node_groups = 0
+ replicas_per_node_group = 0
+ }
+}
+
+resource "aws_elasticsearch_domain" "encrypted_domain" {
+ domain_name = "nimtest-encryption-test"
+ encrypt_at_rest {
+ enabled = true
+ }
+ node_to_node_encryption {
+ enabled = true
+ }
+}
+
+resource "aws_elasticsearch_domain" "unencrypted_domain" {
+ domain_name = "nimtest-encryption-test"
+ node_to_node_encryption {
+ enabled = false
+ }
+}
+
+resource "aws_msk_cluster" "encrypted_msk" {
+ cluster_name = ""
+ kafka_version = ""
+ number_of_broker_nodes = 0
+ broker_node_group_info {
+ client_subnets = []
+ ebs_volume_size = 0
+ instance_type = ""
+ security_groups = []
+ }
+
+ encryption_info {
+ encryption_in_transit {
+ in_cluster = true
+ client_broker = "TLS"
+ }
+ encryption_at_rest_kms_key_arn = "KMS"
+ }
+}
+
+resource "aws_kinesis_stream" "encrypted_stream" {
+ name = "nimtest"
+ shard_count = 1
+ encryption_type = "KMS"
+ kms_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_kinesis_stream" "unencrypted_stream" {
+ name = "nimtest"
+ shard_count = 1
+}
+
+resource "aws_s3_bucket" "unencrypted_bucket" {
+ bucket = "unencrypted"
+}
+
+resource "aws_s3_bucket" "encrypted_bucket" {
+ bucket = "unencrypted"
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+}
+
+resource "aws_s3_bucket" "encrypted_bucket_2" {
+ bucket = "unencrypted"
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+ }
+}
+
+resource "aws_s3_bucket_object" "encrypted_object_by_itself" {
+ bucket = aws_s3_bucket.encrypted_bucket.bucket
+ key = "some-key.html"
+
+ server_side_encryption = "AES256"
+}
+
+resource "aws_s3_bucket_object" "unencrypted_object_by_bucket" {
+ bucket = aws_s3_bucket.encrypted_bucket.bucket
+ key = "some-key.html"
+}
+
+resource "aws_sns_topic" "encrypted_topic" {
+ name = "encrypted"
+ kms_master_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_sns_topic" "unencrypted_topic" {
+ name = "unencrypted"
+}
+
+resource "aws_sqs_queue" "encrypted_queue" {
+ name = "encrypted"
+ kms_master_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_sqs_queue" "unencrypted_queue" {
+ name = "unencrypted"
+}
+
+resource "aws_cloudwatch_log_group" "encrypted_by_default_cloudwatch_log_group" {
+ name = "group"
+}
+
+resource "aws_cloudwatch_log_group" "encrypted" {
+ name = "group"
+ kms_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_cloudtrail" "encrypted" {
+ name = "encrypted"
+ s3_bucket_name = aws_s3_bucket.encrypted_bucket.bucket
+ kms_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_cloudtrail" "unencrypted" {
+ name = "encrypted"
+ s3_bucket_name = aws_s3_bucket.encrypted_bucket.bucket
+}
+
+resource "aws_dynamodb_table" "encrypted" {
+ name = "encrypted"
+ hash_key = ""
+ attribute {
+ name = ""
+ type = ""
+ }
+ server_side_encryption {
+ enabled = true
+ }
+}
+
+resource "aws_dynamodb_table" "encrypted_by_default_dynamodb_table" {
+ name = "encrypted_by_default"
+ hash_key = ""
+ attribute {
+ name = ""
+ type = ""
+ }
+}
+
+resource "aws_iam_role" "role" {
+ assume_role_policy = ""
+}
+
+resource "aws_docdb_cluster" "encrypted_docdb" {
+ storage_encrypted = true
+ kms_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_docdb_cluster" "unencrypted_docdb" {
+ storage_encrypted = false
+}
+
+resource "aws_codebuild_project" "encrypted_project" {
+ name = "encrypted"
+ service_role = ""
+ artifacts {
+ type = ""
+ }
+ environment {
+ compute_type = ""
+ image = ""
+ type = ""
+ }
+ source {
+ type = ""
+ }
+
+ encryption_key = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_codebuild_project" "unencrypted_project" {
+ name = "unencrypted"
+ service_role = ""
+ artifacts {
+ type = ""
+ }
+ environment {
+ compute_type = ""
+ image = ""
+ type = ""
+ }
+ source {
+ type = ""
+ }
+}
+
+resource "aws_codebuild_report_group" "encrypted_report_group" {
+ export_config {
+ type = "S3"
+ s3_destination {
+ bucket = "some-bucket"
+ encryption_disabled = false
+ encryption_key = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+ packaging = "NONE"
+ path = "/some/path"
+ }
+ }
+}
+
+resource "aws_codebuild_report_group" "unencrypted_report_group" {
+ export_config {
+ type = "S3"
+ s3_destination {
+ bucket = "some-bucket"
+ encryption_disabled = true
+ packaging = "NONE"
+ path = "/some/path"
+ }
+ }
+}
+
+resource "aws_athena_database" "encrypted_athena_database" {
+ bucket = ""
+ name = "encrypted"
+ encryption_configuration {
+ encryption_option = "SSE_S3"
+ }
+}
+
+resource "aws_athena_database" "unencrypted_athena_database" {
+ bucket = ""
+ name = "unencrypted"
+}
+
+resource "aws_athena_workgroup" "encrypted_workgroup" {
+ name = "encrypted"
+ configuration {
+ result_configuration {
+ encryption_configuration {
+ encryption_option = "SSE_KMS"
+ kms_key_arn = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+ }
+ }
+ }
+}
+
+resource "aws_athena_workgroup" "unencrypted_workgroup" {
+ name = "unencrypted"
+}
+
+resource "aws_eks_cluster" "encrypted_eks" {
+ name = ""
+ role_arn = ""
+ vpc_config {
+ subnet_ids = []
+ }
+
+ encryption_config {
+ resources = []
+ provider {
+ key_arn = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+ }
+ }
+}
+
+resource "aws_db_instance" "encrypted_instance" {
+ instance_class = ""
+
+ storage_encrypted = true
+}
+
+resource "aws_db_instance" "unencrypted_instance" {
+ instance_class = ""
+
+ storage_encrypted = false
+}
+
+resource "aws_rds_cluster" "encrypted_rds_cluster" {
+ storage_encrypted = true
+ kms_key_id = "arn:aws:kms:us-east-1:000000000000:key/some-key-uuid"
+}
+
+resource "aws_rds_cluster" "unencrypted_rds_cluster" {
+}
+
+resource "aws_rds_global_cluster" "encrypted_global_rds" {
+ global_cluster_identifier = "some-id"
+ storage_encrypted = true
+}
+
+resource "aws_rds_global_cluster" "unencrypted_global_rds" {
+ global_cluster_identifier = "some-id"
+ storage_encrypted = false
+}
+
+resource "aws_s3_bucket_inventory" "encrypted_s3_inventory" {
+ bucket = ""
+ included_object_versions = ""
+ name = ""
+ destination {
+ bucket {
+ bucket_arn = ""
+ format = ""
+ encryption {
+ sse_s3 {}
+ }
+ }
+ }
+ schedule {
+ frequency = ""
+ }
+}
+
+resource "aws_dax_cluster" "encrypted_dax_cluster" {
+ cluster_name = "dax"
+ iam_role_arn = ""
+ node_type = ""
+ replication_factor = 0
+ server_side_encryption {
+ enabled = true
+ }
+}
+
+resource "aws_dax_cluster" "unencrypted_dax_cluster" {
+ cluster_name = "dax"
+ iam_role_arn = ""
+ node_type = ""
+ replication_factor = 0
+ server_side_encryption {
+ enabled = false
+ }
+}
+
+resource "aws_redshift_cluster" "encrypted_redshift_cluster" {
+ cluster_identifier = "redshift"
+ node_type = ""
+ encrypted = true
+}
+
+resource "aws_redshift_cluster" "unencrypted_redshift_cluster" {
+ cluster_identifier = "redshift"
+ node_type = ""
+}
diff --git a/tests/terraform/graph/resources/encryption_test/main.tf b/tests/terraform/graph/resources/encryption_test/main.tf
new file mode 100644
index 0000000000..18154d2dbe
--- /dev/null
+++ b/tests/terraform/graph/resources/encryption_test/main.tf
@@ -0,0 +1,33 @@
+resource "aws_rds_cluster" "rds_cluster_encrypted" {
+ cluster_identifier = "some-encrypted-id"
+ kms_key_id = "some-kms-key-id"
+}
+
+resource "aws_rds_cluster" "rds_cluster_unencrypted" {
+ cluster_identifier = "some-unencrypted-id"
+}
+
+resource "aws_s3_bucket" "encrypted_bucket" {
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+ }
+}
+
+resource "aws_s3_bucket" "unencrypted_bucket" {
+ versioning {
+ enabled = True
+ }
+}
+
+resource "aws_neptune_cluster" "encrypted_neptune" {
+ cluster_identifier = "encrypted-neptune"
+ storage_encrypted = true
+}
+
+resource "aws_neptune_cluster" "unencrypted_neptune" {
+ cluster_identifier = "unencrypted-neptune"
+}
diff --git a/tests/terraform/graph/resources/general_example/main.tf b/tests/terraform/graph/resources/general_example/main.tf
new file mode 100644
index 0000000000..a7ad6ff612
--- /dev/null
+++ b/tests/terraform/graph/resources/general_example/main.tf
@@ -0,0 +1,17 @@
+provider "aws" {
+ profile = var.aws_profile
+ region = "us-east-1"
+ alias = "east1"
+}
+
+locals {
+ bucket_name = var.bucket_name
+}
+
+resource "aws_s3_bucket" "template_bucket" {
+ provider = aws.east1
+ region = var.region
+ bucket = local.bucket_name
+ acl = "acl"
+ force_destroy = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/general_example/variables.tf b/tests/terraform/graph/resources/general_example/variables.tf
new file mode 100644
index 0000000000..dc51e32593
--- /dev/null
+++ b/tests/terraform/graph/resources/general_example/variables.tf
@@ -0,0 +1,15 @@
+variable "bucket_name" {
+ default = {
+ val = "MyBucket"
+ }
+
+}
+
+variable "region" {
+ default = "us-west-2"
+}
+
+variable "aws_profile" {
+ default = "default"
+}
+
diff --git a/tests/terraform/graph/resources/graph_files_test/more_vars.tf b/tests/terraform/graph/resources/graph_files_test/more_vars.tf
new file mode 100644
index 0000000000..8af81c1366
--- /dev/null
+++ b/tests/terraform/graph/resources/graph_files_test/more_vars.tf
@@ -0,0 +1,3 @@
+variable "encryption" {
+ default = "AES256"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/graph_files_test/pass_s3.tf b/tests/terraform/graph/resources/graph_files_test/pass_s3.tf
new file mode 100644
index 0000000000..5c010f66e6
--- /dev/null
+++ b/tests/terraform/graph/resources/graph_files_test/pass_s3.tf
@@ -0,0 +1,13 @@
+resource "aws_s3_bucket" "bucket_with_versioning" {
+ versioning {
+ enabled = var.versioning
+ }
+
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = var.encryption
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/graph_files_test/variables.tf b/tests/terraform/graph/resources/graph_files_test/variables.tf
new file mode 100644
index 0000000000..5bc9d4bf73
--- /dev/null
+++ b/tests/terraform/graph/resources/graph_files_test/variables.tf
@@ -0,0 +1,3 @@
+variable "versioning" {
+ default = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/k8_service/main.tf b/tests/terraform/graph/resources/k8_service/main.tf
new file mode 100644
index 0000000000..3af61cb699
--- /dev/null
+++ b/tests/terraform/graph/resources/k8_service/main.tf
@@ -0,0 +1,162 @@
+locals {
+ name = "bazel-remote-cache"
+ namespace = var.namespace
+ cache_directory = "/var/cache/bazel-remote-cache"
+
+ labels = {
+ "app.kubernetes.io/name" = local.name
+ "app.kubernetes.io/instance" = "web-server"
+ "app.kubernetes.io/version" = "1.0.0"
+ "app.kubernetes.io/part-of" = "foundation-infrastructure"
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+}
+
+resource "kubernetes_deployment" "bazel_remote_cache" {
+ metadata {
+ name = local.name
+ namespace = local.namespace
+ labels = local.labels
+
+ annotations = {
+ "reloader.stakater.com/auto" = "true"
+ }
+ }
+
+ spec {
+ replicas = var.replicas
+ revision_history_limit = 2
+
+ strategy {
+ type = "RollingUpdate"
+
+ rolling_update {
+ max_unavailable = 1
+ max_surge = 1
+ }
+ }
+
+ selector {
+ match_labels = local.labels
+ }
+
+ template {
+ metadata {
+ name = local.name
+ namespace = local.namespace
+ labels = local.labels
+
+ annotations = {
+ "iam.amazonaws.com/role" = var.iam_role
+ }
+ }
+
+ spec {
+ termination_grace_period_seconds = 10
+
+ container {
+ name = local.name
+ image = "776709147254.dkr.ecr.us-west-2.amazonaws.com/bazel-remote-cache@sha256:5c7691bf88ee95f6b50953ac58a75db89340fd6d2636c8ca88e785e1e02790fc"
+ image_pull_policy = "Always"
+
+ args = ["--config_file=/etc/config.yaml"]
+
+ port {
+ container_port = 8080
+ }
+
+ port {
+ container_port = 9092
+ }
+
+ volume_mount {
+ mount_path = "/etc/config.yaml"
+ name = "bazel-remote-cache-config"
+ sub_path = "config.yaml"
+ read_only = true
+ }
+
+ volume_mount {
+ name = "bazel-remote-cache-data"
+ mount_path = local.cache_directory
+ sub_path = "bazel-remote-cache-data"
+ read_only = false
+ }
+
+ resources {
+ requests {
+ memory = "4Gi"
+ cpu = "2"
+ }
+
+ limits {
+ memory = "4Gi"
+ cpu = "2"
+ }
+ }
+
+ liveness_probe {
+ http_get {
+ path = "/status"
+ port = 8080
+ }
+
+ period_seconds = 10
+ success_threshold = 1
+ failure_threshold = 2
+ initial_delay_seconds = 120
+ }
+
+ readiness_probe {
+ http_get {
+ path = "/status"
+ port = 8080
+ }
+
+ period_seconds = 10
+ success_threshold = 1
+ failure_threshold = 2
+ }
+ }
+
+ volume {
+ name = "bazel-remote-cache-data"
+ empty_dir {}
+ }
+
+ volume {
+ name = "bazel-remote-cache-config"
+ config_map {
+ name = local.name
+ }
+ }
+
+ affinity {
+ pod_anti_affinity {
+ preferred_during_scheduling_ignored_during_execution {
+ weight = 50
+
+ pod_affinity_term {
+ topology_key = "domain.beta.kubernetes.io/zone"
+
+ label_selector {
+ match_labels = local.labels
+ }
+ }
+ }
+
+ # Require pods to not schedule on the same node as to not use
+ # the same local storate space
+ required_during_scheduling_ignored_during_execution {
+ topology_key = "kubernetes.io/hostname"
+ label_selector {
+ match_labels = local.labels
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/terraform/graph/resources/k8_service/variables.tf b/tests/terraform/graph/resources/k8_service/variables.tf
new file mode 100644
index 0000000000..a0003adf6a
--- /dev/null
+++ b/tests/terraform/graph/resources/k8_service/variables.tf
@@ -0,0 +1,39 @@
+variable "fqdn" {
+ type = string
+ description = "FQDN of this instance of bazel-remote"
+}
+
+variable "elb_bucket" {
+ type = string
+ description = "S3 bucket to keep ELB access logs"
+}
+
+variable "s3_bucket" {
+ type = string
+ description = "The S3 bucket to be used as cache"
+}
+
+variable "iam_role" {
+ type = string
+ description = "The IAM role to assume to access the S3 bucket"
+}
+
+variable "cache_size" {
+ type = number
+ default = 30
+ description = "The amount of disk space to provision for caching"
+}
+
+variable "replicas" {
+ type = number
+ default = 8
+ description = "The amount of bazel cache replicas to provision"
+}
+
+variable "namespace_dependency_link" {
+ type = string
+}
+
+variable "namespace" {
+ type = string
+}
diff --git a/tests/terraform/graph/resources/lb/main.tf b/tests/terraform/graph/resources/lb/main.tf
new file mode 100644
index 0000000000..1ca824c08b
--- /dev/null
+++ b/tests/terraform/graph/resources/lb/main.tf
@@ -0,0 +1,41 @@
+resource "aws_lb" "lb_bad_1" {
+ name = "test-lb-tf-https-listener"
+ internal = false
+ load_balancer_type = "application"
+
+ enable_deletion_protection = true
+ tags = {
+ Environment = "production"
+ }
+}
+
+resource "aws_lb_listener" "listener_http_1" {
+ load_balancer_arn = aws_lb.lb_bad_1.arn
+ port = "80"
+ protocol = "HTTP"
+ ssl_policy = "ELBSecurityPolicy-2016-08"
+ certificate_arn = "arn:aws:iam::187416307283:server-certificate/test_cert_rab3wuqwgja25ct3n4jdj2tzu4"
+
+ default_action {
+ type = "redirect"
+ }
+}
+
+
+resource "aws_lb_listener" "listener_https_1" {
+ load_balancer_arn = aws_lb.lb_bad_1.arn
+ port = "443"
+ protocol = "HTTPS"
+ ssl_policy = "ELBSecurityPolicy-2016-08"
+ certificate_arn = "arn:aws:iam::187416307283:server-certificate/test_cert_rab3wuqwgja25ct3n4jdj2tzu4"
+
+ default_action {
+ type = "redirect"
+
+ redirect {
+ port = "443"
+ protocol = "HTTPS"
+ status_code = "HTTP_301"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/module_rendering/example/modules/mock/main.tf b/tests/terraform/graph/resources/module_rendering/example/modules/mock/main.tf
new file mode 100644
index 0000000000..3ad4f795de
--- /dev/null
+++ b/tests/terraform/graph/resources/module_rendering/example/modules/mock/main.tf
@@ -0,0 +1,7 @@
+resource "aws_s3_bucket" "some-bucket" {
+ bucket = "my-bucket"
+}
+
+output "o1" {
+ value = aws_s3_bucket.some-bucket.arn
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/module_rendering/example/modules/second-mock/main.tf b/tests/terraform/graph/resources/module_rendering/example/modules/second-mock/main.tf
new file mode 100644
index 0000000000..2d733ec75d
--- /dev/null
+++ b/tests/terraform/graph/resources/module_rendering/example/modules/second-mock/main.tf
@@ -0,0 +1,3 @@
+variable "input" {
+ type = string
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/module_rendering/example/stacks/s1/main.tf b/tests/terraform/graph/resources/module_rendering/example/stacks/s1/main.tf
new file mode 100644
index 0000000000..ead0217a1f
--- /dev/null
+++ b/tests/terraform/graph/resources/module_rendering/example/stacks/s1/main.tf
@@ -0,0 +1,8 @@
+module "mock" {
+ source = "../../modules/mock"
+}
+
+module "second-mock" {
+ source = "../../modules/second-mock"
+ input = module.mock.o1
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/module_rendering/example/stacks/s2/main.tf b/tests/terraform/graph/resources/module_rendering/example/stacks/s2/main.tf
new file mode 100644
index 0000000000..ead0217a1f
--- /dev/null
+++ b/tests/terraform/graph/resources/module_rendering/example/stacks/s2/main.tf
@@ -0,0 +1,8 @@
+module "mock" {
+ source = "../../modules/mock"
+}
+
+module "second-mock" {
+ source = "../../modules/second-mock"
+ input = module.mock.o1
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules-and-vars/context.tf b/tests/terraform/graph/resources/modules-and-vars/context.tf
new file mode 100644
index 0000000000..bae0cf1d93
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/context.tf
@@ -0,0 +1,167 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+ source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+
+ enabled = var.enabled
+ namespace = var.namespace
+ environment = var.environment
+ stage = var.stage
+ name = var.name
+ delimiter = var.delimiter
+ attributes = var.attributes
+ tags = var.tags
+ additional_tag_map = var.additional_tag_map
+ label_order = var.label_order
+ regex_replace_chars = var.regex_replace_chars
+ id_length_limit = var.id_length_limit
+
+ context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+ type = object({
+ enabled = bool
+ namespace = string
+ environment = string
+ stage = string
+ name = string
+ delimiter = string
+ attributes = list(string)
+ tags = map(string)
+ additional_tag_map = map(string)
+ regex_replace_chars = string
+ label_order = list(string)
+ id_length_limit = number
+ })
+ default = {
+ enabled = true
+ namespace = null
+ environment = null
+ stage = null
+ name = null
+ delimiter = null
+ attributes = []
+ tags = {}
+ additional_tag_map = {}
+ regex_replace_chars = null
+ label_order = []
+ id_length_limit = null
+ }
+ description = <<-EOT
+ Single object for setting entire context at once.
+ See description of individual variables for details.
+ Leave string and numeric variables as `null` to use default value.
+ Individual variable settings (non-null) override settings in context object,
+ except for attributes, tags, and additional_tag_map, which are merged.
+ EOT
+}
+
+variable "enabled" {
+ type = bool
+ default = null
+ description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+ type = string
+ default = null
+ description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
+}
+
+variable "environment" {
+ type = string
+ default = null
+ description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+ type = string
+ default = null
+ description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+ type = string
+ default = null
+ description = "Solution name, e.g. 'app' or 'jenkins'"
+}
+
+variable "delimiter" {
+ type = string
+ default = null
+ description = <<-EOT
+ Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
+ Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+ EOT
+}
+
+variable "attributes" {
+ type = list(string)
+ default = []
+ description = "Additional attributes (e.g. `1`)"
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+ description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
+}
+
+variable "additional_tag_map" {
+ type = map(string)
+ default = {}
+ description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
+}
+
+variable "label_order" {
+ type = list(string)
+ default = null
+ description = <<-EOT
+ The naming order of the id output and Name tag.
+ Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+ You can omit any of the 5 elements, but at least one must be present.
+ EOT
+}
+
+variable "regex_replace_chars" {
+ type = string
+ default = null
+ description = <<-EOT
+ Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
+ If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+ EOT
+}
+
+variable "id_length_limit" {
+ type = number
+ default = null
+ description = <<-EOT
+ Limit `id` to this many characters.
+ Set to `0` for unlimited length.
+ Set to `null` for default, which is `0`.
+ Does not affect `id_full`.
+ EOT
+}
+
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/tests/terraform/graph/resources/modules-and-vars/examples/complete/context.tf b/tests/terraform/graph/resources/modules-and-vars/examples/complete/context.tf
new file mode 100644
index 0000000000..bae0cf1d93
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/examples/complete/context.tf
@@ -0,0 +1,167 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+ source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+
+ enabled = var.enabled
+ namespace = var.namespace
+ environment = var.environment
+ stage = var.stage
+ name = var.name
+ delimiter = var.delimiter
+ attributes = var.attributes
+ tags = var.tags
+ additional_tag_map = var.additional_tag_map
+ label_order = var.label_order
+ regex_replace_chars = var.regex_replace_chars
+ id_length_limit = var.id_length_limit
+
+ context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+ type = object({
+ enabled = bool
+ namespace = string
+ environment = string
+ stage = string
+ name = string
+ delimiter = string
+ attributes = list(string)
+ tags = map(string)
+ additional_tag_map = map(string)
+ regex_replace_chars = string
+ label_order = list(string)
+ id_length_limit = number
+ })
+ default = {
+ enabled = true
+ namespace = null
+ environment = null
+ stage = null
+ name = null
+ delimiter = null
+ attributes = []
+ tags = {}
+ additional_tag_map = {}
+ regex_replace_chars = null
+ label_order = []
+ id_length_limit = null
+ }
+ description = <<-EOT
+ Single object for setting entire context at once.
+ See description of individual variables for details.
+ Leave string and numeric variables as `null` to use default value.
+ Individual variable settings (non-null) override settings in context object,
+ except for attributes, tags, and additional_tag_map, which are merged.
+ EOT
+}
+
+variable "enabled" {
+ type = bool
+ default = null
+ description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+ type = string
+ default = null
+ description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
+}
+
+variable "environment" {
+ type = string
+ default = null
+ description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+ type = string
+ default = null
+ description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+ type = string
+ default = null
+ description = "Solution name, e.g. 'app' or 'jenkins'"
+}
+
+variable "delimiter" {
+ type = string
+ default = null
+ description = <<-EOT
+ Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
+ Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+ EOT
+}
+
+variable "attributes" {
+ type = list(string)
+ default = []
+ description = "Additional attributes (e.g. `1`)"
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+ description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
+}
+
+variable "additional_tag_map" {
+ type = map(string)
+ default = {}
+ description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
+}
+
+variable "label_order" {
+ type = list(string)
+ default = null
+ description = <<-EOT
+ The naming order of the id output and Name tag.
+ Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+ You can omit any of the 5 elements, but at least one must be present.
+ EOT
+}
+
+variable "regex_replace_chars" {
+ type = string
+ default = null
+ description = <<-EOT
+ Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
+ If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+ EOT
+}
+
+variable "id_length_limit" {
+ type = number
+ default = null
+ description = <<-EOT
+ Limit `id` to this many characters.
+ Set to `0` for unlimited length.
+ Set to `null` for default, which is `0`.
+ Does not affect `id_full`.
+ EOT
+}
+
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/tests/terraform/graph/resources/modules-and-vars/examples/complete/main.tf b/tests/terraform/graph/resources/modules-and-vars/examples/complete/main.tf
new file mode 100644
index 0000000000..f632db6a4f
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/examples/complete/main.tf
@@ -0,0 +1,15 @@
+provider "aws" {
+ region = var.region
+}
+
+module "s3_bucket" {
+ source = "../.."
+
+ user_enabled = true
+ acl = var.acl
+ force_destroy = var.force_destroy
+ grants = var.grants
+ versioning_enabled = var.versioning_enabled
+ allow_encrypted_uploads_only = var.allow_encrypted_uploads_only
+ allowed_bucket_actions = var.allowed_bucket_actions
+}
diff --git a/tests/terraform/graph/resources/modules-and-vars/examples/complete/outputs.tf b/tests/terraform/graph/resources/modules-and-vars/examples/complete/outputs.tf
new file mode 100644
index 0000000000..ce76e56fa0
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/examples/complete/outputs.tf
@@ -0,0 +1,34 @@
+output "bucket_domain_name" {
+ value = module.s3_bucket.bucket_domain_name
+ description = "FQDN of bucket"
+}
+
+output "bucket_id" {
+ value = module.s3_bucket.bucket_id
+ description = "Bucket Name (aka ID)"
+}
+
+output "bucket_arn" {
+ value = module.s3_bucket.bucket_arn
+ description = "Bucket ARN"
+}
+
+output "bucket_region" {
+ value = module.s3_bucket.bucket_region
+ description = "Bucket region"
+}
+
+output "user_name" {
+ value = module.s3_bucket.user_name
+ description = "Normalized IAM user name"
+}
+
+output "user_arn" {
+ value = module.s3_bucket.user_arn
+ description = "The ARN assigned by AWS for the user"
+}
+
+output "user_unique_id" {
+ value = module.s3_bucket.user_unique_id
+ description = "The user unique ID assigned by AWS"
+}
diff --git a/tests/terraform/graph/resources/modules-and-vars/examples/complete/variables.tf b/tests/terraform/graph/resources/modules-and-vars/examples/complete/variables.tf
new file mode 100644
index 0000000000..1e3dfc9b3f
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/examples/complete/variables.tf
@@ -0,0 +1,174 @@
+variable "acl" {
+ type = string
+ default = "private"
+ description = "The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`."
+}
+
+variable "grants" {
+ type = list(object({
+ id = string
+ type = string
+ permissions = list(string)
+ uri = string
+ }))
+ default = null
+
+ description = "A list of ACL policy grants. Conflicts with `acl`. Set `acl` to `null` to use this."
+}
+
+variable "policy" {
+ type = string
+ default = ""
+ description = "A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy"
+}
+
+variable "region" {
+ type = string
+ default = ""
+ description = "If specified, the AWS region this bucket should reside in. Otherwise, the region used by the callee"
+}
+
+variable "force_destroy" {
+ type = bool
+ default = false
+ description = "A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable"
+}
+
+variable "versioning_enabled" {
+ type = bool
+ default = false
+ description = "A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket"
+}
+
+variable "sse_algorithm" {
+ type = string
+ default = "AES256"
+ description = "The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms`"
+}
+
+variable "kms_master_key_arn" {
+ type = string
+ default = ""
+ description = "The AWS KMS master key ARN used for the `SSE-KMS` encryption. This can only be used when you set the value of `sse_algorithm` as `aws:kms`. The default aws/s3 AWS KMS master key is used if this element is absent while the `sse_algorithm` is `aws:kms`"
+}
+
+variable "user_enabled" {
+ type = bool
+ default = false
+ description = "Set to `true` to create an IAM user with permission to access the bucket"
+}
+
+variable "allowed_bucket_actions" {
+ type = list(string)
+ default = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
+ description = "List of actions the user is permitted to perform on the S3 bucket"
+}
+
+variable "allow_encrypted_uploads_only" {
+ type = bool
+ default = false
+ description = "Set to `true` to prevent uploads of unencrypted objects to S3 bucket"
+}
+
+variable "lifecycle_rule_enabled" {
+ type = bool
+ default = false
+ description = "Enable or disable lifecycle rule"
+}
+
+variable "prefix" {
+ type = string
+ default = ""
+ description = "Prefix identifying one or more objects to which the rule applies"
+}
+
+variable "noncurrent_version_transition_days" {
+ type = number
+ default = 30
+ description = "Number of days to persist in the standard storage tier before moving to the glacier tier infrequent access tier"
+}
+
+variable "noncurrent_version_expiration_days" {
+ type = number
+ default = 90
+ description = "Specifies when noncurrent object versions expire"
+}
+
+variable "cors_rule_inputs" {
+ type = list(object({
+ allowed_headers = list(string)
+ allowed_methods = list(string)
+ allowed_origins = list(string)
+ expose_headers = list(string)
+ max_age_seconds = number
+ }))
+ default = null
+
+ description = "Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket"
+}
+
+variable "standard_transition_days" {
+ type = number
+ default = 30
+ description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier"
+}
+
+variable "glacier_transition_days" {
+ type = number
+ default = 60
+ description = "Number of days after which to move the data to the glacier storage tier"
+}
+
+variable "enable_glacier_transition" {
+ type = bool
+ default = true
+ description = "Enables the transition to AWS Glacier which can cause unnecessary costs for huge amount of small files"
+}
+
+variable "enable_standard_ia_transition" {
+ type = bool
+ default = false
+ description = "Enables the transition to STANDARD_IA"
+}
+
+variable "expiration_days" {
+ type = number
+ default = 90
+ description = "Number of days after which to expunge the objects"
+}
+
+variable "abort_incomplete_multipart_upload_days" {
+ type = number
+ default = 5
+ description = "Maximum time (in days) that you want to allow multipart uploads to remain in progress"
+}
+
+variable "lifecycle_tags" {
+ type = map(string)
+ description = "Tags filter. Used to manage object lifecycle events"
+ default = {}
+}
+
+variable "block_public_acls" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the blocking of new public access lists on the bucket"
+}
+
+variable "block_public_policy" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the blocking of new public policies on the bucket"
+}
+
+variable "ignore_public_acls" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the ignoring of public access lists on the bucket"
+}
+
+variable "restrict_public_buckets" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the restricting of making the bucket public"
+}
diff --git a/tests/terraform/graph/resources/modules-and-vars/main.tf b/tests/terraform/graph/resources/modules-and-vars/main.tf
new file mode 100644
index 0000000000..297407a08d
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/main.tf
@@ -0,0 +1,222 @@
+resource "aws_s3_bucket" "default" {
+ count = module.this.enabled ? 1 : 0
+ bucket = module.this.id
+ acl = try(length(var.grants), 0) == 0 ? var.acl : null
+ force_destroy = var.force_destroy
+ policy = var.policy
+ tags = module.this.tags
+
+ versioning {
+ enabled = var.versioning_enabled
+ }
+
+ lifecycle_rule {
+ id = module.this.id
+ enabled = var.lifecycle_rule_enabled
+ prefix = var.prefix
+ tags = var.lifecycle_tags
+ abort_incomplete_multipart_upload_days = var.abort_incomplete_multipart_upload_days
+
+ noncurrent_version_expiration {
+ days = var.noncurrent_version_expiration_days
+ }
+
+ dynamic "noncurrent_version_transition" {
+ for_each = var.enable_glacier_transition ? [1] : []
+
+ content {
+ days = var.noncurrent_version_transition_days
+ storage_class = "GLACIER"
+ }
+ }
+
+ dynamic "transition" {
+ for_each = var.enable_glacier_transition ? [1] : []
+
+ content {
+ days = var.glacier_transition_days
+ storage_class = "GLACIER"
+ }
+ }
+
+ dynamic "transition" {
+ for_each = var.enable_standard_ia_transition ? [1] : []
+
+ content {
+ days = var.standard_transition_days
+ storage_class = "STANDARD_IA"
+ }
+ }
+
+ expiration {
+ days = var.expiration_days
+ }
+ }
+
+ dynamic "logging" {
+ for_each = var.logging == null ? [] : [1]
+ content {
+ target_bucket = var.logging["bucket_name"]
+ target_prefix = var.logging["prefix"]
+ }
+ }
+
+ # https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html
+ # https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = var.sse_algorithm
+ kms_master_key_id = var.kms_master_key_arn
+ }
+ }
+ }
+
+ dynamic "cors_rule" {
+ for_each = var.cors_rule_inputs == null ? [] : var.cors_rule_inputs
+
+ content {
+ allowed_headers = cors_rule.value.allowed_headers
+ allowed_methods = cors_rule.value.allowed_methods
+ allowed_origins = cors_rule.value.allowed_origins
+ expose_headers = cors_rule.value.expose_headers
+ max_age_seconds = cors_rule.value.max_age_seconds
+ }
+ }
+
+ dynamic "grant" {
+ for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants
+
+ content {
+ id = grant.value.id
+ type = grant.value.type
+ permissions = grant.value.permissions
+ uri = grant.value.uri
+ }
+ }
+
+ dynamic "replication_configuration" {
+ for_each = var.s3_replication_enabled ? [1] : []
+
+ content {
+ role = aws_iam_role.replication[0].arn
+
+ dynamic "rules" {
+ for_each = var.replication_rules == null ? [] : var.replication_rules
+
+ content {
+ id = rules.value.id
+ priority = try(rules.value.priority, 0)
+ prefix = try(rules.value.prefix, null)
+ status = try(rules.value.status, null)
+
+ destination {
+ bucket = var.s3_replica_bucket_arn
+ storage_class = try(rules.value.destination.storage_class, "STANDARD")
+ replica_kms_key_id = try(rules.value.destination.replica_kms_key_id, null)
+ account_id = try(rules.value.destination.account_id, null)
+
+ dynamic "access_control_translation" {
+ for_each = try(rules.value.destination.access_control_translation.owner, null) == null ? [] : [rules.value.destination.access_control_translation.owner]
+
+ content {
+ owner = access_control_translation.value
+ }
+ }
+ }
+
+ dynamic "source_selection_criteria" {
+ for_each = try(rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled, null) == null ? [] : [rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled]
+
+ content {
+ sse_kms_encrypted_objects {
+ enabled = source_selection_criteria.value
+ }
+ }
+ }
+
+ dynamic "filter" {
+ for_each = try(rules.value.filter, null) == null ? [] : [rules.value.filter]
+
+ content {
+ prefix = try(filter.value.prefix, null)
+ tags = try(filter.value.tags, {})
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+module "s3_user" {
+ source = "git::https://github.com/cloudposse/terraform-aws-iam-s3-user.git?ref=tags/0.11.0"
+ enabled = module.this.enabled && var.user_enabled ? true : false
+ s3_actions = var.allowed_bucket_actions
+ s3_resources = ["${join("", aws_s3_bucket.default.*.arn)}/*", join("", aws_s3_bucket.default.*.arn)]
+
+ context = module.this.context
+}
+
+data "aws_partition" "current" {}
+
+data "aws_iam_policy_document" "bucket_policy" {
+ count = module.this.enabled && var.allow_encrypted_uploads_only ? 1 : 0
+
+ statement {
+ sid = "DenyIncorrectEncryptionHeader"
+ effect = "Deny"
+ actions = ["s3:PutObject"]
+ resources = ["arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}/*"]
+
+ principals {
+ identifiers = ["*"]
+ type = "*"
+ }
+
+ condition {
+ test = "StringNotEquals"
+ values = [var.sse_algorithm]
+ variable = "s3:x-amz-server-side-encryption"
+ }
+ }
+
+ statement {
+ sid = "DenyUnEncryptedObjectUploads"
+ effect = "Deny"
+ actions = ["s3:PutObject"]
+ resources = ["arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}/*"]
+
+ principals {
+ identifiers = ["*"]
+ type = "*"
+ }
+
+ condition {
+ test = "Null"
+ values = ["true"]
+ variable = "s3:x-amz-server-side-encryption"
+ }
+ }
+}
+
+resource "aws_s3_bucket_policy" "default" {
+ count = module.this.enabled && var.allow_encrypted_uploads_only ? 1 : 0
+ bucket = join("", aws_s3_bucket.default.*.id)
+ policy = join("", data.aws_iam_policy_document.bucket_policy.*.json)
+ depends_on = [aws_s3_bucket_public_access_block.default]
+}
+
+# Refer to the terraform documentation on s3_bucket_public_access_block at
+# https://www.terraform.io/docs/providers/aws/r/s3_bucket_public_access_block.html
+# for the nuances of the blocking options
+resource "aws_s3_bucket_public_access_block" "default" {
+ count = module.this.enabled ? 1 : 0
+ bucket = join("", aws_s3_bucket.default.*.id)
+
+ block_public_acls = var.block_public_acls
+ block_public_policy = var.block_public_policy
+ ignore_public_acls = var.ignore_public_acls
+ restrict_public_buckets = var.restrict_public_buckets
+}
+
diff --git a/tests/terraform/graph/resources/modules-and-vars/outputs.tf b/tests/terraform/graph/resources/modules-and-vars/outputs.tf
new file mode 100644
index 0000000000..f02f4207dd
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/outputs.tf
@@ -0,0 +1,61 @@
+output "bucket_domain_name" {
+ value = module.this.enabled ? join("", aws_s3_bucket.default.*.bucket_domain_name) : ""
+ description = "FQDN of bucket"
+}
+
+output "bucket_regional_domain_name" {
+ value = module.this.enabled ? join("", aws_s3_bucket.default.*.bucket_regional_domain_name) : ""
+ description = "The bucket region-specific domain name"
+}
+
+output "bucket_id" {
+ value = module.this.enabled ? join("", aws_s3_bucket.default.*.id) : ""
+ description = "Bucket Name (aka ID)"
+}
+
+output "bucket_arn" {
+ value = module.this.enabled ? join("", aws_s3_bucket.default.*.arn) : ""
+ description = "Bucket ARN"
+}
+
+output "bucket_region" {
+ value = module.this.enabled ? join("", aws_s3_bucket.default.*.region) : ""
+ description = "Bucket region"
+}
+
+output "enabled" {
+ value = module.this.enabled
+ description = "Is module enabled"
+}
+
+output "user_enabled" {
+ value = var.user_enabled
+ description = "Is user creation enabled"
+}
+
+output "user_name" {
+ value = module.s3_user.user_name
+ description = "Normalized IAM user name"
+}
+
+output "user_arn" {
+ value = module.s3_user.user_arn
+ description = "The ARN assigned by AWS for the user"
+}
+
+output "user_unique_id" {
+ value = module.s3_user.user_unique_id
+ description = "The user unique ID assigned by AWS"
+}
+
+output "access_key_id" {
+ sensitive = true
+ value = module.s3_user.access_key_id
+ description = "The access key ID"
+}
+
+output "secret_access_key" {
+ sensitive = true
+ value = module.s3_user.secret_access_key
+ description = "The secret access key. This will be written to the state file in plain-text"
+}
diff --git a/tests/terraform/graph/resources/modules-and-vars/replication.tf b/tests/terraform/graph/resources/modules-and-vars/replication.tf
new file mode 100644
index 0000000000..6a6c7024df
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/replication.tf
@@ -0,0 +1,66 @@
+resource "aws_iam_role" "replication" {
+ count = module.this.enabled && var.s3_replication_enabled ? 1 : 0
+
+ name = format("%s-replication", module.this.id)
+ assume_role_policy = data.aws_iam_policy_document.replication_sts[0].json
+}
+
+data "aws_iam_policy_document" "replication_sts" {
+ count = module.this.enabled && var.s3_replication_enabled ? 1 : 0
+
+ statement {
+ sid = "AllowPrimaryToAssumeServiceRole"
+ effect = "Allow"
+ actions = [
+ "sts:AssumeRole"
+ ]
+
+ principals {
+ type = "Service"
+ identifiers = ["s3.amazonaws.com"]
+ }
+ }
+}
+
+resource "aws_iam_policy" "replication" {
+ count = module.this.enabled && var.s3_replication_enabled ? 1 : 0
+
+ name = format("%s-replication", module.this.id)
+ policy = data.aws_iam_policy_document.replication[0].json
+}
+
+data "aws_iam_policy_document" "replication" {
+ count = module.this.enabled && var.s3_replication_enabled ? 1 : 0
+
+ statement {
+ sid = "AllowPrimaryToGetReplicationConfiguration"
+ effect = "Allow"
+ actions = [
+ "s3:Get*",
+ "s3:ListBucket"
+ ]
+ resources = [
+ aws_s3_bucket.default[0].arn,
+ "${aws_s3_bucket.default[0].arn}/*"
+ ]
+ }
+
+ statement {
+ sid = "AllowPrimaryToReplicate"
+ effect = "Allow"
+ actions = [
+ "s3:ReplicateObject",
+ "s3:ReplicateDelete",
+ "s3:ReplicateTags",
+ "s3:GetObjectVersionTagging"
+ ]
+
+ resources = ["${var.s3_replica_bucket_arn}/*"]
+ }
+}
+
+resource "aws_iam_role_policy_attachment" "replication" {
+ count = module.this.enabled && var.s3_replication_enabled ? 1 : 0
+ role = aws_iam_role.replication[0].name
+ policy_arn = aws_iam_policy.replication[0].arn
+}
diff --git a/tests/terraform/graph/resources/modules-and-vars/variables.tf b/tests/terraform/graph/resources/modules-and-vars/variables.tf
new file mode 100644
index 0000000000..17c624ec37
--- /dev/null
+++ b/tests/terraform/graph/resources/modules-and-vars/variables.tf
@@ -0,0 +1,220 @@
+variable "acl" {
+ type = string
+ default = "private"
+ description = "The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`."
+}
+
+variable "grants" {
+ type = list(object({
+ id = string
+ type = string
+ permissions = list(string)
+ uri = string
+ }))
+ default = null
+
+ description = "An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this."
+}
+
+variable "policy" {
+ type = string
+ default = ""
+ description = "A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy"
+}
+
+variable "force_destroy" {
+ type = bool
+ default = false
+ description = "A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable"
+}
+
+variable "versioning_enabled" {
+ type = bool
+ default = false
+ description = "A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket"
+}
+
+variable "logging" {
+ type = object({
+ bucket_name = string
+ prefix = string
+ })
+ default = null
+ description = "Bucket access logging configuration."
+}
+
+variable "sse_algorithm" {
+ type = string
+ default = "AES256"
+ description = "The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms`"
+}
+
+variable "kms_master_key_arn" {
+ type = string
+ default = ""
+ description = "The AWS KMS master key ARN used for the `SSE-KMS` encryption. This can only be used when you set the value of `sse_algorithm` as `aws:kms`. The default aws/s3 AWS KMS master key is used if this element is absent while the `sse_algorithm` is `aws:kms`"
+}
+
+variable "user_enabled" {
+ type = bool
+ default = false
+ description = "Set to `true` to create an IAM user with permission to access the bucket"
+}
+
+variable "allowed_bucket_actions" {
+ type = list(string)
+ default = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
+ description = "List of actions the user is permitted to perform on the S3 bucket"
+}
+
+variable "allow_encrypted_uploads_only" {
+ type = bool
+ default = false
+ description = "Set to `true` to prevent uploads of unencrypted objects to S3 bucket"
+}
+
+variable "lifecycle_rule_enabled" {
+ type = bool
+ default = false
+ description = "Enable or disable lifecycle rule"
+}
+
+variable "prefix" {
+ type = string
+ default = ""
+ description = "Prefix identifying one or more objects to which the rule applies"
+}
+
+variable "noncurrent_version_transition_days" {
+ type = number
+ default = 30
+ description = "Number of days to persist in the standard storage tier before moving to the glacier tier infrequent access tier"
+}
+
+variable "noncurrent_version_expiration_days" {
+ type = number
+ default = 90
+ description = "Specifies when noncurrent object versions expire"
+}
+
+variable "cors_rule_inputs" {
+ type = list(object({
+ allowed_headers = list(string)
+ allowed_methods = list(string)
+ allowed_origins = list(string)
+ expose_headers = list(string)
+ max_age_seconds = number
+ }))
+ default = null
+
+ description = "Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket"
+}
+
+variable "standard_transition_days" {
+ type = number
+ default = 30
+ description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier"
+}
+
+variable "glacier_transition_days" {
+ type = number
+ default = 60
+ description = "Number of days after which to move the data to the glacier storage tier"
+}
+
+variable "enable_glacier_transition" {
+ type = bool
+ default = true
+ description = "Enables the transition to AWS Glacier which can cause unnecessary costs for huge amount of small files"
+}
+
+variable "enable_standard_ia_transition" {
+ type = bool
+ default = false
+ description = "Enables the transition to STANDARD_IA"
+}
+
+variable "expiration_days" {
+ type = number
+ default = 90
+ description = "Number of days after which to expunge the objects"
+}
+
+variable "abort_incomplete_multipart_upload_days" {
+ type = number
+ default = 5
+ description = "Maximum time (in days) that you want to allow multipart uploads to remain in progress"
+}
+
+variable "lifecycle_tags" {
+ type = map(string)
+ description = "Tags filter. Used to manage object lifecycle events"
+ default = {}
+}
+
+variable "block_public_acls" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the blocking of new public access lists on the bucket"
+}
+
+variable "block_public_policy" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the blocking of new public policies on the bucket"
+}
+
+variable "ignore_public_acls" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the ignoring of public access lists on the bucket"
+}
+
+variable "restrict_public_buckets" {
+ type = bool
+ default = true
+ description = "Set to `false` to disable the restricting of making the bucket public"
+}
+
+variable "s3_replication_enabled" {
+ type = bool
+ default = false
+ description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`."
+}
+
+variable "s3_replica_bucket_arn" {
+ type = string
+ default = ""
+ description = "The ARN of the S3 replica bucket (destination)"
+}
+
+variable "replication_rules" {
+ # type = list(object({
+ # id = string
+ # priority = number
+ # prefix = string
+ # status = string
+ # destination = object({
+ # storage_class = string
+ # replica_kms_key_id = string
+ # access_control_translation = object({
+ # owner = string
+ # })
+ # account_id = string
+ # })
+ # source_selection_criteria = object({
+ # sse_kms_encrypted_objects = object({
+ # enabled = bool
+ # })
+ # })
+ # filter = object({
+ # prefix = string
+ # tags = map(string)
+ # })
+ # }))
+
+ type = list(any)
+ default = null
+ description = "Specifies the replication rules if S3 bucket replication is enabled"
+}
+
diff --git a/tests/terraform/graph/resources/modules/git_module/main.tf b/tests/terraform/graph/resources/modules/git_module/main.tf
new file mode 100644
index 0000000000..c48e665d4f
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/git_module/main.tf
@@ -0,0 +1,12 @@
+module "security_group" {
+ source = "git::https://github.com/terraform-aws-modules/terraform-aws-security-group.git"
+ version = "~> 3.0"
+
+ name = "example"
+ description = "Security group for example usage with EC2 instance"
+ vpc_id = data.aws_vpc.default.id
+
+ ingress_cidr_blocks = ["0.0.0.0/0"]
+ ingress_rules = ["http-80-tcp", "all-icmp"]
+ egress_rules = ["all-all"]
+}
diff --git a/tests/terraform/graph/resources/modules/linked_modules/expected_graph.png b/tests/terraform/graph/resources/modules/linked_modules/expected_graph.png
new file mode 100644
index 0000000000..fb899ef83e
Binary files /dev/null and b/tests/terraform/graph/resources/modules/linked_modules/expected_graph.png differ
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/main.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/main.tf
new file mode 100644
index 0000000000..a8540c1ac2
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/main.tf
@@ -0,0 +1,8 @@
+resource "aws_lambda_function" "this" {
+ count = 0
+
+ function_name = "lambda_function_name"
+ role = ""
+ handler = ""
+ runtime = ""
+}
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/outputs.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/outputs.tf
new file mode 100644
index 0000000000..da9f15f21d
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/lambda/outputs.tf
@@ -0,0 +1,9 @@
+output "this_lambda_function_arn" {
+ description = "The ARN of the Lambda Function"
+ value = element(concat(aws_lambda_function.this.*.arn, [""]), 0)
+}
+
+output "this_lambda_function_name" {
+ description = "The name of the Lambda Function"
+ value = element(concat(aws_lambda_function.this.*.function_name, [""]), 0)
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/examples/notification/main.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/examples/notification/main.tf
new file mode 100644
index 0000000000..2ba343ce82
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/examples/notification/main.tf
@@ -0,0 +1,52 @@
+locals {
+ bucket_name = "s3-bucket-${random_pet.this.id}"
+}
+
+resource "random_pet" "this" {
+ length = 2
+}
+
+module "s3_bucket" {
+ source = "../../"
+
+ bucket = local.bucket_name
+ force_destroy = true
+}
+
+#############################################
+# Using packaged function from Lambda module
+#############################################
+
+locals {
+ package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip"
+ downloaded = "downloaded_package_${md5(local.package_url)}.zip"
+}
+
+resource "null_resource" "download_package" {
+ triggers = {
+ downloaded = local.downloaded
+ }
+
+ provisioner "local-exec" {
+ command = "curl -L -o ${local.downloaded} ${local.package_url}"
+ }
+}
+
+data "null_data_source" "downloaded_package" {
+ inputs = {
+ id = null_resource.download_package.id
+ filename = local.downloaded
+ }
+}
+
+module "lambda_function1" {
+ source = "terraform-aws-modules/lambda/"
+ version = "~> 1.0"
+
+ function_name = "${random_pet.this.id}-lambda1"
+ handler = "index.lambda_handler"
+ runtime = "python3.8"
+
+ create_package = false
+ local_existing_package = data.null_data_source.downloaded_package.outputs["filename"]
+}
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/main.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/main.tf
new file mode 100644
index 0000000000..a80d3c8789
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/main.tf
@@ -0,0 +1,15 @@
+resource "aws_s3_bucket" "this" {
+ count = 1
+
+ bucket = var.bucket
+ acl = ""
+ tags = {}
+ force_destroy = false
+}
+
+resource "aws_s3_bucket_policy" "this" {
+ count = var.create_bucket && (var.attach_elb_log_delivery_policy || var.attach_policy) ? 1 : 0
+
+ bucket = aws_s3_bucket.this[0].id
+ policy = var.attach_elb_log_delivery_policy ? {} : var.policy
+}
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/outputs.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/outputs.tf
new file mode 100644
index 0000000000..97df0600e6
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/outputs.tf
@@ -0,0 +1,4 @@
+output "this_s3_bucket_id" {
+ description = "The name of the bucket."
+ value = element(concat(aws_s3_bucket_policy.this.*.id, aws_s3_bucket.this.*.id, list("")), 0)
+}
diff --git a/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/variables.tf b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/variables.tf
new file mode 100644
index 0000000000..88d890d095
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/linked_modules/external_modules/terraform-aws-modules/s3-bucket/variables.tf
@@ -0,0 +1,29 @@
+variable "create_bucket" {
+ description = "Controls if S3 bucket should be created"
+ type = bool
+ default = true
+}
+
+variable "attach_elb_log_delivery_policy" {
+ description = "Controls if S3 bucket should have ELB log delivery policy attached"
+ type = bool
+ default = false
+}
+
+variable "attach_policy" {
+ description = "Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy)"
+ type = bool
+ default = false
+}
+
+variable "bucket" {
+ description = "(Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name."
+ type = string
+ default = null
+}
+
+variable "policy" {
+ description = "(Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide."
+ type = string
+ default = null
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/registry_security_group_inner_module/main.tf b/tests/terraform/graph/resources/modules/registry_security_group_inner_module/main.tf
new file mode 100644
index 0000000000..47ecb4eec9
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/registry_security_group_inner_module/main.tf
@@ -0,0 +1,14 @@
+module "web_server_sg" {
+ source = "terraform-aws-modules/security-group/aws//modules/http-80"
+
+ name = "web-server"
+ description = "Security group for web-server with HTTP ports open within VPC"
+ vpc_id = "vpc-12345678"
+
+ ingress_cidr_blocks = ["10.10.0.0/16"]
+}
+
+resource "aws_flow_log" "related_flow_log" {
+ traffic_type = ""
+ vpc_id = module.web_server_sg.security_group_vpc_id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/registry_security_group_inner_module/test_case_dependencies.png b/tests/terraform/graph/resources/modules/registry_security_group_inner_module/test_case_dependencies.png
new file mode 100644
index 0000000000..71caf898ba
Binary files /dev/null and b/tests/terraform/graph/resources/modules/registry_security_group_inner_module/test_case_dependencies.png differ
diff --git a/tests/terraform/graph/resources/modules/s3_inner_modules/inner/main.tf b/tests/terraform/graph/resources/modules/s3_inner_modules/inner/main.tf
new file mode 100644
index 0000000000..b2874cf65d
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/s3_inner_modules/inner/main.tf
@@ -0,0 +1,7 @@
+resource "aws_s3_bucket" "inner_s3" {
+ bucket = "tf-test-bucket-destination-12345"
+ acl = ""
+ versioning {
+ enabled = var.versioning
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/s3_inner_modules/inner/variables.tf b/tests/terraform/graph/resources/modules/s3_inner_modules/inner/variables.tf
new file mode 100644
index 0000000000..2f8cd9a1d5
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/s3_inner_modules/inner/variables.tf
@@ -0,0 +1,3 @@
+variable "versioning" {
+ default = false
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/s3_inner_modules/main.tf b/tests/terraform/graph/resources/modules/s3_inner_modules/main.tf
new file mode 100644
index 0000000000..d110da5ef3
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/s3_inner_modules/main.tf
@@ -0,0 +1,4 @@
+module "inner_module_call" {
+ source = "./inner"
+ versioning = var.versioning
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/s3_inner_modules/variables.tf b/tests/terraform/graph/resources/modules/s3_inner_modules/variables.tf
new file mode 100644
index 0000000000..2f8cd9a1d5
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/s3_inner_modules/variables.tf
@@ -0,0 +1,3 @@
+variable "versioning" {
+ default = false
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/main.tf b/tests/terraform/graph/resources/modules/same_var_names/main.tf
new file mode 100644
index 0000000000..a97baaaac4
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/main.tf
@@ -0,0 +1,13 @@
+variable "v" {
+ default = true
+}
+
+module "module1" {
+ source = "./module1"
+ v = var.v
+}
+
+module "module2" {
+ source = "./module1"
+ v = var.v
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/module1/main.tf b/tests/terraform/graph/resources/modules/same_var_names/module1/main.tf
new file mode 100644
index 0000000000..9e704d029d
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/module1/main.tf
@@ -0,0 +1,9 @@
+module "submodule1" {
+ source = "../submodule1"
+ v = var.v
+}
+
+module "submodule2" {
+ source = "../submodule2"
+ v = var.v
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/module1/variables.tf b/tests/terraform/graph/resources/modules/same_var_names/module1/variables.tf
new file mode 100644
index 0000000000..cee6504e30
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/module1/variables.tf
@@ -0,0 +1,2 @@
+variable "v" {
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/module2/main.tf b/tests/terraform/graph/resources/modules/same_var_names/module2/main.tf
new file mode 100644
index 0000000000..9e704d029d
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/module2/main.tf
@@ -0,0 +1,9 @@
+module "submodule1" {
+ source = "../submodule1"
+ v = var.v
+}
+
+module "submodule2" {
+ source = "../submodule2"
+ v = var.v
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/module2/variables.tf b/tests/terraform/graph/resources/modules/same_var_names/module2/variables.tf
new file mode 100644
index 0000000000..cee6504e30
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/module2/variables.tf
@@ -0,0 +1,2 @@
+variable "v" {
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/submodule1/variables.tf b/tests/terraform/graph/resources/modules/same_var_names/submodule1/variables.tf
new file mode 100644
index 0000000000..54204f614f
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/submodule1/variables.tf
@@ -0,0 +1,3 @@
+variable "v" {
+ type = bool
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/same_var_names/submodule2/variables.tf b/tests/terraform/graph/resources/modules/same_var_names/submodule2/variables.tf
new file mode 100644
index 0000000000..54204f614f
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/same_var_names/submodule2/variables.tf
@@ -0,0 +1,3 @@
+variable "v" {
+ type = bool
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/stacks/prod/main.tf b/tests/terraform/graph/resources/modules/stacks/prod/main.tf
new file mode 100644
index 0000000000..31ad9a2681
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/stacks/prod/main.tf
@@ -0,0 +1,3 @@
+module "sub-module" {
+ source = "./sub-prod"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/stacks/prod/sub-prod/main.tf b/tests/terraform/graph/resources/modules/stacks/prod/sub-prod/main.tf
new file mode 100644
index 0000000000..c1578ff49e
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/stacks/prod/sub-prod/main.tf
@@ -0,0 +1,4 @@
+module "s3" {
+ source = "../../../s3_inner_modules"
+ versioning = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/stacks/stage/main.tf b/tests/terraform/graph/resources/modules/stacks/stage/main.tf
new file mode 100644
index 0000000000..faa17b4999
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/stacks/stage/main.tf
@@ -0,0 +1,4 @@
+module "s3" {
+ source = "../../s3_inner_modules"
+ versioning = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/stacks/test/main.tf b/tests/terraform/graph/resources/modules/stacks/test/main.tf
new file mode 100644
index 0000000000..c09f097a9b
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/stacks/test/main.tf
@@ -0,0 +1,4 @@
+module "s3" {
+ source = "../../s3_inner_modules"
+ versioning = false
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/violation_example/main.tf b/tests/terraform/graph/resources/modules/violation_example/main.tf
new file mode 100644
index 0000000000..0a392fa549
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/violation_example/main.tf
@@ -0,0 +1,13 @@
+module "learn_terraform" {
+ source = "https://github.com/hashicorp/learn-terraform-provision-eks-cluster.git"
+ version = "1.16.0"
+
+ name = "s3-bucket"
+}
+
+resource "aws_s3_bucket" "destination" {
+ bucket = "tf-test-bucket-destination-12345"
+ versioning {
+ enabled = var.enabled
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/modules/violation_example/variables.tf b/tests/terraform/graph/resources/modules/violation_example/variables.tf
new file mode 100644
index 0000000000..3fcca4a4d5
--- /dev/null
+++ b/tests/terraform/graph/resources/modules/violation_example/variables.tf
@@ -0,0 +1,3 @@
+variable "enabled" {
+ default = module.learn_terraform.region == "something to produce false" ? true : false
+}
diff --git a/tests/terraform/graph/resources/output_example/main.tf b/tests/terraform/graph/resources/output_example/main.tf
new file mode 100644
index 0000000000..27517bc5ac
--- /dev/null
+++ b/tests/terraform/graph/resources/output_example/main.tf
@@ -0,0 +1,13 @@
+module "submodule" {
+ source = "submodule"
+}
+
+resource "aws_subnet" "my_subnet" {
+ vpc_id = module.submodule.vpc_id
+ cidr_block = "172.16.10.0/24"
+ availability_zone = "us-west-2a"
+
+ tags = {
+ Name = "tf-example"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/output_example/submodule/main.tf b/tests/terraform/graph/resources/output_example/submodule/main.tf
new file mode 100644
index 0000000000..41fe5eef9e
--- /dev/null
+++ b/tests/terraform/graph/resources/output_example/submodule/main.tf
@@ -0,0 +1,7 @@
+resource "aws_vpc" "my_vpc" {
+ cidr_block = "172.16.0.0/16"
+
+ tags = {
+ Name = "tf-example"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/output_example/submodule/outputs.tf b/tests/terraform/graph/resources/output_example/submodule/outputs.tf
new file mode 100644
index 0000000000..9f03256804
--- /dev/null
+++ b/tests/terraform/graph/resources/output_example/submodule/outputs.tf
@@ -0,0 +1,3 @@
+output "vpc_id" {
+ value = aws_vpc.my_vpc.id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/public_security_groups/main.tf b/tests/terraform/graph/resources/public_security_groups/main.tf
new file mode 100644
index 0000000000..d8f3a8f85f
--- /dev/null
+++ b/tests/terraform/graph/resources/public_security_groups/main.tf
@@ -0,0 +1,71 @@
+resource "aws_vpc" "my_vpc" {
+ cidr_block = "172.16.0.0/16"
+
+ tags = {
+ Name = "tf-example"
+ }
+}
+
+resource "aws_security_group" "aws_security_group_public" {
+ vpc_id = aws_vpc.my_vpc.id
+
+ ingress {
+ cidr_blocks = ["0.0.0.0/0"]
+ from_port = 0
+ protocol = ""
+ to_port = 0
+ }
+}
+
+resource "aws_security_group" "aws_security_group_private" {
+ vpc_id = aws_vpc.my_vpc.id
+
+ ingress {
+ cidr_blocks = ["25.09.19.92/0"]
+ from_port = 0
+ protocol = ""
+ to_port = 0
+ }
+}
+
+resource "aws_db_security_group" "aws_db_security_group_public" {
+ name = "rds_sg"
+
+ ingress {
+ cidr = "0.0.0.0"
+ }
+}
+
+resource "aws_db_security_group" "aws_db_security_group_private" {
+ name = "rds_sg"
+
+ ingress {
+ cidr = "10.0.0.0/24"
+ }
+}
+
+resource "aws_redshift_security_group" "aws_redshift_security_group_public" {
+ name = "redshift-sg"
+
+ ingress {
+ cidr = "0.0.0.0"
+ }
+}
+
+resource "aws_redshift_security_group" "aws_redshift_security_group_private" {
+ name = "redshift-sg"
+
+ ingress {
+ cidr = "25.09.19.92/0"
+ }
+}
+
+resource "aws_elasticache_security_group" "aws_elasticache_security_group_public" {
+ name = "elasticache-security-group"
+ security_group_names = [aws_security_group.aws_security_group_public.name]
+}
+
+resource "aws_elasticache_security_group" "aws_elasticache_security_group_private" {
+ name = "elasticache-security-group"
+ security_group_names = [aws_security_group.aws_security_group_private.name]
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/public_security_groups/output.tf b/tests/terraform/graph/resources/public_security_groups/output.tf
new file mode 100644
index 0000000000..b40725b273
--- /dev/null
+++ b/tests/terraform/graph/resources/public_security_groups/output.tf
@@ -0,0 +1,32 @@
+output "aws_security_group_public" {
+ value = aws_security_group.aws_security_group_public.id
+}
+
+output "aws_security_group_private" {
+ value = aws_security_group.aws_security_group_private.id
+}
+
+output "aws_db_security_group_public" {
+ value = aws_db_security_group.aws_db_security_group_public.id
+}
+
+output "aws_db_security_group_private" {
+ value = aws_db_security_group.aws_db_security_group_private.id
+}
+
+output "aws_redshift_security_group_public" {
+ value = aws_redshift_security_group.aws_redshift_security_group_public.id
+}
+
+
+output "aws_redshift_security_group_private" {
+ value = aws_redshift_security_group.aws_redshift_security_group_private.id
+}
+
+output "aws_elasticache_security_group_public" {
+ value = aws_elasticache_security_group.aws_elasticache_security_group_public.id
+}
+
+output "aws_elasticache_security_group_private" {
+ value = aws_elasticache_security_group.aws_elasticache_security_group_private.id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/public_virtual_machines/main.tf b/tests/terraform/graph/resources/public_virtual_machines/main.tf
new file mode 100644
index 0000000000..91aa9eda12
--- /dev/null
+++ b/tests/terraform/graph/resources/public_virtual_machines/main.tf
@@ -0,0 +1,142 @@
+resource "aws_vpc" "my_vpc" {
+ cidr_block = "172.16.0.0/16"
+
+ tags = {
+ Name = "tf-example"
+ }
+}
+
+resource "aws_subnet" "subnet_public_ip" {
+ vpc_id = aws_vpc.my_vpc.id
+ cidr_block = "172.16.10.0/24"
+ availability_zone = "us-west-2a"
+ map_public_ip_on_launch = true
+
+ tags = {
+ Name = "first-tf-example"
+ }
+}
+
+resource "aws_subnet" "subnet_not_public_ip" {
+ vpc_id = aws_vpc.my_vpc.id
+ cidr_block = "172.16.10.0/24"
+ availability_zone = "us-west-2a"
+
+ tags = {
+ Name = "second-tf-example"
+ }
+}
+
+
+resource "aws_default_security_group" "default_security_group_open" {
+ vpc_id = aws_vpc.my_vpc.id
+
+ ingress {
+ protocol = -1
+ self = true
+ from_port = 0
+ to_port = 0
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_default_security_group" "default_security_group_closed" {
+ vpc_id = aws_vpc.my_vpc.id
+
+ ingress {
+ protocol = -1
+ self = true
+ from_port = 0
+ to_port = 0
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_instance" "with_open_def_security_groups" {
+ ami = "ami-0"
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ security_groups = [aws_default_security_group.default_security_group_open.id]
+}
+
+resource "aws_instance" "with_closed_def_security_groups" {
+ ami = "ami-1"
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ security_groups = [aws_default_security_group.default_security_group_closed.id]
+}
+
+
+resource "aws_instance" "with_open_security_groups" {
+ ami = "ami-2"
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ vpc_security_group_ids = [aws_security_group.allow_tls.id]
+}
+
+resource "aws_security_group" "allow_tls" {
+ name = "allow_tls"
+ description = "Allow TLS inbound traffic"
+ vpc_id = aws_vpc.my_vpc.id
+
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+
+ tags = {
+ Name = "allow_tls"
+ }
+}
+
+
+resource "aws_instance" "with_subnet_public" {
+ ami = "ami-3"
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ subnet_id = aws_subnet.subnet_public_ip.id
+}
+
+resource "aws_instance" "with_subnet_not_public" {
+ ami = "ami-4"
+ instance_type = "t2.micro"
+
+ credit_specification {
+ cpu_credits = "unlimited"
+ }
+
+ subnet_id = aws_subnet.subnet_not_public_ip.id
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/s3_bucket/main.tf b/tests/terraform/graph/resources/s3_bucket/main.tf
new file mode 100644
index 0000000000..0f4601cef0
--- /dev/null
+++ b/tests/terraform/graph/resources/s3_bucket/main.tf
@@ -0,0 +1,7 @@
+resource "aws_s3_bucket" "destination" {
+ bucket = "tf-test-bucket-destination-12345"
+ acl = var.acl
+ versioning {
+ enabled = var.is_enabled
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/s3_bucket/variables.tf b/tests/terraform/graph/resources/s3_bucket/variables.tf
new file mode 100644
index 0000000000..53734b2c58
--- /dev/null
+++ b/tests/terraform/graph/resources/s3_bucket/variables.tf
@@ -0,0 +1,7 @@
+variable "is_enabled" {
+ default = "True"
+}
+
+variable "acl" {
+ default = "public-read"
+}
diff --git a/tests/terraform/graph/resources/s3_bucket_2/main.tf b/tests/terraform/graph/resources/s3_bucket_2/main.tf
new file mode 100644
index 0000000000..a5a3b61278
--- /dev/null
+++ b/tests/terraform/graph/resources/s3_bucket_2/main.tf
@@ -0,0 +1,24 @@
+resource "aws_s3_bucket" "private" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Dev"
+ }
+}
+
+resource "aws_s3_bucket" "public" {
+ bucket = "my-tf-test-bucket"
+ acl = "public"
+
+ tags = {
+ Name = "My other bucket"
+ Environment = "Prod"
+ }
+}
+
+resource "aws_s3_bucket" "non_tag" {
+ bucket = "no-tags"
+ acl = "public"
+}
diff --git a/tests/terraform/graph/resources/security_group_list_cidr_blocks/main.tf b/tests/terraform/graph/resources/security_group_list_cidr_blocks/main.tf
new file mode 100644
index 0000000000..6485020ed0
--- /dev/null
+++ b/tests/terraform/graph/resources/security_group_list_cidr_blocks/main.tf
@@ -0,0 +1,21 @@
+resource "aws_security_group" "failed_cidr_blocks" {
+ name = "friendly_subnets"
+ description = "Allows access from friendly subnets"
+ ingress {
+ from_port = 0
+ to_port = 0
+ protocol = -1
+ cidr_blocks = ["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
+ }
+}
+
+resource "aws_security_group" "passed_cidr_block" {
+ name = "friendly_subnets"
+ description = "Allows access from friendly subnets"
+ ingress {
+ from_port = 0
+ to_port = 0
+ protocol = -1
+ cidr_blocks = ["10.2.1.0/24", "10.2.2.0/24", "10.2.3.0/24"]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/security_group_multiple_rules/main.tf b/tests/terraform/graph/resources/security_group_multiple_rules/main.tf
new file mode 100644
index 0000000000..51af3a0565
--- /dev/null
+++ b/tests/terraform/graph/resources/security_group_multiple_rules/main.tf
@@ -0,0 +1,79 @@
+resource "aws_security_group" "sg1" {
+ description = "sg1"
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0", "25.0.9.19/92"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "8182"
+ }
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "5432"
+ }
+}
+
+resource "aws_security_group" "sg2" {
+ description = "sg2"
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "1234"
+ }
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0", "8.0.4.19/92"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "5432"
+ }
+}
diff --git a/tests/terraform/graph/resources/security_group_multiple_rules2/main.tf b/tests/terraform/graph/resources/security_group_multiple_rules2/main.tf
new file mode 100644
index 0000000000..51af3a0565
--- /dev/null
+++ b/tests/terraform/graph/resources/security_group_multiple_rules2/main.tf
@@ -0,0 +1,79 @@
+resource "aws_security_group" "sg1" {
+ description = "sg1"
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0", "25.0.9.19/92"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "8182"
+ }
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "5432"
+ }
+}
+
+resource "aws_security_group" "sg2" {
+ description = "sg2"
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "1234"
+ }
+
+ egress {
+ description = "Self Reference"
+ cidr_blocks = ["0.0.0.0/0", "8.0.4.19/92"]
+ from_port = "0"
+ protocol = "-1"
+ self = "false"
+ to_port = "0"
+ }
+
+ ingress {
+ description = "Access to Bastion Host Security Group"
+ from_port = "5432"
+ protocol = "tcp"
+ security_groups = ["sg-id-0"]
+ self = "false"
+ to_port = "5432"
+ }
+}
diff --git a/tests/terraform/graph/resources/tag_includes/main.tf b/tests/terraform/graph/resources/tag_includes/main.tf
new file mode 100644
index 0000000000..59d85d641a
--- /dev/null
+++ b/tests/terraform/graph/resources/tag_includes/main.tf
@@ -0,0 +1,23 @@
+resource "aws_instance" "some_instance" {
+ ami = "some_ami"
+ instance_type = "t3.nano"
+ tags = {
+ Name = "acme-machine"
+ }
+}
+
+resource "aws_subnet" "acme_subnet" {
+ cidr_block = ""
+ vpc_id = ""
+
+ tags = {
+ acme = "true"
+ }
+}
+
+resource "aws_s3_bucket" "acme_s3_bucket" {
+ bucket = "acme-123456"
+ tags = {
+ Environment = "dev"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/tf_parsing_comparison/modifications_diff/main.tf b/tests/terraform/graph/resources/tf_parsing_comparison/modifications_diff/main.tf
new file mode 100644
index 0000000000..77c3fa2818
--- /dev/null
+++ b/tests/terraform/graph/resources/tf_parsing_comparison/modifications_diff/main.tf
@@ -0,0 +1,39 @@
+resource "google_compute_instance" "tfer--test3" {
+ boot_disk {
+ auto_delete = "true"
+ device_name = "test3"
+ mode = "READ_WRITE"
+ source = "https://www.googleapis.com/compute/v1/projects/disco-sector-283918/zones/us-central1-a/disks/test3"
+ }
+ can_ip_forward = "false"
+ deletion_protection = "false"
+ enable_display = "false"
+ machine_type = "e2-medium"
+ name = "test3"
+ network_interface {
+ access_config {
+ nat_ip = "34.122.7.28"
+ network_tier = "PREMIUM"
+ }
+ network = "https://www.googleapis.com/compute/v1/projects/disco-sector-283918/global/networks/default"
+ network_ip = "10.128.0.4"
+ subnetwork = "https://www.googleapis.com/compute/v1/projects/disco-sector-283918/regions/us-central1/subnetworks/default"
+ subnetwork_project = "disco-sector-283918"
+ }
+ project = "disco-sector-283918"
+ scheduling {
+ automatic_restart = "true"
+ on_host_maintenance = "MIGRATE"
+ preemptible = "false"
+ }
+ service_account {
+ email = "630155383092-compute@developer.gserviceaccount.com"
+ scopes = ["https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/trace.append", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/logging.write"]
+ }
+ shielded_instance_config {
+ enable_integrity_monitoring = "true"
+ enable_secure_boot = "false"
+ enable_vtpm = "true"
+ }
+ zone = "us-central1-a"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/tf_parsing_comparison/tf_old/main.tf b/tests/terraform/graph/resources/tf_parsing_comparison/tf_old/main.tf
new file mode 100644
index 0000000000..3c263d3037
--- /dev/null
+++ b/tests/terraform/graph/resources/tf_parsing_comparison/tf_old/main.tf
@@ -0,0 +1,67 @@
+resource "aws_cloudtrail" "tfer--cashdash_trail" {
+ enable_log_file_validation = "true"
+ enable_logging = "true"
+ include_global_service_events = "true"
+ is_multi_region_trail = "true"
+ is_organization_trail = "false"
+ kms_key_id = "arn:aws:kms:us-east-1:098885917934:key/5e7c4a79-bd63-42ca-9ae0-8f8e41f9c2f1"
+ name = "cashdash_trail"
+ s3_bucket_name = "cashdash-trail"
+ sns_topic_name = "arn:aws:sns:us-east-1:098885917934:clodtrail-sns-topic"
+}
+
+resource "google_compute_instance" "tfer--sentry-002D-v1" {
+ attached_disk {
+ device_name = "sentry"
+ mode = "READ_WRITE"
+ source = "https://www.googleapis.com/compute/v1/projects/be-base-wksp-v1/zones/us-west3-b/disks/sentry-data-v1"
+ }
+ boot_disk {
+ auto_delete = "true"
+ device_name = "persistent-disk-0"
+ initialize_params {
+ image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-10-buster-v20200910"
+ size = "10"
+ type = "pd-standard"
+ }
+ kms_key_self_link = "projects/acme-project/locations/global/keyRings/global-v1/cryptoKeys/global-disk-key"
+ mode = "READ_WRITE"
+ source = "https://www.googleapis.com/compute/v1/projects/acme-project/zones/us-west3-b/disks/sentry-v1"
+ }
+ can_ip_forward = "false"
+ deletion_protection = "false"
+ enable_display = "false"
+ machine_type = "n1-standard-2"
+ metadata = {
+ block-project-ssh-keys = "true"
+ some-other-attribute = "false"
+ }
+ name = "sentry-v1"
+ network_interface {
+ access_config {
+ nat_ip = "34.106.48.192"
+ network_tier = "PREMIUM"
+ }
+ network = "https://www.googleapis.com/compute/v1/projects/acme-project/global/networks/acme"
+ network_ip = "10.40.0.53"
+ subnetwork = "https://www.googleapis.com/compute/v1/projects/acme-project/regions/us-west3/subnetworks/sentry"
+ subnetwork_project = "acme-project"
+ }
+ project = "acme-project"
+ scheduling {
+ automatic_restart = "true"
+ on_host_maintenance = "MIGRATE"
+ preemptible = "false"
+ }
+ service_account {
+ email = "sentry-vm@acme-project.iam.gserviceaccount.com"
+ scopes = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/devstorage.read_only"]
+ }
+ shielded_instance_config {
+ enable_integrity_monitoring = "true"
+ enable_secure_boot = "false"
+ enable_vtpm = "true"
+ }
+ tags = ["allow-ssh", "allow-sentry"]
+ zone = "us-west3-b"
+}
diff --git a/tests/terraform/graph/resources/tf_parsing_comparison/tf_regular/main.tf b/tests/terraform/graph/resources/tf_parsing_comparison/tf_regular/main.tf
new file mode 100644
index 0000000000..ca567b9cf6
--- /dev/null
+++ b/tests/terraform/graph/resources/tf_parsing_comparison/tf_regular/main.tf
@@ -0,0 +1,67 @@
+resource "aws_cloudtrail" "tfer--cashdash_trail" {
+ enable_log_file_validation = true
+ enable_logging = true
+ include_global_service_events = true
+ is_multi_region_trail = true
+ is_organization_trail = false
+ kms_key_id = "arn:aws:kms:us-east-1:098885917934:key/5e7c4a79-bd63-42ca-9ae0-8f8e41f9c2f1"
+ name = "cashdash_trail"
+ s3_bucket_name = "cashdash-trail"
+ sns_topic_name = "arn:aws:sns:us-east-1:098885917934:clodtrail-sns-topic"
+}
+
+resource "google_compute_instance" "tfer--sentry-002D-v1" {
+ attached_disk {
+ device_name = "sentry"
+ mode = "READ_WRITE"
+ source = "https://www.googleapis.com/compute/v1/projects/be-base-wksp-v1/zones/us-west3-b/disks/sentry-data-v1"
+ }
+ boot_disk {
+ auto_delete = "true"
+ device_name = "persistent-disk-0"
+ initialize_params {
+ image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-10-buster-v20200910"
+ size = "10"
+ type = "pd-standard"
+ }
+ kms_key_self_link = "projects/acme-project/locations/global/keyRings/global-v1/cryptoKeys/global-disk-key"
+ mode = "READ_WRITE"
+ source = "https://www.googleapis.com/compute/v1/projects/acme-project/zones/us-west3-b/disks/sentry-v1"
+ }
+ can_ip_forward = "false"
+ deletion_protection = "false"
+ enable_display = "false"
+ machine_type = "n1-standard-2"
+ metadata = {
+ block-project-ssh-keys = "true"
+ some-other-attribute = "false"
+ }
+ name = "sentry-v1"
+ network_interface {
+ access_config {
+ nat_ip = "34.106.48.192"
+ network_tier = "PREMIUM"
+ }
+ network = "https://www.googleapis.com/compute/v1/projects/acme-project/global/networks/acme"
+ network_ip = "10.40.0.53"
+ subnetwork = "https://www.googleapis.com/compute/v1/projects/acme-project/regions/us-west3/subnetworks/sentry"
+ subnetwork_project = "acme-project"
+ }
+ project = "acme-project"
+ scheduling {
+ automatic_restart = "true"
+ on_host_maintenance = "MIGRATE"
+ preemptible = "false"
+ }
+ service_account {
+ email = "sentry-vm@acme-project.iam.gserviceaccount.com"
+ scopes = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/devstorage.read_only"]
+ }
+ shielded_instance_config {
+ enable_integrity_monitoring = "true"
+ enable_secure_boot = "false"
+ enable_vtpm = "true"
+ }
+ tags = ["allow-ssh", "allow-sentry"]
+ zone = "us-west3-b"
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_complex_keys/main.tf b/tests/terraform/graph/resources/variable_rendering/render_complex_keys/main.tf
new file mode 100644
index 0000000000..9f79562279
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_complex_keys/main.tf
@@ -0,0 +1,11 @@
+locals {
+ name = "test_local_name"
+ namespace = "test_namespace"
+
+ labels = {
+ "app.kubernetes.io/name" = local.name
+ "app.kubernetes.io/instance" = "hpa"
+ "app.kubernetes.io/version" = "1.0.0"
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/main.tf b/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/main.tf
new file mode 100644
index 0000000000..baef0cb7ff
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/main.tf
@@ -0,0 +1,10 @@
+resource "aws_s3_bucket" "default" {
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = var.sse_algorithm
+ kms_master_key_id = var.kms_master_key_arn
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/variables.tf
new file mode 100644
index 0000000000..cf4983a715
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_deep_nesting/variables.tf
@@ -0,0 +1,7 @@
+variable "sse_algorithm" {
+ default = "AES256"
+}
+
+variable "kms_master_key_arn" {
+ default = ""
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/main.tf b/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/main.tf
new file mode 100644
index 0000000000..a09f048170
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/main.tf
@@ -0,0 +1,13 @@
+variable "aws" {
+ type = object({ access_key = string, secret_key = string, region = string })
+}
+
+variable "vcs_repo" {
+ type = object({ identifier = string, branch = string, oauth_token = string })
+}
+
+provider "aws" {
+ access_key = var.aws.access_key
+ secret_key = var.aws.secret_key
+ region = var.aws.region
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/terraform.tfvars b/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/terraform.tfvars
new file mode 100644
index 0000000000..2de660ee92
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_dictionary_tfvars/terraform.tfvars
@@ -0,0 +1,11 @@
+aws = {
+ access_key = "AKIAVAN"
+ secret_key = "0CU4jk0"
+ region = "us-west-2"
+}
+
+vcs_repo = {
+ branch = "master"
+ identifier = "DTherHtun/deploy-infra"
+ oauth_token = "885efa"
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/main.tf b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/main.tf
new file mode 100644
index 0000000000..1690cf90fe
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/main.tf
@@ -0,0 +1,7 @@
+module "sg2" {
+ source = "modules\/security_group"
+
+ port = 22
+ cidrs = ["0.0.0.0/0"]
+
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/main.tf b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/main.tf
new file mode 100644
index 0000000000..cd245b1690
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/main.tf
@@ -0,0 +1,24 @@
+resource "aws_security_group" "sg" {
+ name = "sg"
+ description = "Allow TLS inbound traffic"
+ vpc_id = "vpc-123"
+
+ ingress {
+ description = "TLS from VPC"
+ from_port = var.port
+ to_port = var.port
+ protocol = "tcp"
+ cidr_blocks = var.cidrs
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ tags = {
+ Name = "allow_ssh"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/variables.tf
new file mode 100644
index 0000000000..911cbf5514
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_from_module_def_sg/modules/security_group/variables.tf
@@ -0,0 +1,12 @@
+
+variable "port" {
+
+}
+
+variable "cidrs" {
+
+}
+
+variable "test" {
+ default = 0
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/main.tf b/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/main.tf
new file mode 100644
index 0000000000..bf72510f97
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/main.tf
@@ -0,0 +1,34 @@
+module "vpc" {
+ source = "./vpc"
+ version = "2.47.0"
+
+ name = "test-vpc"
+ cidr = "172.16.0.0/16"
+ private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"]
+ public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"]
+ enable_nat_gateway = true
+ single_nat_gateway = true
+ enable_dns_hostnames = true
+
+ public_subnet_tags = {
+ "kubernetes.io/cluster/${local.cluster_name}" = "shared"
+ "kubernetes.io/role/elb" = "1"
+ }
+
+ private_subnet_tags = {
+ "kubernetes.io/cluster/${local.cluster_name}" = "shared"
+ "kubernetes.io/role/internal-elb" = "1"
+ }
+}
+
+locals {
+ cluster_name = "test-eks-${random_string.suffix.result}"
+}
+
+data "aws_availability_zones" "available" {
+}
+
+resource "random_string" "suffix" {
+ length = 8
+ special = false
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/vpc/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/vpc/variables.tf
new file mode 100644
index 0000000000..e063fe2cb1
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_from_module_vpc/vpc/variables.tf
@@ -0,0 +1,1210 @@
+variable "create_vpc" {
+ description = "Controls if VPC should be created (it affects almost all resources)"
+ type = bool
+ default = true
+}
+
+variable "name" {
+ description = "Name to be used on all the resources as identifier"
+ default = ""
+}
+
+variable "cidr" {
+ description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overridden"
+ default = "0.0.0.0/0"
+}
+
+variable "assign_generated_ipv6_cidr_block" {
+ description = "Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block"
+ type = bool
+ default = false
+}
+
+variable "secondary_cidr_blocks" {
+ description = "List of secondary CIDR blocks to associate with the VPC to extend the IP Address pool"
+ type = list(string)
+ default = []
+}
+
+variable "instance_tenancy" {
+ description = "A tenancy option for instances launched into the VPC"
+ type = string
+ default = "default"
+}
+
+variable "public_subnet_suffix" {
+ description = "Suffix to append to public subnets name"
+ type = string
+ default = "public"
+}
+
+variable "private_subnet_suffix" {
+ description = "Suffix to append to private subnets name"
+ type = string
+ default = "private"
+}
+
+variable "intra_subnet_suffix" {
+ description = "Suffix to append to intra subnets name"
+ type = string
+ default = "intra"
+}
+
+variable "database_subnet_suffix" {
+ description = "Suffix to append to database subnets name"
+ type = string
+ default = "db"
+}
+
+variable "redshift_subnet_suffix" {
+ description = "Suffix to append to redshift subnets name"
+ type = string
+ default = "redshift"
+}
+
+variable "elasticache_subnet_suffix" {
+ description = "Suffix to append to elasticache subnets name"
+ type = string
+ default = "elasticache"
+}
+
+variable "public_subnets" {
+ description = "A list of public subnets inside the VPC"
+ type = list(string)
+ default = []
+}
+
+variable "private_subnets" {
+ description = "A list of private subnets inside the VPC"
+ type = list(string)
+ default = []
+}
+
+variable "database_subnets" {
+ description = "A list of database subnets"
+ type = list(string)
+ default = []
+}
+
+variable "redshift_subnets" {
+ description = "A list of redshift subnets"
+ type = list(string)
+ default = []
+}
+
+variable "elasticache_subnets" {
+ description = "A list of elasticache subnets"
+ type = list(string)
+ default = []
+}
+
+variable "intra_subnets" {
+ description = "A list of intra subnets"
+ type = list(string)
+ default = []
+}
+
+variable "create_database_subnet_route_table" {
+ description = "Controls if separate route table for database should be created"
+ type = bool
+ default = false
+}
+
+variable "create_redshift_subnet_route_table" {
+ description = "Controls if separate route table for redshift should be created"
+ type = bool
+ default = false
+}
+
+variable "enable_public_redshift" {
+ description = "Controls if redshift should have public routing table"
+ type = bool
+ default = false
+}
+
+variable "create_elasticache_subnet_route_table" {
+ description = "Controls if separate route table for elasticache should be created"
+ type = bool
+ default = false
+}
+
+variable "create_database_subnet_group" {
+ description = "Controls if database subnet group should be created"
+ type = bool
+ default = true
+}
+
+variable "create_elasticache_subnet_group" {
+ description = "Controls if elasticache subnet group should be created"
+ type = bool
+ default = true
+}
+
+variable "create_redshift_subnet_group" {
+ description = "Controls if redshift subnet group should be created"
+ type = bool
+ default = true
+}
+
+variable "create_database_internet_gateway_route" {
+ description = "Controls if an internet gateway route for public database access should be created"
+ type = bool
+ default = false
+}
+
+variable "create_database_nat_gateway_route" {
+ description = "Controls if a nat gateway route should be created to give internet access to the database subnets"
+ type = bool
+ default = false
+}
+
+variable "azs" {
+ description = "A list of availability zones in the region"
+ type = list(string)
+ default = []
+}
+
+variable "enable_dns_hostnames" {
+ description = "Should be true to enable DNS hostnames in the VPC"
+ type = bool
+ default = false
+}
+
+variable "enable_dns_support" {
+ description = "Should be true to enable DNS support in the VPC"
+ type = bool
+ default = true
+}
+
+variable "enable_nat_gateway" {
+ description = "Should be true if you want to provision NAT Gateways for each of your private networks"
+ type = bool
+ default = false
+}
+
+variable "single_nat_gateway" {
+ description = "Should be true if you want to provision a single shared NAT Gateway across all of your private networks"
+ type = bool
+ default = false
+}
+
+variable "one_nat_gateway_per_az" {
+ description = "Should be true if you want only one NAT Gateway per availability zone. Requires `var.azs` to be set, and the number of `public_subnets` created to be greater than or equal to the number of availability zones specified in `var.azs`."
+ type = bool
+ default = false
+}
+
+variable "reuse_nat_ips" {
+ description = "Should be true if you don't want EIPs to be created for your NAT Gateways and will instead pass them in via the 'external_nat_ip_ids' variable"
+ type = bool
+ default = false
+}
+
+variable "external_nat_ip_ids" {
+ description = "List of EIP IDs to be assigned to the NAT Gateways (used in combination with reuse_nat_ips)"
+ type = list(string)
+ default = []
+}
+
+variable "enable_dynamodb_endpoint" {
+ description = "Should be true if you want to provision a DynamoDB endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "enable_s3_endpoint" {
+ description = "Should be true if you want to provision an S3 endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "enable_sqs_endpoint" {
+ description = "Should be true if you want to provision an SQS endpoint to the VPC"
+ default = false
+}
+
+variable "sqs_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for SQS endpoint"
+ default = []
+}
+
+variable "sqs_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for SQS endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ default = []
+}
+
+variable "sqs_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for SQS endpoint"
+ default = false
+}
+
+variable "enable_ssm_endpoint" {
+ description = "Should be true if you want to provision an SSM endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ssm_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for SSM endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ssm_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for SSM endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ssm_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for SSM endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_ssmmessages_endpoint" {
+ description = "Should be true if you want to provision a SSMMESSAGES endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "enable_apigw_endpoint" {
+ description = "Should be true if you want to provision an api gateway endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "apigw_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for API GW endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "apigw_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for API GW endpoint"
+ type = bool
+ default = false
+}
+
+variable "apigw_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for API GW endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ssmmessages_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for SSMMESSAGES endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ssmmessages_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for SSMMESSAGES endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ssmmessages_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for SSMMESSAGES endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_ec2_endpoint" {
+ description = "Should be true if you want to provision an EC2 endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ec2_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for EC2 endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ec2_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for EC2 endpoint"
+ type = bool
+ default = false
+}
+
+variable "ec2_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for EC2 endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "enable_ec2messages_endpoint" {
+ description = "Should be true if you want to provision an EC2MESSAGES endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ec2messages_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for EC2MESSAGES endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ec2messages_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for EC2MESSAGES endpoint"
+ type = bool
+ default = false
+}
+
+variable "ec2messages_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for EC2MESSAGES endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "enable_ecr_api_endpoint" {
+ description = "Should be true if you want to provision an ecr api endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ecr_api_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for ECR api endpoint. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ecr_api_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for ECR API endpoint"
+ type = bool
+ default = false
+}
+
+variable "ecr_api_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for ECR API endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "enable_ecr_dkr_endpoint" {
+ description = "Should be true if you want to provision an ecr dkr endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ecr_dkr_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for ECR dkr endpoint. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ecr_dkr_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for ECR DKR endpoint"
+ type = bool
+ default = false
+}
+
+variable "ecr_dkr_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for ECR DKR endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "enable_kms_endpoint" {
+ description = "Should be true if you want to provision a KMS endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "kms_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for KMS endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "kms_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for KMS endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "kms_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for KMS endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_ecs_endpoint" {
+ description = "Should be true if you want to provision a ECS endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ecs_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for ECS endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ecs_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for ECS endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ecs_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for ECS endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_ecs_agent_endpoint" {
+ description = "Should be true if you want to provision a ECS Agent endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ecs_agent_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for ECS Agent endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ecs_agent_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for ECS Agent endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ecs_agent_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for ECS Agent endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_ecs_telemetry_endpoint" {
+ description = "Should be true if you want to provision a ECS Telemetry endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "ecs_telemetry_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for ECS Telemetry endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "ecs_telemetry_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for ECS Telemetry endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "ecs_telemetry_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for ECS Telemetry endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_sns_endpoint" {
+ description = "Should be true if you want to provision a SNS endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "sns_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for SNS endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "sns_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for SNS endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "sns_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for SNS endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_monitoring_endpoint" {
+ description = "Should be true if you want to provision a CloudWatch Monitoring endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "monitoring_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for CloudWatch Monitoring endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "monitoring_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for CloudWatch Monitoring endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "monitoring_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for CloudWatch Monitoring endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_elasticloadbalancing_endpoint" {
+ description = "Should be true if you want to provision a Elastic Load Balancing endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "elasticloadbalancing_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for Elastic Load Balancing endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "elasticloadbalancing_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for Elastic Load Balancing endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "elasticloadbalancing_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for Elastic Load Balancing endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_events_endpoint" {
+ description = "Should be true if you want to provision a CloudWatch Events endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "events_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for CloudWatch Events endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "events_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for CloudWatch Events endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "events_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for CloudWatch Events endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_logs_endpoint" {
+ description = "Should be true if you want to provision a CloudWatch Logs endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "logs_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for CloudWatch Logs endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "logs_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for CloudWatch Logs endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "logs_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for CloudWatch Logs endpoint"
+ type = bool
+ default = false
+}
+
+variable "enable_cloudtrail_endpoint" {
+ description = "Should be true if you want to provision a CloudTrail endpoint to the VPC"
+ type = bool
+ default = false
+}
+
+variable "cloudtrail_endpoint_security_group_ids" {
+ description = "The ID of one or more security groups to associate with the network interface for CloudTrail endpoint"
+ type = list(string)
+ default = []
+}
+
+variable "cloudtrail_endpoint_subnet_ids" {
+ description = "The ID of one or more subnets in which to create a network interface for CloudTrail endpoint. Only a single subnet within an AZ is supported. If omitted, private subnets will be used."
+ type = list(string)
+ default = []
+}
+
+variable "cloudtrail_endpoint_private_dns_enabled" {
+ description = "Whether or not to associate a private hosted zone with the specified VPC for CloudTrail endpoint"
+ type = bool
+ default = false
+}
+
+variable "map_public_ip_on_launch" {
+ description = "Should be false if you do not want to auto-assign public IP on launch"
+ type = bool
+ default = true
+}
+
+variable "enable_vpn_gateway" {
+ description = "Should be true if you want to create a new VPN Gateway resource and attach it to the VPC"
+ type = bool
+ default = false
+}
+
+variable "vpn_gateway_id" {
+ description = "ID of VPN Gateway to attach to the VPC"
+ default = ""
+}
+
+variable "amazon_side_asn" {
+ description = "The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the virtual private gateway is created with the current default Amazon ASN."
+ default = "64512"
+}
+
+variable "propagate_private_route_tables_vgw" {
+ description = "Should be true if you want route table propagation"
+ type = bool
+ default = false
+}
+
+variable "propagate_public_route_tables_vgw" {
+ description = "Should be true if you want route table propagation"
+ type = bool
+ default = false
+}
+
+variable "tags" {
+ description = "A map of tags to add to all resources"
+ type = map(string)
+ default = {}
+}
+
+variable "vpc_tags" {
+ description = "Additional tags for the VPC"
+ type = map(string)
+ default = {}
+}
+
+variable "igw_tags" {
+ description = "Additional tags for the internet gateway"
+ type = map(string)
+ default = {}
+}
+
+variable "public_subnet_tags" {
+ description = "Additional tags for the public subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "private_subnet_tags" {
+ description = "Additional tags for the private subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "public_route_table_tags" {
+ description = "Additional tags for the public route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "private_route_table_tags" {
+ description = "Additional tags for the private route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "database_route_table_tags" {
+ description = "Additional tags for the database route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "redshift_route_table_tags" {
+ description = "Additional tags for the redshift route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "elasticache_route_table_tags" {
+ description = "Additional tags for the elasticache route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "intra_route_table_tags" {
+ description = "Additional tags for the intra route tables"
+ type = map(string)
+ default = {}
+}
+
+variable "database_subnet_tags" {
+ description = "Additional tags for the database subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "database_subnet_group_tags" {
+ description = "Additional tags for the database subnet group"
+ type = map(string)
+ default = {}
+}
+
+variable "redshift_subnet_tags" {
+ description = "Additional tags for the redshift subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "redshift_subnet_group_tags" {
+ description = "Additional tags for the redshift subnet group"
+ type = map(string)
+ default = {}
+}
+
+variable "elasticache_subnet_tags" {
+ description = "Additional tags for the elasticache subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "intra_subnet_tags" {
+ description = "Additional tags for the intra subnets"
+ type = map(string)
+ default = {}
+}
+
+variable "public_acl_tags" {
+ description = "Additional tags for the public subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "private_acl_tags" {
+ description = "Additional tags for the private subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "intra_acl_tags" {
+ description = "Additional tags for the intra subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "database_acl_tags" {
+ description = "Additional tags for the database subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "redshift_acl_tags" {
+ description = "Additional tags for the redshift subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "elasticache_acl_tags" {
+ description = "Additional tags for the elasticache subnets network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "dhcp_options_tags" {
+ description = "Additional tags for the DHCP option set (requires enable_dhcp_options set to true)"
+ type = map(string)
+ default = {}
+}
+
+variable "nat_gateway_tags" {
+ description = "Additional tags for the NAT gateways"
+ type = map(string)
+ default = {}
+}
+
+variable "nat_eip_tags" {
+ description = "Additional tags for the NAT EIP"
+ type = map(string)
+ default = {}
+}
+
+variable "vpn_gateway_tags" {
+ description = "Additional tags for the VPN gateway"
+ type = map(string)
+ default = {}
+}
+
+variable "enable_dhcp_options" {
+ description = "Should be true if you want to specify a DHCP options set with a custom domain name, DNS servers, NTP servers, netbios servers, and/or netbios server type"
+ type = bool
+ default = false
+}
+
+variable "dhcp_options_domain_name" {
+ description = "Specifies DNS name for DHCP options set (requires enable_dhcp_options set to true)"
+ type = string
+ default = ""
+}
+
+variable "dhcp_options_domain_name_servers" {
+ description = "Specify a list of DNS server addresses for DHCP options set, default to AWS provided (requires enable_dhcp_options set to true)"
+ type = list(string)
+ default = ["AmazonProvidedDNS"]
+}
+
+variable "dhcp_options_ntp_servers" {
+ description = "Specify a list of NTP servers for DHCP options set (requires enable_dhcp_options set to true)"
+ type = list(string)
+ default = []
+}
+
+variable "dhcp_options_netbios_name_servers" {
+ description = "Specify a list of netbios servers for DHCP options set (requires enable_dhcp_options set to true)"
+ type = list(string)
+ default = []
+}
+
+variable "dhcp_options_netbios_node_type" {
+ description = "Specify netbios node_type for DHCP options set (requires enable_dhcp_options set to true)"
+ type = string
+ default = ""
+}
+
+variable "manage_default_vpc" {
+ description = "Should be true to adopt and manage Default VPC"
+ type = bool
+ default = false
+}
+
+variable "default_vpc_name" {
+ description = "Name to be used on the Default VPC"
+ type = string
+ default = ""
+}
+
+variable "default_vpc_enable_dns_support" {
+ description = "Should be true to enable DNS support in the Default VPC"
+ type = bool
+ default = true
+}
+
+variable "default_vpc_enable_dns_hostnames" {
+ description = "Should be true to enable DNS hostnames in the Default VPC"
+ type = bool
+ default = false
+}
+
+variable "default_vpc_enable_classiclink" {
+ description = "Should be true to enable ClassicLink in the Default VPC"
+ type = bool
+ default = false
+}
+
+variable "default_vpc_tags" {
+ description = "Additional tags for the Default VPC"
+ type = map(string)
+ default = {}
+}
+
+variable "manage_default_network_acl" {
+ description = "Should be true to adopt and manage Default Network ACL"
+ type = bool
+ default = false
+}
+
+variable "default_network_acl_name" {
+ description = "Name to be used on the Default Network ACL"
+ type = string
+ default = ""
+}
+
+variable "default_network_acl_tags" {
+ description = "Additional tags for the Default Network ACL"
+ type = map(string)
+ default = {}
+}
+
+variable "public_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for public subnets"
+ type = bool
+ default = false
+}
+
+variable "private_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for private subnets"
+ type = bool
+ default = false
+}
+
+variable "intra_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for intra subnets"
+ type = bool
+ default = false
+}
+
+variable "database_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for database subnets"
+ type = bool
+ default = false
+}
+
+variable "redshift_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for redshift subnets"
+ type = bool
+ default = false
+}
+
+variable "elasticache_dedicated_network_acl" {
+ description = "Whether to use dedicated network ACL (not default) and custom rules for elasticache subnets"
+ type = bool
+ default = false
+}
+
+variable "default_network_acl_ingress" {
+ description = "List of maps of ingress rules to set on the Default Network ACL"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_no = 100
+ action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ {
+ rule_no = 101
+ action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ ipv6_cidr_block = "::/0"
+ },
+ ]
+}
+
+variable "default_network_acl_egress" {
+ description = "List of maps of egress rules to set on the Default Network ACL"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_no = 100
+ action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ {
+ rule_no = 101
+ action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ ipv6_cidr_block = "::/0"
+ },
+ ]
+}
+
+variable "public_inbound_acl_rules" {
+ description = "Public subnets inbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "public_outbound_acl_rules" {
+ description = "Public subnets outbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "private_inbound_acl_rules" {
+ description = "Private subnets inbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "private_outbound_acl_rules" {
+ description = "Private subnets outbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "intra_inbound_acl_rules" {
+ description = "Intra subnets inbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "intra_outbound_acl_rules" {
+ description = "Intra subnets outbound network ACLs"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "database_inbound_acl_rules" {
+ description = "Database subnets inbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "database_outbound_acl_rules" {
+ description = "Database subnets outbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "redshift_inbound_acl_rules" {
+ description = "Redshift subnets inbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "redshift_outbound_acl_rules" {
+ description = "Redshift subnets outbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "elasticache_inbound_acl_rules" {
+ description = "Elasticache subnets inbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
+variable "elasticache_outbound_acl_rules" {
+ description = "Elasticache subnets outbound network ACL rules"
+ type = list(map(string))
+
+ default = [
+ {
+ rule_number = 100
+ rule_action = "allow"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_block = "0.0.0.0/0"
+ },
+ ]
+}
+
diff --git a/tests/terraform/graph/resources/variable_rendering/render_lambda/main.tf b/tests/terraform/graph/resources/variable_rendering/render_lambda/main.tf
new file mode 100644
index 0000000000..e41fba1faa
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_lambda/main.tf
@@ -0,0 +1,7 @@
+resource "aws_lambda_permission" "test_lambda_permissions" {
+ count = length([])
+ statement_id = "test_statement_id"
+ action = var.action
+ function_name = "my-func"
+ principal = "dumbeldor"
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_lambda/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_lambda/variables.tf
new file mode 100644
index 0000000000..2df96ec4f5
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_lambda/variables.tf
@@ -0,0 +1,5 @@
+variable "action" {
+ description = "Action for the Lambda permission"
+ type = string
+ default = "lambda:InvokeFunction"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_local/main.tf b/tests/terraform/graph/resources/variable_rendering/render_local/main.tf
new file mode 100644
index 0000000000..7235c79989
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_local/main.tf
@@ -0,0 +1,11 @@
+locals {
+ bucket_name = "test_bucket_name"
+}
+
+resource "aws_s3_bucket" "template_bucket" {
+ region = "us-west-2"
+ bucket = local.bucket_name
+ acl = "acl"
+ force_destroy = true
+}
+
diff --git a/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/main.tf b/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/main.tf
new file mode 100644
index 0000000000..069084dde7
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/main.tf
@@ -0,0 +1,3 @@
+locals {
+ bucket_name = var.var_bucket_name
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/variables.tf
new file mode 100644
index 0000000000..451ad3b268
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_local_from_variable/variables.tf
@@ -0,0 +1,3 @@
+variable "var_bucket_name" {
+ default = "test_bucket_name"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/auto_values.tf b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/auto_values.tf
new file mode 100644
index 0000000000..99ae9a395c
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/auto_values.tf
@@ -0,0 +1,79 @@
+# This file was generated from values defined in rules.tf using update_groups.sh.
+###################################
+# DO NOT CHANGE THIS FILE MANUALLY
+###################################
+
+variable "auto_ingress_rules" {
+ description = "List of ingress rules to add automatically"
+ type = list(string)
+ default = ["postgresql-tcp"]
+}
+
+variable "auto_ingress_with_self" {
+ description = "List of maps defining ingress rules with self to add automatically"
+ type = list(map(string))
+ default = [{ rule = "all-all" }]
+}
+
+variable "auto_egress_rules" {
+ description = "List of egress rules to add automatically"
+ type = list(string)
+ default = ["all-all"]
+}
+
+variable "auto_egress_with_self" {
+ description = "List of maps defining egress rules with self to add automatically"
+ type = list(map(string))
+ default = []
+}
+
+# Computed
+variable "auto_computed_ingress_rules" {
+ description = "List of ingress rules to add automatically"
+ type = list(string)
+ default = []
+}
+
+variable "auto_computed_ingress_with_self" {
+ description = "List of maps defining computed ingress rules with self to add automatically"
+ type = list(map(string))
+ default = []
+}
+
+variable "auto_computed_egress_rules" {
+ description = "List of computed egress rules to add automatically"
+ type = list(string)
+ default = []
+}
+
+variable "auto_computed_egress_with_self" {
+ description = "List of maps defining computed egress rules with self to add automatically"
+ type = list(map(string))
+ default = []
+}
+
+# Number of computed rules
+variable "auto_number_of_computed_ingress_rules" {
+ description = "Number of computed ingress rules to create by name"
+ type = number
+ default = 0
+}
+
+variable "auto_number_of_computed_ingress_with_self" {
+ description = "Number of computed ingress rules to create where 'self' is defined"
+ type = number
+ default = 0
+}
+
+variable "auto_number_of_computed_egress_rules" {
+ description = "Number of computed egress rules to create by name"
+ type = number
+ default = 0
+}
+
+variable "auto_number_of_computed_egress_with_self" {
+ description = "Number of computed egress rules to create where 'self' is defined"
+ type = number
+ default = 0
+}
+
diff --git a/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/main.tf b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/main.tf
new file mode 100644
index 0000000000..00b9625d3a
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/main.tf
@@ -0,0 +1,115 @@
+module "sg" {
+ source = ""
+
+ create = var.create
+ name = var.name
+ use_name_prefix = var.use_name_prefix
+ description = var.description
+ vpc_id = var.vpc_id
+ revoke_rules_on_delete = var.revoke_rules_on_delete
+ tags = var.tags
+
+ ##########
+ # Ingress
+ ##########
+ # Rules by names - open for default CIDR
+ ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""]))))
+
+ # Open for self
+ ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self)
+
+ # Open to IPv4 cidr blocks
+ ingress_with_cidr_blocks = var.ingress_with_cidr_blocks
+
+ # Open to IPv6 cidr blocks
+ ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks
+
+ # Open for security group id
+ ingress_with_source_security_group_id = var.ingress_with_source_security_group_id
+
+ # Default ingress CIDR blocks
+ ingress_cidr_blocks = var.ingress_cidr_blocks
+ ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks
+
+ # Default prefix list ids
+ ingress_prefix_list_ids = var.ingress_prefix_list_ids
+
+ ###################
+ # Computed Ingress
+ ###################
+ # Rules by names - open for default CIDR
+ computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""]))))
+
+ # Open for self
+ computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self)
+
+ # Open to IPv4 cidr blocks
+ computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks
+
+ # Open to IPv6 cidr blocks
+ computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks
+
+ # Open for security group id
+ computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id
+
+ #############################
+ # Number of computed ingress
+ #############################
+ number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules
+ number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self
+ number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks
+ number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks
+ number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id
+
+ #########
+ # Egress
+ #########
+ # Rules by names - open for default CIDR
+ egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""]))))
+
+ # Open for self
+ egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self)
+
+ # Open to IPv4 cidr blocks
+ egress_with_cidr_blocks = var.egress_with_cidr_blocks
+
+ # Open to IPv6 cidr blocks
+ egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks
+
+ # Open for security group id
+ egress_with_source_security_group_id = var.egress_with_source_security_group_id
+
+ # Default egress CIDR blocks
+ egress_cidr_blocks = var.egress_cidr_blocks
+ egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks
+
+ # Default prefix list ids
+ egress_prefix_list_ids = var.egress_prefix_list_ids
+
+ ##################
+ # Computed Egress
+ ##################
+ # Rules by names - open for default CIDR
+ computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""]))))
+
+ # Open for self
+ computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self)
+
+ # Open to IPv4 cidr blocks
+ computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks
+
+ # Open to IPv6 cidr blocks
+ computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks
+
+ # Open for security group id
+ computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id
+
+ #############################
+ # Number of computed egress
+ #############################
+ number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules
+ number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self
+ number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks
+ number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks
+ number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/outputs.tf b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/outputs.tf
new file mode 100644
index 0000000000..3d7ad67f44
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/outputs.tf
@@ -0,0 +1,24 @@
+output "this_security_group_id" {
+ description = "The ID of the security group"
+ value = module.sg.this_security_group_id
+}
+
+output "this_security_group_vpc_id" {
+ description = "The VPC ID"
+ value = module.sg.this_security_group_vpc_id
+}
+
+output "this_security_group_owner_id" {
+ description = "The owner ID"
+ value = module.sg.this_security_group_owner_id
+}
+
+output "this_security_group_name" {
+ description = "The name of the security group"
+ value = module.sg.this_security_group_name
+}
+
+output "this_security_group_description" {
+ description = "The description of the security group"
+ value = module.sg.this_security_group_description
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/variables.tf
new file mode 100644
index 0000000000..4d33156199
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/variables.tf
@@ -0,0 +1,348 @@
+#################
+# Security group
+#################
+variable "create" {
+ description = "Whether to create security group and all rules"
+ type = bool
+ default = true
+}
+
+variable "vpc_id" {
+ description = "ID of the VPC where to create security group"
+ type = string
+}
+
+variable "name" {
+ description = "Name of security group"
+ type = string
+}
+
+variable "use_name_prefix" {
+ description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation"
+ type = bool
+ default = true
+}
+
+variable "description" {
+ description = "Description of security group"
+ type = string
+ default = "Security Group managed by Terraform"
+}
+
+variable "revoke_rules_on_delete" {
+ description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR."
+ type = bool
+ default = false
+}
+
+variable "tags" {
+ description = "A mapping of tags to assign to security group"
+ type = map(string)
+ default = {}
+}
+
+##########
+# Ingress
+##########
+variable "ingress_rules" {
+ description = "List of ingress rules to create by name"
+ type = list(string)
+ default = []
+}
+
+variable "ingress_with_self" {
+ description = "List of ingress rules to create where 'self' is defined"
+ type = list(map(string))
+ default = []
+}
+
+variable "ingress_with_cidr_blocks" {
+ description = "List of ingress rules to create where 'cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "ingress_with_ipv6_cidr_blocks" {
+ description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "ingress_with_source_security_group_id" {
+ description = "List of ingress rules to create where 'source_security_group_id' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "ingress_cidr_blocks" {
+ description = "List of IPv4 CIDR ranges to use on all ingress rules"
+ type = list(string)
+ default = []
+}
+
+variable "ingress_ipv6_cidr_blocks" {
+ description = "List of IPv6 CIDR ranges to use on all ingress rules"
+ type = list(string)
+ default = []
+}
+
+variable "ingress_prefix_list_ids" {
+ description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules"
+ type = list(string)
+ default = []
+}
+
+###################
+# Computed Ingress
+###################
+variable "computed_ingress_rules" {
+ description = "List of computed ingress rules to create by name"
+ type = list(string)
+ default = []
+}
+
+variable "computed_ingress_with_self" {
+ description = "List of computed ingress rules to create where 'self' is defined"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_ingress_with_cidr_blocks" {
+ description = "List of computed ingress rules to create where 'cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_ingress_with_ipv6_cidr_blocks" {
+ description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_ingress_with_source_security_group_id" {
+ description = "List of computed ingress rules to create where 'source_security_group_id' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_ingress_cidr_blocks" {
+ description = "List of IPv4 CIDR ranges to use on all computed ingress rules"
+ type = list(string)
+ default = []
+}
+
+variable "computed_ingress_ipv6_cidr_blocks" {
+ description = "List of IPv6 CIDR ranges to use on all computed ingress rules"
+ type = list(string)
+ default = []
+}
+
+variable "computed_ingress_prefix_list_ids" {
+ description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules"
+ type = list(string)
+ default = []
+}
+
+###################################
+# Number of computed ingress rules
+###################################
+variable "number_of_computed_ingress_rules" {
+ description = "Number of computed ingress rules to create by name"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_with_self" {
+ description = "Number of computed ingress rules to create where 'self' is defined"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_with_cidr_blocks" {
+ description = "Number of computed ingress rules to create where 'cidr_blocks' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_with_ipv6_cidr_blocks" {
+ description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_with_source_security_group_id" {
+ description = "Number of computed ingress rules to create where 'source_security_group_id' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_cidr_blocks" {
+ description = "Number of IPv4 CIDR ranges to use on all computed ingress rules"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_ipv6_cidr_blocks" {
+ description = "Number of IPv6 CIDR ranges to use on all computed ingress rules"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_ingress_prefix_list_ids" {
+ description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules"
+ type = number
+ default = 0
+}
+
+#########
+# Egress
+#########
+variable "egress_rules" {
+ description = "List of egress rules to create by name"
+ type = list(string)
+ default = []
+}
+
+variable "egress_with_self" {
+ description = "List of egress rules to create where 'self' is defined"
+ type = list(map(string))
+ default = []
+}
+
+variable "egress_with_cidr_blocks" {
+ description = "List of egress rules to create where 'cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "egress_with_ipv6_cidr_blocks" {
+ description = "List of egress rules to create where 'ipv6_cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "egress_with_source_security_group_id" {
+ description = "List of egress rules to create where 'source_security_group_id' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "egress_cidr_blocks" {
+ description = "List of IPv4 CIDR ranges to use on all egress rules"
+ type = list(string)
+ default = ["0.0.0.0/0"]
+}
+
+variable "egress_ipv6_cidr_blocks" {
+ description = "List of IPv6 CIDR ranges to use on all egress rules"
+ type = list(string)
+ default = ["::/0"]
+}
+
+variable "egress_prefix_list_ids" {
+ description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules"
+ type = list(string)
+ default = []
+}
+
+##################
+# Computed Egress
+##################
+variable "computed_egress_rules" {
+ description = "List of computed egress rules to create by name"
+ type = list(string)
+ default = []
+}
+
+variable "computed_egress_with_self" {
+ description = "List of computed egress rules to create where 'self' is defined"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_egress_with_cidr_blocks" {
+ description = "List of computed egress rules to create where 'cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_egress_with_ipv6_cidr_blocks" {
+ description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_egress_with_source_security_group_id" {
+ description = "List of computed egress rules to create where 'source_security_group_id' is used"
+ type = list(map(string))
+ default = []
+}
+
+variable "computed_egress_cidr_blocks" {
+ description = "List of IPv4 CIDR ranges to use on all computed egress rules"
+ type = list(string)
+ default = ["0.0.0.0/0"]
+}
+
+variable "computed_egress_ipv6_cidr_blocks" {
+ description = "List of IPv6 CIDR ranges to use on all computed egress rules"
+ type = list(string)
+ default = ["::/0"]
+}
+
+variable "computed_egress_prefix_list_ids" {
+ description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules"
+ type = list(string)
+ default = []
+}
+
+##################################
+# Number of computed egress rules
+##################################
+variable "number_of_computed_egress_rules" {
+ description = "Number of computed egress rules to create by name"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_with_self" {
+ description = "Number of computed egress rules to create where 'self' is defined"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_with_cidr_blocks" {
+ description = "Number of computed egress rules to create where 'cidr_blocks' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_with_ipv6_cidr_blocks" {
+ description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_with_source_security_group_id" {
+ description = "Number of computed egress rules to create where 'source_security_group_id' is used"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_cidr_blocks" {
+ description = "Number of IPv4 CIDR ranges to use on all computed egress rules"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_ipv6_cidr_blocks" {
+ description = "Number of IPv6 CIDR ranges to use on all computed egress rules"
+ type = number
+ default = 0
+}
+
+variable "number_of_computed_egress_prefix_list_ids" {
+ description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules"
+ type = number
+ default = 0
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/versions.tf b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/versions.tf
new file mode 100644
index 0000000000..1bdee38b34
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_module_postgresql/versions.tf
@@ -0,0 +1,7 @@
+terraform {
+ required_version = ">= 0.12.6, < 0.14"
+
+ required_providers {
+ aws = ">= 2.42, < 4.0"
+ }
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_nested_modules/child/main.tf b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/child/main.tf
new file mode 100644
index 0000000000..0aadebe58c
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/child/main.tf
@@ -0,0 +1,3 @@
+output "myoutput" {
+ value = "bar"
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_nested_modules/main.tf b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/main.tf
new file mode 100644
index 0000000000..1ab4d53079
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/main.tf
@@ -0,0 +1,39 @@
+provider "aws" {
+ profile = var.aws_profile
+ region = "us-east-1"
+ alias = "east1"
+}
+
+locals {
+ dummy_with_dash = format("-%s", var.dummy_1)
+ bucket_name = var.bucket_name
+ x = {
+ y = "z"
+ }
+}
+resource "aws_instance" "example" {
+ ami = local.ami_name
+ instance_type = module.child.myoutput
+}
+
+resource "aws_s3_bucket" "template_bucket" {
+ provider = aws.east1
+ region = var.region
+ bucket = local.bucket_name
+ acl = var.acl
+ force_destroy = true
+}
+
+resource "aws_eip" "ip" {
+ vpc = local.is_vpc
+ instance = aws_instance.example.id
+}
+
+locals {
+ is_vpc = true
+ ami_name = local.dummy_with_dash
+}
+
+module "child" {
+ source = "./child"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_nested_modules/outputs.tf b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/outputs.tf
new file mode 100644
index 0000000000..adbbd2fda5
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/outputs.tf
@@ -0,0 +1,7 @@
+output "bucket_acl" {
+ value = aws_s3_bucket.template_bucket.acl
+
+ depends_on = [
+ aws_eip.ip
+ ]
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/render_nested_modules/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/variables.tf
new file mode 100644
index 0000000000..605f9059ba
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_nested_modules/variables.tf
@@ -0,0 +1,27 @@
+variable "bucket_name" {
+ default = {
+ val = "MyBucket"
+ }
+
+}
+
+variable "acl" {
+ default = var.acl_default_value
+}
+
+variable "acl_default_value" {
+ default = local.x.y
+}
+
+variable "region" {
+ default = "us-west-2"
+}
+
+variable "aws_profile" {
+ default = "default"
+}
+
+variable "dummy_1" {
+ default = "dummy_1"
+}
+
diff --git a/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/consts.tf b/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/consts.tf
new file mode 100644
index 0000000000..f3f513496e
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/consts.tf
@@ -0,0 +1,61 @@
+data "aws_caller_identity" "current" {
+ account_id = "test_id"
+}
+
+variable "company_name" {
+ default = "acme"
+}
+
+variable "environment" {
+ default = "dev"
+}
+
+locals {
+ resource_prefix = {
+ value = "${data.aws_caller_identity.current.account_id}-${var.company_name}-${var.environment}"
+ }
+}
+
+
+
+variable "profile" {
+ default = "default"
+}
+
+variable "region" {
+ default = "us-west-2"
+}
+
+variable "availability_zone" {
+ type = "string"
+ default = "us-west-2a"
+}
+
+variable "availability_zone2" {
+ type = "string"
+ default = "us-west-2b"
+}
+
+
+variable ami {
+ type = "string"
+ default = "ami-09a5b0b7edf08843d"
+}
+
+variable "dbname" {
+ type = "string"
+ description = "Name of the Database"
+ default = "db1"
+}
+
+variable "password" {
+ type = "string"
+ description = "Database password"
+ default = "Aa1234321Bb"
+}
+
+variable "neptune-dbname" {
+ type = "string"
+ description = "Name of the Neptune graph database"
+ default = "neptunedb1"
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/main.tf b/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/main.tf
new file mode 100644
index 0000000000..79d8e31f15
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_terragoat_db_app/main.tf
@@ -0,0 +1,316 @@
+resource "aws_db_instance" "default" {
+ name = var.dbname
+ engine = "mysql"
+ option_group_name = aws_db_option_group.default.name
+ parameter_group_name = aws_db_parameter_group.default.name
+ db_subnet_group_name = aws_db_subnet_group.default.name
+ vpc_security_group_ids = ["${aws_security_group.default.id}"]
+
+ identifier = "rds-${local.resource_prefix.value}"
+ engine_version = "8.0" # Latest major version
+ instance_class = "db.t3.micro"
+ allocated_storage = "20"
+ username = "admin"
+ password = var.password
+ apply_immediately = true
+ multi_az = false
+ backup_retention_period = 0
+ storage_encrypted = false
+ skip_final_snapshot = true
+ monitoring_interval = 0
+ publicly_accessible = true
+
+ tags = {
+ Name = "${local.resource_prefix.value}-rds"
+ Environment = local.resource_prefix.value
+ }
+
+ # Ignore password changes from tf plan diff
+ lifecycle {
+ ignore_changes = ["password"]
+ }
+}
+
+resource "aws_db_option_group" "default" {
+ engine_name = "mysql"
+ name = "og-${local.resource_prefix.value}"
+ major_engine_version = "8.0"
+ option_group_description = "Terraform OG"
+
+ tags = {
+ Name = "${local.resource_prefix.value}-og"
+ Environment = local.resource_prefix.value
+ }
+}
+
+resource "aws_db_parameter_group" "default" {
+ name = "pg-${local.resource_prefix.value}"
+ family = "mysql8.0"
+ description = "Terraform PG"
+
+ parameter {
+ name = "character_set_client"
+ value = "utf8"
+ apply_method = "immediate"
+ }
+
+ parameter {
+ name = "character_set_server"
+ value = "utf8"
+ apply_method = "immediate"
+ }
+
+ tags = {
+ Name = "${local.resource_prefix.value}-pg"
+ Environment = local.resource_prefix.value
+ }
+}
+
+resource "aws_db_subnet_group" "default" {
+ name = "sg-${local.resource_prefix.value}"
+ subnet_ids = ["${aws_subnet.web_subnet.id}", "${aws_subnet.web_subnet2.id}"]
+ description = "Terraform DB Subnet Group"
+
+ tags = {
+ Name = "sg-${local.resource_prefix.value}"
+ Environment = local.resource_prefix.value
+ }
+}
+
+resource "aws_security_group" "default" {
+ name = "${local.resource_prefix.value}-rds-sg"
+ vpc_id = aws_vpc.web_vpc.id
+
+ tags = {
+ Name = "${local.resource_prefix.value}-rds-sg"
+ Environment = local.resource_prefix.value
+ }
+}
+
+resource "aws_security_group_rule" "ingress" {
+ type = "ingress"
+ from_port = "3306"
+ to_port = "3306"
+ protocol = "tcp"
+ cidr_blocks = ["${aws_vpc.web_vpc.cidr_block}"]
+ security_group_id = aws_security_group.default.id
+}
+
+resource "aws_security_group_rule" "egress" {
+ type = "egress"
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ security_group_id = "${aws_security_group.default.id}"
+}
+
+
+### EC2 instance
+resource "aws_iam_instance_profile" "ec2profile" {
+ name = "${local.resource_prefix.value}-profile"
+ role = "${aws_iam_role.ec2role.name}"
+}
+
+resource "aws_iam_role" "ec2role" {
+ name = "${local.resource_prefix.value}-role"
+ path = "/"
+
+ assume_role_policy = < /tmp/dbinfo.inc
+
+EnD
+sudo mv /tmp/dbinfo.inc /var/www/inc
+sudo chown root:root /var/www/inc/dbinfo.inc
+cat << EnD > /tmp/index.php
+
+
+
+Sample page
+
+
+
+
+
+
+ ID
+ NAME
+ ADDRESS
+
+";
+ echo "",\$query_data[0], " ",
+ "",\$query_data[1], " ",
+ "",\$query_data[2], " ";
+ echo "";
+}
+?>
+
+
+
+
+
+Error adding employee data.");
+}
+/* Check whether the table exists and, if not, create it. */
+function VerifyEmployeesTable(\$connection, \$dbName) {
+ if(!TableExists("EMPLOYEES", \$connection, \$dbName))
+ {
+ \$query = "CREATE TABLE EMPLOYEES (
+ ID int(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(45),
+ ADDRESS VARCHAR(90)
+ )";
+ if(!mysqli_query(\$connection, \$query)) echo("Error creating table.
");
+ }
+}
+/* Check for the existence of a table. */
+function TableExists(\$tableName, \$connection, \$dbName) {
+ \$t = mysqli_real_escape_string(\$connection, \$tableName);
+ \$d = mysqli_real_escape_string(\$connection, \$dbName);
+ \$checktable = mysqli_query(\$connection,
+ "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = '\$t' AND TABLE_SCHEMA = '\$d'");
+ if(mysqli_num_rows(\$checktable) > 0) return true;
+ return false;
+}
+?>
+EnD
+sudo mv /tmp/index.php /var/www/html
+sudo chown root:root /var/www/html/index.php
+EOF
+ tags = {
+ Name = "${local.resource_prefix.value}-dbapp"
+ }
+}
+
+output "db_app_public_dns" {
+ description = "DB Public DNS name"
+ value = aws_instance.db_app.public_dns
+}
+
+output "db_endpoint" {
+ description = "DB Endpoint"
+ value = aws_db_instance.default.endpoint
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_variable/main.tf b/tests/terraform/graph/resources/variable_rendering/render_variable/main.tf
new file mode 100644
index 0000000000..56c2e79d60
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_variable/main.tf
@@ -0,0 +1,6 @@
+resource "aws_s3_bucket" "template_bucket" {
+ region = var.region
+ bucket = "test_bucket_name"
+ acl = "acl"
+ force_destroy = true
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/resources/variable_rendering/render_variable/variables.tf b/tests/terraform/graph/resources/variable_rendering/render_variable/variables.tf
new file mode 100644
index 0000000000..81b8dbe73e
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/render_variable/variables.tf
@@ -0,0 +1,3 @@
+variable "region" {
+ default = "us-west-2"
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/eks.tf b/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/eks.tf
new file mode 100644
index 0000000000..a2105fffb5
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/eks.tf
@@ -0,0 +1,19 @@
+resource "aws_eks_cluster" "tf_eks" {
+ name = local.cluster_name
+ role_arn = aws_iam_role.master.arn
+ version = var.kubernetes_version
+
+ vpc_config {
+ security_group_ids = [aws_security_group.master.id]
+ subnet_ids = aws_subnet.eks[*].id
+ }
+
+ tags = {
+ project = var.project
+ }
+
+ depends_on = [
+ aws_iam_role_policy_attachment.AmazonEKSClusterPolicy,
+ aws_iam_role_policy_attachment.AmazonEKSServicePolicy
+ ]
+}
diff --git a/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/variables.tf b/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/variables.tf
new file mode 100644
index 0000000000..09c6122d20
--- /dev/null
+++ b/tests/terraform/graph/resources/variable_rendering/terraform-aws-eks-master/variables.tf
@@ -0,0 +1,79 @@
+variable "region" {
+ type = string
+ description = "AWS Region"
+}
+
+variable "access_key" {
+ type = string
+ description = "AWS Access Key"
+}
+
+variable "secret_key" {
+ type = string
+ description = "AWS Secret Key"
+}
+
+variable "project" {
+ type = string
+}
+
+variable "cluster_name" {
+ type = string
+ description = "EKS name"
+}
+
+variable "accessing_computer_ips" {
+ type = list(string)
+ description = "cidr blocks"
+}
+
+variable "number_of_subnets" {
+ type = number
+ default = 2
+ description = "Number of subnets"
+}
+
+variable "iam_worker_instance_profile_name" {
+ type = string
+ default = "ipaas-eks-workers"
+ description = "IAM worker instance profile name"
+}
+
+variable "kubeconfig_path" {
+ type = string
+ default = "./kubeconfig"
+ description = "Kubeconfig path"
+}
+
+variable "create_kubeconfig" {
+ type = bool
+ default = true
+}
+
+variable "kubernetes_version" {
+ type = string
+ default = "1.19"
+ description = "EKS kubernetes version."
+}
+
+variable "node_groups" {
+ type = list(object({
+ name = string
+ desired_size = number
+ max_size = number
+ min_size = number
+ instance_type = string
+ # Opcionais
+ # ami_type = string (Default: AL2_x86_64)
+ # disk_size = number (Default: 20)
+ }))
+ default = [
+ {
+ name = "example"
+ desired_size = 2
+ max_size = 3
+ min_size = 1
+ instance_type = "t3.medium"
+ }
+ ]
+}
diff --git a/tests/terraform/graph/runner/__init__.py b/tests/terraform/graph/runner/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/runner/persistent_data.json b/tests/terraform/graph/runner/persistent_data.json
new file mode 100644
index 0000000000..0272eb1a04
--- /dev/null
+++ b/tests/terraform/graph/runner/persistent_data.json
@@ -0,0 +1,215 @@
+{
+ "tf_definitions": {
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/more_vars.tf": {
+ "variable": [
+ {
+ "encryption": {
+ "default": [
+ "AES256"
+ ]
+ }
+ }
+ ]
+ },
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/pass_s3.tf": {
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "bucket_with_versioning": {
+ "server_side_encryption_configuration": [
+ {
+ "rule": [
+ {
+ "apply_server_side_encryption_by_default": [
+ {
+ "sse_algorithm": [
+ "AES256"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "versioning": [
+ {
+ "enabled": [
+ true
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/variables.tf": {
+ "variable": [
+ {
+ "versioning": {
+ "default": [
+ true
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "definitions_context": {
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/more_vars.tf": {
+ "variable": {
+ "encryption": {
+ "start_line": 1,
+ "end_line": 3,
+ "code_lines": [
+ [
+ 1,
+ "variable \"encryption\" {\n"
+ ],
+ [
+ 2,
+ " default = \"AES256\"\n"
+ ],
+ [
+ 3,
+ "}"
+ ]
+ ],
+ "skipped_checks": []
+ },
+ "assignments": {
+ "encryption": "AES256"
+ }
+ }
+ },
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/pass_s3.tf": {
+ "resource": {
+ "aws_s3_bucket": {
+ "bucket_with_versioning": {
+ "start_line": 1,
+ "end_line": 13,
+ "code_lines": [
+ [
+ 1,
+ "resource \"aws_s3_bucket\" \"bucket_with_versioning\" {\n"
+ ],
+ [
+ 2,
+ " versioning {\n"
+ ],
+ [
+ 3,
+ " enabled = var.versioning\n"
+ ],
+ [
+ 4,
+ " }\n"
+ ],
+ [
+ 5,
+ "\n"
+ ],
+ [
+ 6,
+ " server_side_encryption_configuration {\n"
+ ],
+ [
+ 7,
+ " rule {\n"
+ ],
+ [
+ 8,
+ " apply_server_side_encryption_by_default {\n"
+ ],
+ [
+ 9,
+ " sse_algorithm = var.encryption\n"
+ ],
+ [
+ 10,
+ " }\n"
+ ],
+ [
+ 11,
+ " }\n"
+ ],
+ [
+ 12,
+ " }\n"
+ ],
+ [
+ 13,
+ "}"
+ ]
+ ],
+ "skipped_checks": []
+ }
+ }
+ }
+ },
+ "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/variables.tf": {
+ "variable": {
+ "versioning": {
+ "start_line": 1,
+ "end_line": 3,
+ "code_lines": [
+ [
+ 1,
+ "variable \"versioning\" {\n"
+ ],
+ [
+ 2,
+ " default = true\n"
+ ],
+ [
+ 3,
+ "}"
+ ]
+ ],
+ "skipped_checks": []
+ },
+ "assignments": {
+ "versioning": true
+ }
+ }
+ }
+ },
+ "breadcrumbs": {
+ "/pass_s3.tf": {
+ "aws_s3_bucket.bucket_with_versioning": {
+ "server_side_encryption_configuration.rule.apply_server_side_encryption_by_default.sse_algorithm": [
+ {
+ "type": "variable",
+ "name": "encryption",
+ "path": "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/more_vars.tf",
+ "module_connection": false
+ }
+ ],
+ "server_side_encryption_configuration.rule.apply_server_side_encryption_by_default": [
+ {
+ "type": "variable",
+ "name": "encryption",
+ "path": "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/more_vars.tf",
+ "module_connection": false
+ }
+ ],
+ "server_side_encryption_configuration.rule": [
+ {
+ "type": "variable",
+ "name": "encryption",
+ "path": "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/more_vars.tf",
+ "module_connection": false
+ }
+ ],
+ "versioning.enabled": [
+ {
+ "type": "variable",
+ "name": "versioning",
+ "path": "/Users/nkor/dev/checkov_v2/tests/graph/terraform/resources/graph_files_test/variables.tf",
+ "module_connection": false
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/runner/test_graph_builder.py b/tests/terraform/graph/runner/test_graph_builder.py
new file mode 100644
index 0000000000..fcb1cd251a
--- /dev/null
+++ b/tests/terraform/graph/runner/test_graph_builder.py
@@ -0,0 +1,72 @@
+import json
+import os
+from unittest import TestCase
+
+from checkov.terraform.runner import Runner
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestGraphBuilder(TestCase):
+
+ def test_build_graph(self):
+ resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graph_files_test")
+ source_files = ["pass_s3.tf", "variables.tf"]
+ runner = Runner()
+ report = runner.run(None, None, files=list(map(lambda f: f'{resources_path}/{f}', source_files)))
+ tf_definitions = runner.definitions
+ self.assertEqual(3, len(report.failed_checks))
+ for file, definitions in tf_definitions.items():
+ if file.endswith('pass_s3.tf'):
+ s3_bucket_config = definitions['resource'][0]['aws_s3_bucket']['bucket_with_versioning']
+ # Evaluation succeeded for included vars
+ self.assertTrue(s3_bucket_config['versioning'][0]['enabled'][0])
+ # Evaluation does not run for un-included vars
+ self.assertEqual(s3_bucket_config['server_side_encryption_configuration'][0]['rule'][0]['apply_server_side_encryption_by_default'][0]['sse_algorithm'][0], 'var.encryption')
+
+ def test_run_clean(self):
+ resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graph_files_test")
+ runner = Runner()
+ report = runner.run(root_folder=resources_path)
+ self.assertEqual(4, len(report.failed_checks))
+ self.assertEqual(6, len(report.passed_checks))
+ self.assertEqual(0, len(report.skipped_checks))
+
+ def test_run_persistent_data(self):
+ resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graph_files_test")
+ runner = Runner()
+ data_path = os.path.join(os.path.dirname(__file__), "persistent_data.json")
+ with open(data_path) as f:
+ data = json.load(f)
+ tf_definitions = data["tf_definitions"]
+ breadcrumbs = data["breadcrumbs"]
+ definitions_context = data["definitions_context"]
+ runner.set_external_data(tf_definitions, definitions_context, breadcrumbs)
+ report = runner.run(root_folder=resources_path)
+ self.assertGreaterEqual(len(report.failed_checks), 3)
+ self.assertEqual(len(report.passed_checks), 6)
+ self.assertEqual(len(report.skipped_checks), 0)
+
+ def test_module_and_variables(self):
+ resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "modules-and-vars")
+ runner = Runner()
+ report = runner.run(root_folder=resources_path)
+ self.assertLessEqual(2, len(report.failed_checks))
+ self.assertLessEqual(12, len(report.passed_checks))
+ self.assertEqual(0, len(report.skipped_checks))
+
+ found_versioning_failure = False
+
+ for record in report.failed_checks:
+ if record.check_id != 'CKV_AWS_40':
+ self.assertIsNotNone(record.breadcrumbs)
+ if record.check_id == 'CKV_AWS_21':
+ found_versioning_failure = True
+ bc = record.breadcrumbs.get('versioning.enabled')
+ self.assertEqual(len(bc), 2)
+ bc = bc[0]
+ self.assertEqual(bc.get('type'), 'module')
+ self.assertEqual(os.path.relpath(bc.get('path'), resources_path), 'examples/complete/main.tf')
+
+ self.assertTrue(found_versioning_failure)
+
diff --git a/tests/terraform/graph/utils/__init__.py b/tests/terraform/graph/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/utils/test_utils.py b/tests/terraform/graph/utils/test_utils.py
new file mode 100644
index 0000000000..39031d5ce3
--- /dev/null
+++ b/tests/terraform/graph/utils/test_utils.py
@@ -0,0 +1,182 @@
+import os
+import pprint
+from typing import List, Tuple
+from unittest import TestCase
+
+from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.utils import VertexReference, get_referenced_vertices_in_value, \
+ replace_map_attribute_access_with_dot, update_dictionary_attribute, generate_possible_strings_from_wildcards, \
+ attribute_has_nested_attributes
+
+
+class TestUtils(TestCase):
+ def test_find_non_literal_values(self):
+ aliases = {'aws': {CustomAttributes.BLOCK_TYPE: BlockType.PROVIDER}}
+ str_value = 'aws.east1'
+ expected = [VertexReference(BlockType.PROVIDER, ['aws', 'east1'], 'aws.east1')]
+ self.assertEqual(expected, get_referenced_vertices_in_value(str_value, aliases, []))
+
+ str_values = [
+ 'var.x',
+ 'format("-%s", var.x)',
+ '../child',
+ 'aws_instance.example.id',
+ 'bc_c_${var.customer_name}',
+ 'aws iam delete-role --role-name ${local.role_name} --profile ${var.profile} --region ${var.region}',
+ 'length(aws_vpc.main) > 0 ? aws_vpc.main[0].cidr_block : ${var.x}',
+ ]
+ expected = [
+ [VertexReference(BlockType.VARIABLE, ['x'], 'var.x')],
+ [VertexReference(BlockType.VARIABLE, ['x'], 'var.x')],
+ [],
+ [VertexReference(BlockType.RESOURCE, ['aws_instance.example', 'id'], 'aws_instance.example.id')],
+ [VertexReference(BlockType.VARIABLE, ['customer_name'], 'var.customer_name')],
+ [VertexReference(BlockType.LOCALS, ['role_name'], 'local.role_name'), VertexReference(BlockType.VARIABLE, ['profile'], 'var.profile'), VertexReference(BlockType.VARIABLE, ['region'], 'var.region')],
+ [VertexReference(BlockType.RESOURCE, ['aws_vpc.main'], 'aws_vpc.main'), VertexReference(BlockType.RESOURCE, ['aws_vpc.main', 'cidr_block'], 'aws_vpc.main.cidr_block'), VertexReference(BlockType.VARIABLE, ['x'], 'var.x')],
+ ]
+
+ for i in range(0, len(str_values)):
+ self.assertEqual(expected[i], get_referenced_vertices_in_value(str_values[i], aliases, ['aws_vpc', 'aws_instance']))
+
+ def test_replace_map_attribute_access_with_dot(self):
+ str_value = 'data.aws_availability_zones["available"].names[1]'
+ replace_map_attribute_access_with_dot(str_value)
+ self.assertEqual('data.aws_availability_zones.available.names[1]', replace_map_attribute_access_with_dot(str_value))
+
+ str_value = 'data.aws_availability_zones[0].names[1]'
+ replace_map_attribute_access_with_dot(str_value)
+ self.assertEqual('data.aws_availability_zones[0].names[1]', replace_map_attribute_access_with_dot(str_value))
+
+ def test_update_dictionary_attribute_nested(self):
+ origin_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['${var.acl}'], 'versioning': [{'enabled': ['${var.is_enabled}']}]}}}
+ key_to_update = 'versioning.enabled'
+ new_value = [False]
+ expected_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['${var.acl}'], 'versioning': [{'enabled': [False]}]}}}
+ actual_config = update_dictionary_attribute(origin_config, key_to_update, new_value)
+ self.assertEqual(expected_config, actual_config, f'failed to update config. expected: {expected_config}, got: {actual_config}')
+
+ def test_update_dictionary_attribute(self):
+ origin_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['${var.acl}'], 'versioning': [{'enabled': ['${var.is_enabled}']}]}}}
+ key_to_update = 'acl'
+ new_value = ['public-read']
+ expected_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['public-read'], 'versioning': [{'enabled': ['${var.is_enabled}']}]}}}
+ actual_config = update_dictionary_attribute(origin_config, key_to_update, new_value)
+ self.assertEqual(expected_config, actual_config, f'failed to update config.\nexpected: {expected_config}\ngot: {actual_config}')
+
+ def test_update_dictionary_locals(self):
+ origin_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['${var.acl}'], 'versioning': [{'enabled': ['${var.is_enabled}']}]}}}
+ key_to_update = 'acl'
+ new_value = ['public-read']
+ expected_config = {'aws_s3_bucket': {'destination': {'bucket': ['tf-test-bucket-destination-12345'], 'acl': ['public-read'], 'versioning': [{'enabled': ['${var.is_enabled}']}]}}}
+ actual_config = update_dictionary_attribute(origin_config, key_to_update, new_value)
+ self.assertEqual(expected_config, actual_config, f'failed to update config.\nexpected: {expected_config}\ngot: {actual_config}')
+
+ def test_generate_possible_strings_from_wildcards(self):
+ origin_string = "a.*.b.*.c.*"
+ expected_results = [
+ "a.0.b.0.c.0",
+ "a.0.b.1.c.0",
+ "a.1.b.0.c.0",
+ "a.0.b.0.c.1",
+ "a.1.b.1.c.0",
+ "a.1.b.0.c.1",
+ "a.0.b.1.c.1",
+ "a.1.b.1.c.1",
+ "a.b.c",
+ ]
+ expected_results.sort()
+ results = generate_possible_strings_from_wildcards(origin_string=origin_string, max_entries=2)
+ results.sort()
+ self.assertEqual(expected_results, results)
+
+ def test_find_var_blocks(self):
+ cases: List[Tuple[str, List[VertexReference]]] = [
+ (
+ "${local.one}",
+ [
+ VertexReference(BlockType.LOCALS, sub_parts=["one"], origin_value="local.one")
+ ]
+ ),
+ (
+ "${local.NAME[foo]}-${local.TAIL}${var.gratuitous_var_default}",
+ [
+ VertexReference(BlockType.LOCALS, sub_parts=["NAME"], origin_value="local.NAME"),
+ VertexReference(BlockType.LOCALS, sub_parts=["TAIL"], origin_value="local.TAIL"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["gratuitous_var_default"], origin_value="var.gratuitous_var_default"),
+ ]
+ ),
+ # Ordered returning of sub-vars and then outer var.
+ (
+ "${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ [
+ VertexReference(BlockType.LOCALS, sub_parts=["common_tags"], origin_value="local.common_tags"),
+ VertexReference(BlockType.LOCALS, sub_parts=["common_data_tags"], origin_value="local.common_data_tags"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["ENVIRONMENT"],
+ origin_value="var.ENVIRONMENT"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["REGION"],
+ origin_value="var.REGION"),
+ ],
+ ),
+ (
+ "${merge(${local.common_tags},${local.common_data_tags},{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ [
+ VertexReference(BlockType.LOCALS, sub_parts=["common_tags"], origin_value="local.common_tags"),
+ VertexReference(BlockType.LOCALS, sub_parts=["common_data_tags"],
+ origin_value="local.common_data_tags"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["ENVIRONMENT"],
+ origin_value="var.ENVIRONMENT"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["REGION"],
+ origin_value="var.REGION"),
+ ],
+ ),
+ (
+ '${merge(var.tags, map("Name", "${var.name}", "data_classification", "none"))}',
+ [
+ VertexReference(BlockType.VARIABLE, sub_parts=["tags"],
+ origin_value="var.tags"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["name"],
+ origin_value="var.name"),
+ ]
+ ),
+ (
+ '${var.metadata_http_tokens_required ? "required" : "optional"}',
+ [
+ VertexReference(BlockType.VARIABLE, sub_parts=["metadata_http_tokens_required"],
+ origin_value="var.metadata_http_tokens_required"),
+ ]
+ ),
+ (
+ '${local.NAME[${module.bucket.bucket_name}]}-${local.TAIL}${var.gratuitous_var_default}',
+ [
+ VertexReference(BlockType.LOCALS, sub_parts=["NAME"],
+ origin_value="local.NAME"),
+ VertexReference(BlockType.MODULE, sub_parts=["bucket", "bucket_name"],
+ origin_value="module.bucket.bucket_name"),
+ VertexReference(BlockType.LOCALS, sub_parts=["TAIL"],
+ origin_value="local.TAIL"),
+ VertexReference(BlockType.VARIABLE, sub_parts=["gratuitous_var_default"],
+ origin_value="var.gratuitous_var_default"),
+ ]
+ ),
+ ]
+ for case in cases:
+ actual = get_referenced_vertices_in_value(value=case[0], aliases={}, resources_types=[])
+ assert actual == case[1], \
+ f"Case \"{case[0]}\" failed ❌:\n" \
+ f" Expected: \n{pprint.pformat([str(c) for c in case[1]], indent=2)}\n\n" \
+ f" Actual: \n{pprint.pformat([str(c) for c in actual], indent=2)}"
+ print(f"Case \"{case[0]}: ✅")
+
+ def test__attribute_has_nested_attributes_dictionary(self):
+ attributes = {'name': ['${var.lb_name}'], 'internal': [True], 'security_groups': ['${var.lb_security_group_ids}'], 'subnets': ['${var.subnet_id}'], 'enable_deletion_protection': [True], 'tags': {'Terraform': True, 'Environment': 'sophi-staging'}, 'resource_type': ['aws_alb'], 'tags.Terraform': True, 'tags.Environment': 'sophi-staging'}
+ self.assertTrue(attribute_has_nested_attributes(attribute_key='tags', attributes=attributes))
+ self.assertFalse(attribute_has_nested_attributes(attribute_key='name', attributes=attributes))
+ self.assertFalse(attribute_has_nested_attributes(attribute_key='tags.Environment', attributes=attributes))
+
+ def test__attribute_has_nested_attributes_list(self):
+ attributes = {'most_recent': [True], 'filter': [{'name': 'name', 'values': ['amzn-ami-hvm-*-x86_64-gp2']}, {'name': 'owner-alias', 'values': ['amazon']}], 'filter.0': {'name': 'name', 'values': ['amzn-ami-hvm-*-x86_64-gp2']}, 'filter.0.name': 'name', 'filter.0.values': ['amzn-ami-hvm-*-x86_64-gp2'], 'filter.0.values.0': 'amzn-ami-hvm-*-x86_64-gp2', 'filter.1': {'name': 'owner-alias', 'values': ['amazon']}, 'filter.1.name': 'owner-alias', 'filter.1.values': ['amazon'], 'filter.1.values.0': 'amazon'}
+ self.assertTrue(attribute_has_nested_attributes(attribute_key='filter', attributes=attributes))
+ self.assertTrue(attribute_has_nested_attributes(attribute_key='filter.1.values', attributes=attributes))
+ self.assertFalse(attribute_has_nested_attributes(attribute_key='filter.1.values.0', attributes=attributes))
+
diff --git a/tests/terraform/graph/variable_rendering/__init__.py b/tests/terraform/graph/variable_rendering/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/graph/variable_rendering/expected_data.py b/tests/terraform/graph/variable_rendering/expected_data.py
new file mode 100644
index 0000000000..6655bca4c3
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/expected_data.py
@@ -0,0 +1,52 @@
+from lark import Tree
+
+expected_postgres_module = {"create": True,
+ "name": "${var.name}",
+ "use_name_prefix": True,
+ "description": "Security Group managed by Terraform",
+ "vpc_id": "${var.vpc_id}",
+ "revoke_rules_on_delete": False,
+ "tags": {},
+ "ingress_rules": ["postgresql-tcp"],
+ "ingress_with_self": [{"rule": "all-all"}],
+ "number_of_computed_egress_rules": 0,
+ }
+
+expected_terragoat_local_resource_prefix = {'resource_prefix': {'value': 'test_id-acme-dev'}}
+
+expected_terragoat_db_instance = {'name': 'db1',
+ 'engine': 'mysql',
+ 'option_group_name': 'og-test_id-acme-dev',
+ 'parameter_group_name': 'pg-test_id-acme-dev',
+ 'db_subnet_group_name': 'sg-test_id-acme-dev',
+ 'vpc_security_group_ids': ['aws_security_group.default.id'],
+ 'identifier': 'rds-test_id-acme-dev',
+ 'password': 'Aa1234321Bb',
+ 'tags': {'Name': 'test_id-acme-dev-rds',
+ 'Environment': 'test_id-acme-dev'}
+ }
+
+
+expected_eks = {
+ "resource": {
+ "aws_eks_cluster.tf_eks": {
+ "version": ["1.19"],
+ "vpc_config": {
+ "security_group_ids": ["aws_security_group.master.id"],
+ "subnet_ids": "Tree('full_splat_expr_term', ['aws_subnet.eks', 'id'])"
+ },
+ }
+ }
+}
+
+
+expected_provider = {
+ "provider": {
+ "aws": {
+ "access_key": ["AKIAVAN"],
+ "secret_key": ["0CU4jk0"],
+ "region": ["us-west-2"],
+ }
+ }
+}
+
diff --git a/tests/terraform/graph/variable_rendering/resources/bad_ref_fallbacks_expected.json b/tests/terraform/graph/variable_rendering/resources/bad_ref_fallbacks_expected.json
new file mode 100644
index 0000000000..d55842d0cd
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/resources/bad_ref_fallbacks_expected.json
@@ -0,0 +1,23 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "BAD_VAR": ["var.var_not_there"],
+ "BAD_LOCAL": ["local.local_not_there"],
+ "BAD_MODULE": ["module.module_not_there.nope"],
+ "BAD_MODULE2": ["module.module_not_there"],
+ "BAD_MODULE3": ["module.module_not_there.nope.still_not"],
+ "QUOTE_IN_QUOTE_446": ["filemd5(\"path.module/templates/some-file.json\")"]
+ }
+ ],
+ "resource": [
+ {
+ "aws_lambda_function": {
+ "test_lambda": {
+ "source_code_hash": ["filemd5(\"path.module/templates/some-file.json\")"]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/variable_rendering/resources/colon_expected.json b/tests/terraform/graph/variable_rendering/resources/colon_expected.json
new file mode 100644
index 0000000000..b3b59affca
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/resources/colon_expected.json
@@ -0,0 +1,16 @@
+{
+ "colon.tf": {
+ "variable": [
+ {
+ "tags": {
+ "default": [[]],
+ "type": [
+ "list(object({'key': 'string', 'value': 'string', 'propagate_at_launch': 'bool'}))"
+ ]
+ }
+ }
+ ]
+ }
+}
+
+
diff --git a/tests/terraform/graph/variable_rendering/resources/doc_evaluations_verify_expected.json b/tests/terraform/graph/variable_rendering/resources/doc_evaluations_verify_expected.json
new file mode 100644
index 0000000000..cbc3281fb7
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/resources/doc_evaluations_verify_expected.json
@@ -0,0 +1,39 @@
+{
+ "main.tf": {
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "my_bucket": {
+ "region": ["us-west-2"],
+ "bucket": ["local.bucket_name"],
+ "acl": ["public-read"],
+ "force_destroy": [true]
+ }
+ }
+ }
+ ]
+ },
+ "variables.tf": {
+ "variable": [
+ {
+ "bucket_name": {
+ "default": [
+ "MyBucket"
+ ]
+ }
+ },
+ {
+ "acl": {
+ "default": [
+ "public-read"
+ ]
+ }
+ },
+ {
+ "region": {
+ "default": ["us-west-2"]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/variable_rendering/resources/merge_function_unresolved_var_expected.json b/tests/terraform/graph/variable_rendering/resources/merge_function_unresolved_var_expected.json
new file mode 100644
index 0000000000..2ad48da1cc
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/resources/merge_function_unresolved_var_expected.json
@@ -0,0 +1,34 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "common_tags": [
+ {
+ "Tag1": "one",
+ "Tag2": "two"
+ }
+ ]
+ }
+ ],
+ "variable": [
+ {
+ "ENV": {}
+ }
+ ],
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "bucket": {
+ "tags": [
+ {
+ "Tag1": "one",
+ "Tag2": "two",
+ "Name": "my-bucket-var.ENV"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/graph/variable_rendering/test_render_scenario.py b/tests/terraform/graph/variable_rendering/test_render_scenario.py
new file mode 100644
index 0000000000..84c7e686d7
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/test_render_scenario.py
@@ -0,0 +1,258 @@
+import json
+import os
+import re
+from unittest.case import TestCase
+
+import jmespath
+
+from checkov.terraform.checks.utils.dependency_path_handler import PATH_SEPARATOR, unify_dependency_path
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_builder.graph_to_tf_definitions import convert_graph_vertices_to_tf_definitions
+from checkov.terraform.graph_manager import TerraformGraphManager
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestRendererScenarios(TestCase):
+
+ def test_maze_of_variables(self):
+ self.go('maze_of_variables')
+
+ def test_merge_function(self):
+ self.go("merge_function")
+
+ def test_empty_file(self):
+ self.go("empty_file")
+
+ def test_simple_bucket_single_file(self):
+ self.go("simple_bucket_single_file")
+
+ def test_variable_defaults(self):
+ self.go("variable_defaults")
+
+ def test_variable_defaults_separate_files(self):
+ self.go("variable_defaults_separate_files")
+
+ def test_local_block(self):
+ self.go("local_block")
+
+ def test_local_bool_string_conversion(self):
+ self.go("local_bool_string_conversion")
+
+ def test_compound_local(self):
+ self.go("compound_local")
+
+ def test_concat_function(self):
+ self.go("concat_function")
+
+ def test_merge_function_unresolved_var(self):
+ self.go("merge_function_unresolved_var", replace_expected=True)
+
+ def test_tobool_function(self):
+ self.go("tobool_function", {"JUNK": ['tobool("invalid")']})
+
+ def test_tolist_function(self):
+ self.go("tolist_function")
+
+ def test_tomap_function(self):
+ self.go("tomap_function")
+
+ def test_map_function(self):
+ self.go("map_function", {"INVALID_ODD_ARGS": ['map("only one")']})
+
+ def test_tonumber_function(self):
+ self.go("tonumber_function", {"INVALID": ['tonumber("no")']})
+
+ def test_toset_function(self):
+ self.go("toset_function", {"VAR": [{'c', 'b', 'a'}]})
+
+ def test_tostring_function(self):
+ self.go("tostring_function", {"INVALID_ARRAY": ['tostring([])']})
+
+ def test_module_simple(self):
+ self.go("module_simple")
+
+ def test_module_simple_up_dir_ref(self):
+ self.go("module_simple_up_dir_ref")
+
+ def test_module_matryoshka(self):
+ self.go("module_matryoshka")
+
+ def test_list_default_622(self): # see https://github.com/bridgecrewio/checkov/issues/622
+ self.go("list_default_622", {"log_types_enabled": {'default': [['api',
+ 'audit',
+ 'authenticator',
+ 'controllerManager',
+ 'scheduler']],
+ 'type': ['list(string)']}})
+
+ def test_module_reference(self):
+ self.go("module_reference")
+
+ def test_module_output_reference(self):
+ self.go("module_output_reference")
+
+ def test_bad_ref_fallbacks(self):
+ self.go("bad_ref_fallbacks", replace_expected=True)
+
+ def test_doc_evaluations_verify(self):
+ self.go("doc_evaluations_verify", replace_expected=True)
+
+ def test_bad_tf(self):
+ # Note: this hits the _clean_bad_definitions internal function
+ self.go("bad_tf")
+
+ def test_colon(self):
+ # Note: this hits the _clean_bad_definitions internal function
+ self.go("colon", replace_expected=True)
+
+ def test_null_variables_651(self):
+ self.skipTest("different implementation, we keep the original variable reference")
+ self.go("null_variables_651")
+
+ def test_ternaries(self):
+ self.go("ternaries")
+
+ def test_ternary_793(self):
+ self.go("ternary_793")
+
+ def test_tfvars(self):
+ self.go("tfvars")
+
+ def test_account_dirs_and_modules(self):
+ self.go("account_dirs_and_modules")
+
+ def test_bogus_function(self):
+ self.skipTest("invalid values are not supported")
+ self.go("bogus_function")
+
+ def go(self, dir_name, different_expected=None, replace_expected=False):
+ os.environ['RENDER_VARIABLES_ASYNC'] = 'False'
+ os.environ['LOG_LEVEL'] = 'INFO'
+ different_expected = {} if not different_expected else different_expected
+ resources_dir = os.path.realpath(
+ os.path.join(TEST_DIRNAME, '../../parser/resources/parser_scenarios', dir_name))
+ graph_manager = TerraformGraphManager(dir_name, [dir_name])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+ got_tf_definitions, _ = convert_graph_vertices_to_tf_definitions(local_graph.vertices, resources_dir)
+ expected = load_expected(replace_expected, dir_name, resources_dir)
+
+ for expected_file, expected_block_type_dict in expected.items():
+ module_removed_path = expected_file
+ got_file = got_tf_definitions.get(module_removed_path)
+ self.assertIsNotNone(got_file)
+ for expected_block_type, expected_block_type_list in expected_block_type_dict.items():
+ got_block_type_list = got_file.get(expected_block_type)
+ self.assertIsNotNone(got_block_type_list)
+ for expected_block_dict in expected_block_type_list:
+ for expected_block_name, expected_block_val in expected_block_dict.items():
+ if expected_block_type != BlockType.RESOURCE:
+ found = self.match_blocks(expected_block_val, different_expected, got_block_type_list,
+ expected_block_name)
+ else:
+ found = self.match_resources(expected_block_val, different_expected, got_block_type_list,
+ expected_block_name)
+ self.assertTrue(found,
+ f"expected to find block {expected_block_dict} from file {expected_file} in graph")
+
+ def match_blocks(self, expected_block_val, different_expected, got_block_type_list, expected_block_name):
+ for got_block_dict in got_block_type_list:
+ for got_block_name, got_block_val in got_block_dict.items():
+ if got_block_name == expected_block_name:
+ if got_block_name in different_expected:
+ expected_block_val = different_expected.get(got_block_name)
+ self.assertEqual(expected_block_val, got_block_val,
+ f"failed to match block [{got_block_name}].\nExpected: {expected_block_val}\nActual: {got_block_val}\n")
+ print(f"success {got_block_name}: {got_block_val}")
+ return True
+
+ return False
+
+ def match_resources(self, expected_block_val, different_expected, got_block_type_list, expected_block_name):
+ for got_block_dict in got_block_type_list:
+ for got_block_name, got_block_val in got_block_dict.items():
+ if got_block_name == expected_block_name:
+ expected_resource_name = list(expected_block_val.keys())[0]
+ got_resource_name = list(got_block_val.keys())[0]
+ if expected_resource_name != got_resource_name:
+ continue
+ if expected_resource_name in different_expected:
+ expected_block_val = {expected_resource_name: different_expected.get(expected_resource_name)}
+ self.assertEqual(expected_block_val, got_block_val,
+ f"failed to match block [{got_block_name}].\nExpected: {expected_block_val}\nActual: {got_block_val}\n")
+ print(f"success {got_block_name}: {got_block_val}")
+ return True
+
+ return False
+
+
+def load_expected(replace_expected, dir_name, resources_dir):
+ if replace_expected:
+ expected_file_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources")
+ old_expected = load_expected_data(f"{dir_name}_expected.json", expected_file_dir)
+ expected = {}
+ for file_path in old_expected:
+ new_file_path = file_path.replace(expected_file_dir, resources_dir)
+ expected[new_file_path] = old_expected[file_path]
+ else:
+ expected = load_expected_data("expected.json", resources_dir)
+ return expected
+
+
+def load_expected_data(source_file_name, dir_path):
+ expected_path = os.path.join(dir_path, source_file_name)
+ if not os.path.exists(expected_path):
+ return None
+
+ with open(expected_path, "r") as f:
+ expected_data = json.load(f)
+
+ # Convert to absolute path: "buckets/bucket.tf[main.tf#0]"
+ # ^^^^^^^^^^^^^^^^^ ^^^^^^^
+ # HERE & HERE
+ #
+ resolved_pattern = re.compile(r"(.+)\[(.+)#(\d+)]") # groups: location (1), referrer (2), index (3)
+
+ # Expected files should have the filenames relative to their base directory, but the parser will
+ # use the absolute path. This loop with replace relative filenames with absolute.
+ keys = list(expected_data.keys())
+ for key in keys:
+ # NOTE: Sometimes keys have module referrers, sometimes they don't
+
+ match = resolved_pattern.match(key)
+ if match:
+ new_key = _make_module_ref_absolute(match, dir_path)
+ else:
+ if os.path.isabs(key):
+ continue
+ new_key = os.path.join(dir_path, key)
+ expected_data[new_key] = expected_data[key]
+ del expected_data[key]
+
+ for resolved_list in jmespath.search("*.module[].*[].__resolved__", expected_data):
+ for list_index in range(0, len(resolved_list)):
+ match = resolved_pattern.match(resolved_list[list_index])
+ assert match is not None, f"Unexpected module resolved data: {resolved_list[list_index]}"
+ resolved_list[list_index] = _make_module_ref_absolute(match, dir_path)
+ # print(f"{match[0]} -> {resolved_list[list_index]}")
+
+ return expected_data
+
+
+def _make_module_ref_absolute(match, dir_path) -> str:
+ module_location = match[1]
+ if not os.path.isabs(module_location):
+ module_location = os.path.join(dir_path, module_location)
+
+ module_referrer = match[2]
+ if PATH_SEPARATOR in module_referrer:
+ module_referrer_fixed = []
+ if '#' in module_referrer:
+ module_referrer = module_referrer[:-2]
+ for ref in module_referrer.split(PATH_SEPARATOR):
+ if not os.path.isabs(ref):
+ module_referrer_fixed.append(os.path.join(dir_path, ref))
+ module_referrer = unify_dependency_path(module_referrer_fixed)
+ else:
+ module_referrer = os.path.join(dir_path, module_referrer)
+ return f"{module_location}[{module_referrer}#{match[3]}]"
diff --git a/tests/terraform/graph/variable_rendering/test_renderer.py b/tests/terraform/graph/variable_rendering/test_renderer.py
new file mode 100644
index 0000000000..4e3a998e3a
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/test_renderer.py
@@ -0,0 +1,182 @@
+import os
+from unittest.case import TestCase
+
+from checkov.terraform.graph_builder.graph_components.block_types import BlockType
+from checkov.terraform.graph_manager import TerraformGraphManager
+from checkov.terraform.graph_manager import GraphManager
+from tests.terraform.graph.variable_rendering.expected_data import *
+
+TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+
+
+class TestRenderer(TestCase):
+ def setUp(self) -> None:
+ os.environ['UNIQUE_TAG'] = ''
+ os.environ['RENDER_ASYNC_MAX_WORKERS'] = '50'
+ os.environ['RENDER_VARIABLES_ASYNC'] = 'False'
+
+ def test_render_local(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_local')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_local = {'bucket_name': 'test_bucket_name'}
+ expected_resource = {'region': 'us-west-2', 'bucket': expected_local['bucket_name']}
+
+ self.compare_vertex_attributes(local_graph, expected_local, BlockType.LOCALS, 'bucket_name')
+ self.compare_vertex_attributes(local_graph, expected_resource, BlockType.RESOURCE, 'aws_s3_bucket.template_bucket')
+
+ def test_render_variable(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_variable')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_resource = {'region': "us-west-2", 'bucket': "test_bucket_name", "acl": "acl", "force_destroy": True}
+
+ self.compare_vertex_attributes(local_graph, expected_resource, BlockType.RESOURCE, 'aws_s3_bucket.template_bucket')
+
+ def test_render_local_from_variable(self):
+ resources_dir = os.path.join(TEST_DIRNAME,
+ '../resources/variable_rendering/render_local_from_variable')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_local = {'bucket_name': 'test_bucket_name'}
+
+ self.compare_vertex_attributes(local_graph, expected_local, BlockType.LOCALS, 'bucket_name')
+
+ def test_general_example(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/general_example')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_provider = {'profile': 'default', 'region': 'us-east-1', 'alias': 'east1'}
+ expected_local = {'bucket_name': {'val': 'MyBucket'}}
+ expected_resource = {'region': 'us-west-2', 'bucket': expected_local['bucket_name']}
+
+ self.compare_vertex_attributes(local_graph, expected_provider, BlockType.PROVIDER, 'aws.east1')
+ self.compare_vertex_attributes(local_graph, expected_local, BlockType.LOCALS, 'bucket_name')
+ self.compare_vertex_attributes(local_graph, expected_resource, BlockType.RESOURCE, 'aws_s3_bucket.template_bucket')
+
+ def test_terragoat_db_app(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_terragoat_db_app')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ self.compare_vertex_attributes(local_graph, expected_terragoat_local_resource_prefix, BlockType.LOCALS, 'resource_prefix')
+ self.compare_vertex_attributes(local_graph, expected_terragoat_db_instance, BlockType.RESOURCE, "aws_db_instance.default")
+
+ def test_render_nested_modules(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_nested_modules')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_aws_instance = {"instance_type": "bar"}
+ self.compare_vertex_attributes(local_graph, expected_aws_instance, BlockType.RESOURCE, "aws_instance.example")
+ expected_output_bucket_acl = {"value": "z"}
+ self.compare_vertex_attributes(local_graph, expected_output_bucket_acl, BlockType.OUTPUT, "bucket_acl")
+
+ def compare_vertex_attributes(self, local_graph, expected_attributes, block_type, block_name):
+ vertex = local_graph.vertices[local_graph.vertices_block_name_map[block_type][block_name][0]]
+ print(f'breadcrumbs = {vertex.breadcrumbs}')
+ vertex_attributes = vertex.get_attribute_dict()
+ for attribute_key, expected_value in expected_attributes.items():
+ actual_value = vertex_attributes.get(attribute_key)
+ self.assertEqual(expected_value, actual_value, f'error during comparing {block_type} in attribute key: {attribute_key}')
+
+ def test_breadcrumbs(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/s3_bucket')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+ vertices = local_graph.vertices
+ s3_vertex = list(filter(lambda vertex: vertex.block_type == BlockType.RESOURCE, vertices))[0]
+ changed_attributes = list(s3_vertex.changed_attributes.keys())
+ self.assertListEqual(changed_attributes, ['versioning.enabled', 'acl'])
+
+ for breadcrumbs in s3_vertex.changed_attributes.values():
+ self.assertEqual(1, len(breadcrumbs))
+
+ acl_origin_vertex = s3_vertex.changed_attributes.get('acl')[0]
+ matching_acl_vertex = vertices[acl_origin_vertex]
+ self.assertEqual('acl', matching_acl_vertex.name)
+
+ versioning_origin_vertex = s3_vertex.changed_attributes.get('versioning.enabled')[0]
+ matching_versioning_vertex = vertices[versioning_origin_vertex]
+ self.assertEqual('is_enabled', matching_versioning_vertex.name)
+
+ def test_multiple_breadcrumbs(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/general_example')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+ vertices = local_graph.vertices
+ s3_vertex = list(filter(lambda vertex: vertex.block_type == BlockType.RESOURCE, vertices))[0]
+ changed_attributes = list(s3_vertex.changed_attributes.keys())
+ self.assertListEqual(changed_attributes, ['region', 'bucket'])
+
+ bucket_vertices_ids_list = s3_vertex.changed_attributes.get('bucket')
+ self.assertEqual(2, len(bucket_vertices_ids_list))
+
+ self.assertEqual(BlockType.VARIABLE, vertices[bucket_vertices_ids_list[0]].block_type)
+ self.assertEqual('bucket_name', vertices[bucket_vertices_ids_list[0]].name)
+ self.assertEqual(vertices[bucket_vertices_ids_list[0]].name, s3_vertex.breadcrumbs['bucket'][0]['name'])
+
+ self.assertEqual(BlockType.LOCALS, vertices[bucket_vertices_ids_list[1]].block_type)
+ self.assertEqual('bucket_name', vertices[bucket_vertices_ids_list[1]].name)
+ self.assertEqual(vertices[bucket_vertices_ids_list[1]].name, s3_vertex.breadcrumbs['bucket'][1]['name'])
+
+ def test_render_lambda(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_lambda')
+ graph_manager = TerraformGraphManager('acme', ['acme'])
+ local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ expected_aws_lambda_permission = {'count': 0, 'statement_id': 'test_statement_id', 'action': 'lambda:InvokeFunction', 'function_name': 'my-func', 'principal': 'dumbeldor', 'resource_type': 'aws_lambda_permission'}
+
+ self.compare_vertex_attributes(local_graph, expected_aws_lambda_permission, BlockType.RESOURCE, "aws_lambda_permission.test_lambda_permissions")
+
+ def test_eks(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/terraform-aws-eks-master')
+ graph_manager = TerraformGraphManager('eks', ['eks'])
+ local_graph, tf_def = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ for v in local_graph.vertices:
+ expected_v = expected_eks.get(v.block_type, {}).get(v.name)
+ if expected_v:
+ for attribute_key, expected_value in expected_v.items():
+ actual_value = v.attributes.get(attribute_key)
+ self.assertEqual(expected_value, actual_value,
+ f'error during comparing {v.block_type} in attribute key: {attribute_key}')
+
+
+ def test_dict_tfvar(self):
+ resources_dir = os.path.join(TEST_DIRNAME, '../resources/variable_rendering/render_dictionary_tfvars')
+ graph_manager = TerraformGraphManager('d', ['d'])
+ local_graph, tf_def = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)
+
+ for v in local_graph.vertices:
+ expected_v = expected_provider.get(v.block_type, {}).get(v.name)
+ if expected_v:
+ for attribute_key, expected_value in expected_v.items():
+ actual_value = v.attributes.get(attribute_key)
+ self.assertEqual(expected_value, actual_value,
+ f'error during comparing {v.block_type} in attribute key: {attribute_key}')
+
+ def test_graph_rendering_order(self):
+ resource_path = os.path.join(TEST_DIRNAME, "..", "resources", "module_rendering", "example")
+ graph_manager = TerraformGraphManager('m', ['m'])
+ local_graph, tf_def = graph_manager.build_graph_from_source_directory(resource_path, render_variables=True)
+ module_vertices = list(filter(lambda v: v.block_type == BlockType.MODULE, local_graph.vertices))
+ existing = set()
+ self.assertEqual(6, len(local_graph.edges))
+ for e in local_graph.edges:
+ if e in existing:
+ self.fail("No 2 edges should be aimed at the same vertex in this example")
+ else:
+ existing.add(e)
+ count = 0
+ found = 0
+ for v in module_vertices:
+ if v.name == 'second-mock':
+ found += 1
+ if v.attributes['input'] == ['aws_s3_bucket.some-bucket.arn']:
+ count += 1
+ self.assertEqual(found, count, f"Expected all instances to have the same value, found {found} instances but only {count} correct values")
diff --git a/tests/terraform/graph/variable_rendering/test_string_evaluation.py b/tests/terraform/graph/variable_rendering/test_string_evaluation.py
new file mode 100644
index 0000000000..d073b5b664
--- /dev/null
+++ b/tests/terraform/graph/variable_rendering/test_string_evaluation.py
@@ -0,0 +1,339 @@
+import os
+from unittest import TestCase
+
+from checkov.terraform.variable_rendering.evaluate_terraform import evaluate_terraform, replace_string_value, \
+ remove_interpolation
+
+
+class TestTerraformEvaluation(TestCase):
+ def test_directive(self):
+ input_str = '"Hello, %{ if "d" != "" }named%{ else }unnamed%{ endif }!"'
+ expected = 'Hello, named!'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_condition(self):
+ input_str = '"2 > 0 ? bigger : smaller"'
+ expected = 'bigger'
+ self.assertEqual(expected, evaluate_terraform(input_str).strip())
+
+ input_str = '"2 > 5 ? bigger : smaller"'
+ expected = 'smaller'
+ self.assertEqual(expected, evaluate_terraform(input_str).strip())
+
+ def test_format(self):
+ input_str = '"format("Hello, %s!", "Ander")"'
+ expected = 'Hello, Ander!'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = '"format("There are %d lights", 4)"'
+ expected = 'There are 4 lights'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_formatlist(self):
+ input_str = '"formatlist("Hello, %s!", ["Valentina", "Ander", "Olivia", "Sam"])"'
+ expected = ['Hello, Valentina!', 'Hello, Ander!', 'Hello, Olivia!', 'Hello, Sam!']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_join(self):
+ input_str = 'join(", ", ["foo", "bar", "baz"])'
+ expected = 'foo, bar, baz'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'join(", ", ["foo"])'
+ expected = 'foo'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_regex(self):
+ input_str = 'regex("[a-z]+", "53453453.345345aaabbbccc23454")'
+ expected = 'aaabbbccc'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'regex("[a-z]+", "53453453.34534523454")'
+ expected = ''
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'regex("^(?:(?P[^:/?#]+):)?(?://(?P[^/?#]*))?", "https://terraform.io/docs/")'
+ expected = {"authority":"terraform.io", "scheme" : "https"}
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'regex(r"(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)", "2019-02-01")'
+ expected = ["2019","02","01"]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'regex("[a-z]+", "53453453.34534523454")'
+ expected = ''
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_regexall(self):
+ input_str = 'regexall("[a-z]+", "1234abcd5678efgh9")'
+ expected = ["abcd","efgh"]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'length(regexall("[a-z]+", "1234abcd5678efgh9"))'
+ expected = 2
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'length(regexall("[a-z]+", "123456789")) > 0'
+ expected = False
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_replace(self):
+ input_str = 'replace("1 + 2 + 3", "+", "-")'
+ expected = -4
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_substr(self):
+ input_str = 'substr("hello world", 1, 4)'
+ expected = 'ello'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_trim(self):
+ input_str = 'trim("?!hello?!", "!?")'
+ expected = 'hello'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_trimprefix(self):
+ input_str = 'trimprefix("helloworld", "hello")'
+ expected = 'world'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_upper(self):
+ input_str = 'upper("hello")'
+ expected = 'HELLO'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'upper("алло!")'
+ expected = 'АЛЛО!'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_chunklist(self):
+ input_str = 'chunklist(["a", "b", "c", "d", "e"], 2)'
+ expected = [["a", "b"], ["c", "d"], ["e"]]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_coalese(self):
+ input_str = 'coalesce("a", "b")'
+ expected = 'a'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'coalesce("", "b")'
+ expected = 'b'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'coalesce(1, 2)'
+ expected = 1
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_coalescelist(self):
+ input_str = 'coalescelist(["a", "b"], ["c", "d"])'
+ expected = ['a', 'b']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'coalescelist([], ["c", "d"])'
+ expected = ["c", "d"]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_compact(self):
+ input_str = 'compact(["a", "", "b", "c"])'
+ expected = ['a', 'b', 'c']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_concat(self):
+ input_str = 'concat(["a", ""], ["b", "c"])'
+ expected = ['a', '', 'b', 'c']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'concat([\'postgresql-tcp\'],[],[\'\'])'
+ expected = ['postgresql-tcp', '']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_concat_dictionaries(self):
+ input_str = "concat([{'key':'a','value':'a'},{'key':'b','value':'b'}, \"{'key':'d','value':'d'}\"],,[{'key':'c','value':'c'}],)"
+ expected = [{'key':'a','value':'a'},{'key':'b','value':'b'},"{'key':'d','value':'d'}",{'key':'c','value':'c'}]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'concat([\'postgresql-tcp\'],[],[\'\'])'
+ expected = ['postgresql-tcp', '']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_distinct(self):
+ input_str = 'distinct(["a", "b", "a", "c", "d", "b"])'
+ expected = ['a', 'b', 'c', 'd']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_flatten(self):
+ input_str = 'flatten([["a", "b"], [], ["c"]])'
+ expected = ['a', 'b', 'c']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = 'flatten([[["a", "b"], []], ["c"]])'
+ expected = ['a', 'b', 'c']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_index(self):
+ input_str = 'index(["a", "b", "c"], "b")'
+ expected = 1
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_keys(self):
+ input_str = 'keys({"a"="ay", "b"="bee"})'
+ expected = ["a", "b"]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_list(self):
+ input_str = 'list("a", "b", "c")'
+ expected = ['a', 'b', 'c']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_lookup(self):
+ input_str = 'lookup({"a"="ay", "b"="bee"}, "a", "what?")'
+ expected = 'ay'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_matchkeys(self):
+ input_str = 'matchkeys(["i-123", "i-abc", "i-def"], ["us-west", "us-east", "us-east"], ["us-east"])'
+ expected = ["i-abc", "i-def"]
+ actual = evaluate_terraform(input_str)
+ for elem in actual:
+ if elem not in expected:
+ self.fail(f'expected to find {elem} in {expected}. Got {actual}')
+
+ def test_merge(self):
+ input_str = 'merge({"a"="b", "c"="d"}, {"e"="f", "c"="z"})'
+ expected = {"a":"b", "c":"z","e":"f"}
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_merge2(self):
+ input_str = 'merge({"a"="b", "c"="d"}, {"e"="f", "c"="z"}, {"r"="o", "t"="m"})'
+ expected = {"a":"b", "c":"z","e":"f","r":"o","t":"m"}
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_merge_multiline(self):
+ input_str = "merge(\n{'Tag1':'one','Tag2':'two'},\n{'Tag4' = 'four'},\n{'Tag2'='multiline_tag2'})"
+ expected = {'Tag1': 'one', 'Tag2': 'multiline_tag2', 'Tag4': 'four'}
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+
+ def test_reverse(self):
+ input_str = 'reverse([1, 2, 3])'
+ expected = [3, 2, 1]
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_sort(self):
+ input_str = 'sort(compact(distinct(concat([\'postgresql-tcp\'],[],[\'\']))))'
+ expected = ['postgresql-tcp']
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_condition2(self):
+ input_str = 'us-west-2 == "something to produce false" ? true : false'
+ expected = 'false'
+ self.assertEqual(expected, evaluate_terraform(input_str).strip())
+
+ def test_complex_merge(self):
+ cases = [
+ ("merge(local.one, local.two)",
+ "merge(local.one, local.two)"),
+ ("merge({\"Tag4\" = \"four\"}, {\"Tag5\" = \"five\"})",
+ {"Tag4" : "four", "Tag5" : "five"}),
+ ("merge({\"a\"=\"b\"}, {\"b\"=[1,2], \"c\"=\"z\"}, {\"d\"=3})",
+ {"a":"b", "b":[1,2], "c":"z", "d":3}),
+ ('merge({\'a\': \'}, evil\'})',
+ {"a": '}, evil'}),
+ ('merge(local.common_tags,,{\'Tag4\': \'four\'},,{\'Tag2\': \'Dev\'},)',
+ 'merge(local.common_tags,{\'Tag4\': \'four\'},{\'Tag2\': \'Dev\'},)')
+ ]
+ for case in cases:
+ input_str = case[0]
+ expected = input_str if case[1] is None else case[1]
+ actual = evaluate_terraform(input_str)
+ assert actual == expected, f"Case \"{input_str}\" failed. Expected: {expected} Actual: {actual}"
+
+ def test_map_access(self):
+ input_str = '{\'module-input-bucket\':\'mapped-bucket-name\'}[module-input-bucket]-works-yay'
+ expected = 'mapped-bucket-name-works-yay'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ input_str = '{"module-input-bucket":"mapped-bucket-name"}[module-input-bucket]-works-yay'
+ expected = 'mapped-bucket-name-works-yay'
+ self.assertEqual(expected, evaluate_terraform(input_str))
+
+ def test_replace_with_map(self):
+ original_str = '{\'module-input-bucket\':\'mapped-bucket-name\'}[module.bucket.name]-works-yay'
+ replaced = replace_string_value(original_str, "module.bucket.name", "module-input-bucket", keep_origin=False)
+ expected = '{\'module-input-bucket\':\'mapped-bucket-name\'}[module-input-bucket]-works-yay'
+ self.assertEqual(expected, replaced)
+
+ def test_replace_interpolation(self):
+ original_str = '${mapped-bucket-name}[module.bucket.name]-works-yay'
+ replaced = replace_string_value(original_str, "module.bucket.name", "module-input-bucket", keep_origin=False)
+ expected = '${mapped-bucket-name}[module-input-bucket]-works-yay'
+ self.assertEqual(expected, replaced)
+
+ def test_remove_interpolation1(self):
+ original_str = '${merge(local.common_tags,local.common_data_tags,{\'Name\':\'Bob-${local.static1}-${local.static2}\'})}'
+ replaced = remove_interpolation(original_str)
+ expected = 'merge(local.common_tags,local.common_data_tags,{\'Name\':\'Bob-local.static1-local.static2\'})'
+ self.assertEqual(expected, replaced)
+
+ def test_jsonencode(self):
+ cases = [
+ ("jsonencode(['a', 42, true, null])", ["a", 42, True, None]),
+ ("jsonencode({'a': 'b'})", {"a": "b"}),
+ ("jsonencode({'a' = 'b'})", {"a": "b"}),
+ ("jsonencode({'a' = 42})", {"a": 42}),
+ ("jsonencode({'a' = true})", {"a": True}),
+ ("jsonencode({'a' = false})", {"a": False}),
+ ("jsonencode({'a' = null})", {"a": None}),
+ ("jsonencode({'a' = ['b', 'c']})", {"a": ["b", "c"]}),
+ ("jsonencode({'a' = jsonencode(['b', 'c'])})", {"a": ["b", "c"]}),
+ ]
+
+ for input_str, expected in cases:
+ with self.subTest(input_str):
+ assert evaluate_terraform(input_str) == expected
+
+ def test_block_file_write(self):
+ temp_file_path = "/tmp/file_shouldnt_create"
+ input_str = "[x for x in {}.__class__.__bases__[0].__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('date >> /tmp/file_shouldnt_create')"
+ evaluated = evaluate_terraform(input_str)
+ self.assertEqual(input_str, evaluated)
+ self.assertFalse(os.path.exists(temp_file_path))
+
+ def test_block_file_write2(self):
+ temp_file_path = "/tmp/file_shouldnt_create_vuln"
+ input_str = "(lambda: [x for x in {}.__class__.__bases__[0].__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('date >> /tmp/file_shouldnt_create_vuln'))()"
+ evaluated = evaluate_terraform(input_str)
+ self.assertEqual(input_str, evaluated)
+ self.assertFalse(os.path.exists(temp_file_path))
+
+ def test_block_file_write_lower(self):
+ temp_file_path = "/tmp/file_shouldnt_create"
+ input_str = "[x for x in parsint.__bases__[0].__subclasses__()][134]()._module.__builtins__['__IMPORT__'.lower()]('os').system('date >> /tmp/file_shouldnt_create')"
+ evaluated = evaluate_terraform(input_str)
+ self.assertEqual(input_str, evaluated)
+ self.assertFalse(os.path.exists(temp_file_path))
+
+ def test_block_math_expr(self):
+ input_str = "__import__('math').sqrt(25)"
+ evaluated = evaluate_terraform(input_str)
+ self.assertEqual(input_str, evaluated)
+
+ def test_block_segmentation_fault(self):
+ # in this test, the following code is causing segmentation fault if evaluated
+ input_str = """
+(lambda fc=(
+ lambda n: [
+ c for c in
+ ().__class__.__bases__[0].__subclasses__()
+ if c.__name__ == n
+ ][0]
+ ):
+ fc("function")(
+ fc("code")(
+ 0,0,0,0,0,b'test',(),(),(),"","",0,b'test'
+ ),{}
+ )()
+)()
+"""
+ evaluated = evaluate_terraform(input_str)
+ self.assertEqual(input_str.replace("\n", ""), evaluated)
diff --git a/tests/terraform/module_loading/test_registry.py b/tests/terraform/module_loading/test_registry.py
index 0cfe0f838d..40d63d947b 100644
--- a/tests/terraform/module_loading/test_registry.py
+++ b/tests/terraform/module_loading/test_registry.py
@@ -26,16 +26,7 @@ def test_load_terraform_registry_check_cache(self):
registry = ModuleLoaderRegistry(download_external_modules=True)
source1 = "https://github.com/bridgecrewio/checkov_not_working1.git"
registry.load(current_dir=self.current_dir, source=source1, source_version="latest")
- self.assertTrue(source1 in registry.failed_urls_cache)
+ self.assertIn(source1, registry.failed_urls_cache)
source2 = "https://github.com/bridgecrewio/checkov_not_working2.git"
registry.load(current_dir=self.current_dir, source=source2, source_version="latest")
- self.assertTrue(source1 in registry.failed_urls_cache and source2 in registry.failed_urls_cache)
-
- def test_load_local_module_absolute_path(self):
- registry = ModuleLoaderRegistry(download_external_modules=True)
- source = "/some/module"
- try:
- registry.load(current_dir=self.current_dir, source=source, source_version="latest")
- self.assertEqual(1, 2, 'Module loading should have thrown an error')
- except FileNotFoundError as e:
- self.assertEqual(str(e), source)
+ self.assertIn(source1 in registry.failed_urls_cache and source2, registry.failed_urls_cache)
diff --git a/tests/terraform/parser/resources/malformed_outputs/main.tf b/tests/terraform/parser/resources/malformed_outputs/main.tf
new file mode 100644
index 0000000000..e902b0315a
--- /dev/null
+++ b/tests/terraform/parser/resources/malformed_outputs/main.tf
@@ -0,0 +1,17 @@
+## Outputs
+
+output "cluster_name" {
+ value = "${aws_eks_cluster.eks.name}"
+}
+
+output "kubeconfig" {
+ value = "${local.kubeconfig}"
+}
+
+output "aws-auth-cm.yaml" {
+ value = "${local.aws-auth-cm}"
+}
+
+//output "config_map_aws_auth" {
+// value = "${local.config_map_aws_auth}"
+//}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/main.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/main.tf
new file mode 100644
index 0000000000..8612adac73
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/main.tf
@@ -0,0 +1,6 @@
+module "mydb" {
+ source = "../../../modules/db"
+ DB_INSTANCE_TYPE = "${var.DB_INSTANCE_TYPE}"
+ ENGINE_VERSION = "${var.ENGINE_VERSION}"
+ ENCRYPTED = "${var.ENCRYPTED}"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/terraform.tfvars b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/terraform.tfvars
new file mode 100644
index 0000000000..2c30116866
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/terraform.tfvars
@@ -0,0 +1,3 @@
+ENGINE_VERSION = "11"
+DB_INSTANCE_TYPE = "db.t3.small"
+ENCRYPTED = true
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/variables.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/variables.tf
new file mode 100644
index 0000000000..ec01d45b58
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myaccount/us-east-1/variables.tf
@@ -0,0 +1,3 @@
+variable "ENGINE_VERSION" {}
+variable "DB_INSTANCE_TYPE" {}
+variable "ENCRYPTED" {}
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/main.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/main.tf
new file mode 100644
index 0000000000..5f3f5178ab
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/main.tf
@@ -0,0 +1,6 @@
+module "mydb" {
+ source = "../../../modules/db"
+ DB_INSTANCE_TYPE = "${var.DB_INSTANCE_TYPE}"
+ ENGINE_VERSION = "${var.ENGINE_VERSION}"
+ ENCRYPTED = "${var.ENCRYPTED}"
+}
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/terraform.tfvars b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/terraform.tfvars
new file mode 100644
index 0000000000..f9c7380a84
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/terraform.tfvars
@@ -0,0 +1,3 @@
+ENGINE_VERSION = "11"
+DB_INSTANCE_TYPE = "db.t9.mega"
+ENCRYPTED = true
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/variables.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/variables.tf
new file mode 100644
index 0000000000..ec01d45b58
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/envs/myotheraccount/us-east-1/variables.tf
@@ -0,0 +1,3 @@
+variable "ENGINE_VERSION" {}
+variable "DB_INSTANCE_TYPE" {}
+variable "ENCRYPTED" {}
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/expected.json b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/expected.json
new file mode 100644
index 0000000000..fb750e8620
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/expected.json
@@ -0,0 +1,138 @@
+{
+ "envs/myaccount/us-east-1/main.tf": {
+ "module": [
+ {
+ "mydb": {
+ "source": ["../../../modules/db"],
+ "DB_INSTANCE_TYPE": ["db.t3.small"],
+ "ENGINE_VERSION": [11],
+ "ENCRYPTED": [true],
+ "__resolved__": [
+ "modules/db/db.tf[envs/myaccount/us-east-1/main.tf#0]",
+ "modules/db/variables.tf[envs/myaccount/us-east-1/main.tf#0]"
+ ]
+ }
+ }
+ ]
+ },
+ "envs/myaccount/us-east-1/variables.tf": {
+ "variable": [
+ {
+ "ENGINE_VERSION": {}
+ },
+ {
+ "DB_INSTANCE_TYPE": {}
+ },
+ {
+ "ENCRYPTED": {}
+ }
+ ]
+ },
+ "modules/db/variables.tf[envs/myaccount/us-east-1/main.tf#0]": {
+ "variable": [
+ {
+ "DB_DELETION_PROTECTION": {
+ "default": [true]
+ }
+ },
+ {
+ "ENGINE_VERSION": {
+ "default": [11]
+ }
+ },
+ {
+ "DB_INSTANCE_TYPE": {
+ "default": ["db.t3.small"]
+ }
+ },
+ {
+ "ENCRYPTED": {}
+ }
+ ]
+ },
+ "modules/db/db.tf[envs/myaccount/us-east-1/main.tf#0]": {
+ "resource": [
+ {
+ "aws_db_instance": {
+ "db": {
+ "name": ["my_db"],
+ "instance_class": ["db.t3.small"],
+ "engine": ["postgres"],
+ "engine_version": [11],
+ "storage_type": ["gp2"],
+ "deletion_protection": [true],
+ "storage_encrypted": [true]
+ }
+ }
+ }
+ ]
+ },
+ "envs/myotheraccount/us-east-1/main.tf": {
+ "module": [
+ {
+ "mydb": {
+ "source": ["../../../modules/db"],
+ "DB_INSTANCE_TYPE": ["db.t9.mega"],
+ "ENGINE_VERSION": [11],
+ "ENCRYPTED": [true],
+ "__resolved__": [
+ "modules/db/db.tf[envs/myotheraccount/us-east-1/main.tf#0]",
+ "modules/db/variables.tf[envs/myotheraccount/us-east-1/main.tf#0]"
+ ]
+ }
+ }
+ ]
+ },
+ "envs/myotheraccount/us-east-1/variables.tf": {
+ "variable": [
+ {
+ "ENGINE_VERSION": {}
+ },
+ {
+ "DB_INSTANCE_TYPE": {}
+ },
+ {
+ "ENCRYPTED": {}
+ }
+ ]
+ },
+ "modules/db/variables.tf[envs/myotheraccount/us-east-1/main.tf#0]": {
+ "variable": [
+ {
+ "DB_DELETION_PROTECTION": {
+ "default": [true]
+ }
+ },
+ {
+ "ENGINE_VERSION": {
+ "default": [11]
+ }
+ },
+ {
+ "DB_INSTANCE_TYPE": {
+ "default": ["db.t9.mega"]
+ }
+ },
+ {
+ "ENCRYPTED": {}
+ }
+ ]
+ },
+ "modules/db/db.tf[envs/myotheraccount/us-east-1/main.tf#0]": {
+ "resource": [
+ {
+ "aws_db_instance": {
+ "db": {
+ "name": ["my_db"],
+ "instance_class": ["db.t9.mega"],
+ "engine": ["postgres"],
+ "engine_version": [11],
+ "storage_type": ["gp2"],
+ "deletion_protection": [true],
+ "storage_encrypted": [true]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/db.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/db.tf
new file mode 100644
index 0000000000..2aa25a9c43
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/db.tf
@@ -0,0 +1,9 @@
+resource "aws_db_instance" "db" {
+ name = "my_db"
+ instance_class = "${var.DB_INSTANCE_TYPE}"
+ engine = "postgres"
+ engine_version = "${var.ENGINE_VERSION}"
+ storage_type = "gp2"
+ deletion_protection = "${var.DB_DELETION_PROTECTION}"
+ storage_encrypted = "${var.ENCRYPTED}"
+}
diff --git a/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/variables.tf b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/variables.tf
new file mode 100644
index 0000000000..0d105ff64b
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/account_dirs_and_modules/modules/db/variables.tf
@@ -0,0 +1,13 @@
+variable "DB_DELETION_PROTECTION" {
+ default = true
+}
+
+variable "ENGINE_VERSION" {
+ default = "9.5"
+}
+
+variable "DB_INSTANCE_TYPE" {
+ default = "db.t3.medium"
+}
+
+variable "ENCRYPTED" {}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/bogus_function/expected.json b/tests/terraform/parser/resources/parser_scenarios/bogus_function/expected.json
new file mode 100644
index 0000000000..3b3baaf01c
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/bogus_function/expected.json
@@ -0,0 +1,5 @@
+{
+ "main.tf": {
+ "value": ["${not_a_real_function(1,2,3,4)}"]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/bogus_function/main.tf b/tests/terraform/parser/resources/parser_scenarios/bogus_function/main.tf
new file mode 100644
index 0000000000..030dc8232a
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/bogus_function/main.tf
@@ -0,0 +1 @@
+value = not_a_real_function(1, 2, 3, 4)
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/colon/colon.tf b/tests/terraform/parser/resources/parser_scenarios/colon/colon.tf
new file mode 100644
index 0000000000..28008e018a
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/colon/colon.tf
@@ -0,0 +1,4 @@
+variable "tags" {
+ type = list(object({ key: string, value: string, propagate_at_launch: bool }))
+default = []
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/colon/expected.json b/tests/terraform/parser/resources/parser_scenarios/colon/expected.json
new file mode 100644
index 0000000000..b6b9a4003c
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/colon/expected.json
@@ -0,0 +1,15 @@
+{
+ "colon.tf": {
+ "variable": [
+ {
+ "tags": {
+ "type": [
+ "${list(object({'key': '${string}', 'value': '${string}', 'propagate_at_launch': '${bool}'}))}"
+ ]
+ }
+ }
+ ]
+ }
+}
+
+
diff --git a/tests/terraform/parser/resources/parser_scenarios/concat_function/expected.json b/tests/terraform/parser/resources/parser_scenarios/concat_function/expected.json
new file mode 100644
index 0000000000..8bb71b6437
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/concat_function/expected.json
@@ -0,0 +1,134 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "inline_map": [
+ {
+ "key": "a_key",
+ "value": "a_value",
+ "propagate_at_launch": false
+ }
+ ]
+ },
+ {
+ "simple_list": [
+ [
+ "a",
+ "",
+ "b",
+ "c"
+ ]
+ ],
+ "simple_list2": [
+ [
+ "a",
+ "",
+ "b",
+ "c"
+ ]
+ ],
+ "single_item_list": [
+ ["a"]
+ ],
+ "single_item_trailing_list": [
+ ["a"]
+ ]
+ }
+ ],
+ "variable": [
+ {
+ "extra_tags": {
+ "default": [
+ [
+ {
+ "key": "Foo",
+ "value": "Bar",
+ "propagate_at_launch": true
+ },
+ {
+ "key": "Baz",
+ "value": "Bam",
+ "propagate_at_launch": true
+ }
+ ]
+ ]
+ }
+ }
+ ],
+ "resource": [
+ {
+ "aws_autoscaling_group": {
+ "bar": {
+ "name": [
+ "foobar3-terraform-test"
+ ],
+ "max_size": [
+ 5
+ ],
+ "min_size": [
+ 2
+ ],
+ "tags": [
+ [
+ {
+ "key": "interpolation1",
+ "value": "value3",
+ "propagate_at_launch": true
+ },
+ {
+ "key": "interpolation2",
+ "value": "value4",
+ "propagate_at_launch": true
+ },
+ {
+ "key": "a_key",
+ "value": "a_value",
+ "propagate_at_launch": false
+ },
+ {
+ "key": "Foo",
+ "value": "Bar",
+ "propagate_at_launch": true
+ },
+ {
+ "key": "Baz",
+ "value": "Bam",
+ "propagate_at_launch": true
+ }
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "aws_autoscaling_group": {
+ "bar_simplified": {
+ "name": [
+ "bar_simplified_group"
+ ],
+ "max_size": [
+ 5
+ ],
+ "min_size": [
+ 2
+ ],
+ "tags": [
+ [
+ {
+ "key": "interpolation1",
+ "value": "value3",
+ "propagate_at_launch": true
+ },
+ {
+ "key": "interpolation2",
+ "value": "value4",
+ "propagate_at_launch": true
+ }
+ ]
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/concat_function/main.tf b/tests/terraform/parser/resources/parser_scenarios/concat_function/main.tf
new file mode 100644
index 0000000000..4079c8071d
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/concat_function/main.tf
@@ -0,0 +1,74 @@
+# Loosely from https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group
+locals {
+ inline_map = {
+ key = "a_key"
+ value = "a_value"
+ propagate_at_launch = false
+ }
+}
+
+variable "extra_tags" {
+ default = [
+ {
+ key = "Foo"
+ value = "Bar"
+ propagate_at_launch = true
+ },
+ {
+ key = "Baz"
+ value = "Bam"
+ propagate_at_launch = true
+ }
+ ]
+}
+
+resource "aws_autoscaling_group" "bar" {
+ name = "foobar3-terraform-test"
+ max_size = 5
+ min_size = 2
+
+ tags = concat(
+ [
+ {
+ "key" = "interpolation1"
+ "value" = "value3"
+ "propagate_at_launch" = true
+ },
+ {
+ "key" = "interpolation2"
+ "value" = "value4"
+ "propagate_at_launch" = true
+ },
+ local.inline_map
+ ],
+ var.extra_tags,
+ )
+}
+
+resource "aws_autoscaling_group" "bar_simplified" {
+ name = "bar_simplified_group"
+ max_size = 5
+ min_size = 2
+ tags = concat(
+ [
+ {
+ "key" = "interpolation1"
+ "value" = "value3"
+ "propagate_at_launch" = true
+ },
+ {
+ "key" = "interpolation2"
+ "value" = "value4"
+ "propagate_at_launch" = true
+ }
+ ]
+ )
+}
+
+# From https://www.terraform.io/docs/language/functions/concat.html
+locals {
+ simple_list = concat(["a", ""], ["b", "c"])
+ simple_list2 = concat(["a"], [""], ["b"], ["c"])
+ single_item_list = concat(["a"])
+ single_item_trailing_list = concat(["a"],)
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/json_807/cdk.tf.json b/tests/terraform/parser/resources/parser_scenarios/json_807/cdk.tf.json
new file mode 100644
index 0000000000..363ce7c269
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/json_807/cdk.tf.json
@@ -0,0 +1,49 @@
+{
+ "variable": {
+ "environment": {
+ "type": "string"
+ },
+ "aws_region": {
+ "default": "us-east-1",
+ "type": "string"
+ },
+ "aws_profile": {
+ "type": "string"
+ }
+ },
+ "terraform": {
+ "required_providers": {
+ "aws": {
+ "version": "~> 2.70.0",
+ "source": "aws"
+ }
+ }
+ },
+ "provider": {
+ "aws": [
+ {
+ "profile": "${var.aws_profile}",
+ "region": "${var.aws_region}",
+ "alias": "default"
+ },
+ {
+ "profile": "external",
+ "region": "us-west-1",
+ "skip_requesting_account_id": true,
+ "alias": "external"
+ }
+ ]
+ },
+ "resource": {
+ "aws_secretsmanager_secret": {
+ "local-secret": {
+ "name": "internal-secret",
+ "provider": "aws.default"
+ },
+ "external-secret": {
+ "name": "external-secret",
+ "provider": "aws.external"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/json_807/expected.json b/tests/terraform/parser/resources/parser_scenarios/json_807/expected.json
new file mode 100644
index 0000000000..25d665bcfc
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/json_807/expected.json
@@ -0,0 +1,51 @@
+{
+ "cdk.tf.json": {
+ "variable": {
+ "environment": {
+ "type": "string"
+ },
+ "aws_region": {
+ "default": "us-east-1",
+ "type": "string"
+ },
+ "aws_profile": {
+ "type": "string"
+ }
+ },
+ "terraform": {
+ "required_providers": {
+ "aws": {
+ "version": "~> 2.70.0",
+ "source": "aws"
+ }
+ }
+ },
+ "provider": {
+ "aws": [
+ {
+ "profile": "${var.aws_profile}",
+ "region": "${var.aws_region}",
+ "alias": "default"
+ },
+ {
+ "profile": "external",
+ "region": "us-west-1",
+ "skip_requesting_account_id": true,
+ "alias": "external"
+ }
+ ]
+ },
+ "resource": {
+ "aws_secretsmanager_secret": {
+ "local-secret": {
+ "name": "internal-secret",
+ "provider": "aws.default"
+ },
+ "external-secret": {
+ "name": "external-secret",
+ "provider": "aws.external"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/map_function/expected.json b/tests/terraform/parser/resources/parser_scenarios/map_function/expected.json
new file mode 100644
index 0000000000..ed7ad06ed2
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/map_function/expected.json
@@ -0,0 +1,57 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "INTS": [
+ {
+ "a": 1,
+ "b": 2
+ }
+ ],
+ "FLOATS": [
+ {
+ "a": 1.1,
+ "b": 2.2
+ }
+ ],
+ "STRINGS": [
+ {
+ "a": "one",
+ "b": "two"
+ }
+ ],
+ "BOOLS": [
+ {
+ "a": true,
+ "b": false
+ }
+ ],
+ "MIXED_BOOL": [
+ {
+ "a": "foo",
+ "b": "true"
+ }
+ ],
+ "MIXED_FLOAT": [
+ {
+ "a": "foo",
+ "b": "1.2"
+ }
+ ],
+ "ANNOYING_SPLIT": [
+ {
+ "this, is": "really, annoying"
+ }
+ ],
+ "INVALID_ODD_ARGS": ["${map(\"only one\")}"],
+ "common_tags": [
+ {
+ "App": "my_app",
+ "Product": "my_product",
+ "Team": "my_team"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/map_function/main.tf b/tests/terraform/parser/resources/parser_scenarios/map_function/main.tf
new file mode 100644
index 0000000000..12e322d1f6
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/map_function/main.tf
@@ -0,0 +1,19 @@
+locals {
+ INTS = map("a", 1, "b", 2)
+ FLOATS = map("a", 1.1, "b", 2.2)
+ STRINGS = map("a", "one", "b", "two")
+ BOOLS = map("a", true, "b", false)
+
+ MIXED_BOOL = map("a", "foo", "b", true)
+ MIXED_FLOAT = map("a", "foo", "b", 1.2)
+
+ ANNOYING_SPLIT = map("this, is", "really, annoying")
+
+ INVALID_ODD_ARGS = map("only one")
+
+ common_tags = "${map(
+ "App", "my_app",
+ "Product", "my_product",
+ "Team", "my_team",
+ )}"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/merge_function/expected.json b/tests/terraform/parser/resources/parser_scenarios/merge_function/expected.json
index b1938b78c0..0182a32f38 100644
--- a/tests/terraform/parser/resources/parser_scenarios/merge_function/expected.json
+++ b/tests/terraform/parser/resources/parser_scenarios/merge_function/expected.json
@@ -27,6 +27,14 @@
"Tag4": "four"
}
],
+ "local_local_manual": [
+ {
+ "Tag1": "one",
+ "Tag2": "two",
+ "Tag3": "three",
+ "Name": "Bob"
+ }
+ ],
"manual_to_local": [
{
"Tag4": "four",
@@ -87,7 +95,25 @@
"Tag2": "multiline_tag2",
"Tag4": "four"
}
- ]
+ ],
+ "static1": ["one"],
+ "static2": ["two"]
+ }
+ ],
+ "resource": [
+ {
+ "aws_something": {
+ "something": {
+ "tags": [
+ {
+ "Tag1": "one",
+ "Tag2": "two",
+ "Tag3": "three",
+ "Name": "Bob-one-two"
+ }
+ ]
+ }
+ }
}
]
}
diff --git a/tests/terraform/parser/resources/parser_scenarios/merge_function/main.tf b/tests/terraform/parser/resources/parser_scenarios/merge_function/main.tf
index 8e95ec06fe..6e45e33716 100644
--- a/tests/terraform/parser/resources/parser_scenarios/merge_function/main.tf
+++ b/tests/terraform/parser/resources/parser_scenarios/merge_function/main.tf
@@ -9,6 +9,7 @@ locals {
local_to_local = merge(local.common_tags, local.common_data_tags)
local_to_manual = merge(local.common_tags, {Tag4 = "four"})
+ local_local_manual = merge(local.common_tags, local.common_data_tags, {Name = "Bob"})
manual_to_local = merge({Tag4 = "four"}, local.common_tags)
manual_to_manual = merge({Tag4 = "four"}, {Tag5="five"})
@@ -36,4 +37,12 @@ locals {
{Tag4 = "four"},
{Tag2="multiline_tag2"}
)
+
+ static1 = "one"
+ static2 = "two"
+}
+
+resource "aws_something" "something" {
+ #
+ tags = merge(local.common_tags, local.common_data_tags, {Name = "Bob-${local.static1}-${local.static2}"})
}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/expected.json b/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/expected.json
new file mode 100644
index 0000000000..c9601eaa96
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/expected.json
@@ -0,0 +1,34 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "common_tags": [
+ {
+ "Tag1": "one",
+ "Tag2": "two"
+ }
+ ]
+ }
+ ],
+ "variable": [
+ {
+ "ENV": {}
+ }
+ ],
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "bucket": {
+ "tags": [
+ {
+ "Tag1": "one",
+ "Tag2": "two",
+ "Name": "my-bucket-${var.ENV}"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/main.tf b/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/main.tf
new file mode 100644
index 0000000000..a69f79c79d
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/merge_function_unresolved_var/main.tf
@@ -0,0 +1,14 @@
+locals {
+ common_tags = {
+ Tag1 = "one"
+ Tag2 = "two"
+ }
+}
+
+variable "ENV" {}
+
+resource "aws_s3_bucket" "bucket" {
+ # var.ENV has no default, so need to evaluate the merge without the
+ # fully resolved statement.
+ tags = merge(local.common_tags, {Name = "my-bucket-${var.ENV}"})
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_matryoshka/expected.json b/tests/terraform/parser/resources/parser_scenarios/module_matryoshka/expected.json
index 996e17ea70..8376ea3bbc 100644
--- a/tests/terraform/parser/resources/parser_scenarios/module_matryoshka/expected.json
+++ b/tests/terraform/parser/resources/parser_scenarios/module_matryoshka/expected.json
@@ -18,7 +18,7 @@
}
]
},
- "bucket1/bucket2/bucket3/bucket.tf[bucket1/bucket2/bucket.tf#0]": {
+ "bucket1/bucket2/bucket3/bucket.tf[buckets.tf->bucket1/bucket.tf->bucket1/bucket2/bucket.tf#0#0]": {
"resource": [
{
"aws_s3_bucket": {
@@ -30,7 +30,7 @@
}
]
},
- "bucket1/bucket2/bucket.tf[bucket1/bucket.tf#0]": {
+ "bucket1/bucket2/bucket.tf[buckets.tf->bucket1/bucket.tf#0]": {
"module": [
{
"bucket3": {
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_output_reference/bucket/bucket.tf b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/bucket/bucket.tf
new file mode 100644
index 0000000000..f0d56dbd0b
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/bucket/bucket.tf
@@ -0,0 +1,8 @@
+variable "tags" {}
+
+
+resource "aws_s3_bucket" "bucket" {
+ bucket = "its.a.bucket"
+ # NOTE: Prior to find_var_blocks handling vars in parameters, this didn't work
+ tags = merge(var.tags, {"more_tags" = "yes"})
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_output_reference/common/common.tf b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/common/common.tf
new file mode 100644
index 0000000000..d29a5ad13f
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/common/common.tf
@@ -0,0 +1,6 @@
+output "tags" {
+ value = {
+ Team = "my_team"
+ Color = "red"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_output_reference/expected.json b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/expected.json
new file mode 100644
index 0000000000..44a2fa16d2
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/expected.json
@@ -0,0 +1,59 @@
+{
+ "main.tf": {
+ "module": [
+ {
+ "common": {
+ "source": ["./common"],
+ "__resolved__": ["common/common.tf[main.tf#0]"]
+ }
+ },
+ {
+ "bucket": {
+ "source": ["./bucket"],
+ "tags": [
+ {
+ "Team": "my_team",
+ "Color": "red"
+ }
+ ],
+ "__resolved__": ["bucket/bucket.tf[main.tf#1]"]
+ }
+ }
+ ]
+ },
+ "bucket/bucket.tf[main.tf#1]": {
+ "variable": [
+ {
+ "tags": {}
+ }
+ ],
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "bucket": {
+ "bucket": ["its.a.bucket"],
+ "tags": [
+ {
+ "Team": "my_team",
+ "Color": "red",
+ "more_tags": "yes"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "common/common.tf[main.tf#0]": {
+ "output": [
+ {
+ "tags": {
+ "value": [{
+ "Team": "my_team",
+ "Color": "red"
+ }]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_output_reference/main.tf b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/main.tf
new file mode 100644
index 0000000000..443365d021
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_output_reference/main.tf
@@ -0,0 +1,7 @@
+module "common" {
+ source = "./common"
+}
+module "bucket" {
+ source = "./bucket"
+ tags = module.common.tags # <-- reference to other module, must be resolved in second pass
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/bucket/bucket.tf b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/bucket/bucket.tf
new file mode 100644
index 0000000000..459fe9754c
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/bucket/bucket.tf
@@ -0,0 +1,3 @@
+resource "aws_s3_bucket" "mybucket" {
+ bucket = "MyBucket"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/expected.json b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/expected.json
new file mode 100644
index 0000000000..629c6f3525
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/expected.json
@@ -0,0 +1,23 @@
+{
+ "tf/main.tf": {
+ "module": [
+ {
+ "bucket": {
+ "source": ["../bucket"],
+ "__resolved__": ["bucket/bucket.tf[tf/main.tf#0]"]
+ }
+ }
+ ]
+ },
+ "bucket/bucket.tf[tf/main.tf#0]": {
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "mybucket": {
+ "bucket": ["MyBucket"]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/tf/main.tf b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/tf/main.tf
new file mode 100644
index 0000000000..404630caad
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/module_simple_up_dir_ref/tf/main.tf
@@ -0,0 +1,3 @@
+module "bucket" {
+ source = "../bucket"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/ternaries/expected.json b/tests/terraform/parser/resources/parser_scenarios/ternaries/expected.json
new file mode 100644
index 0000000000..717d940085
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/ternaries/expected.json
@@ -0,0 +1,15 @@
+{
+ "main.tf": {
+ "locals": [
+ {
+ "a": ["a"],
+ "b": ["b"],
+ "empty": [""],
+ "bool_true": ["correct"],
+ "bool_false": ["correct"],
+ "type": ["bool"],
+ "default": [true]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/ternaries/main.tf b/tests/terraform/parser/resources/parser_scenarios/ternaries/main.tf
new file mode 100644
index 0000000000..c90c597fb0
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/ternaries/main.tf
@@ -0,0 +1,32 @@
+locals {
+ a = "a"
+ b = "b"
+ empty = ""
+
+ bool_true = true ? "correct" : "wrong"
+ bool_false = false ? "wrong" : "correct"
+
+// local_true = true
+ // TODO: HCL2 parser doesn't like the following line
+// multiline = (local.local_true) ?
+// "correct" : "wrong"
+
+ // TODO: See test_hcl2_load_assumptions.py -> test_weird_ternary_string_clipping
+ // Doesn't currently pull the ternary correctly since it's evaluated inside the string.
+// bool_string_true = "true" ? "correct" : "wrong"
+// bool_string_false = "false" ? "wrong" : "correct"
+
+ // TODO: Comparison cases...
+// compare_string_true = "a" == "a" ? "correct" : "wrong"
+// compare_string_false = "a" != "a" ? "wrong" : "correct"
+//
+// compare_num_true = 1 == 1 ? "correct" : "wrong"
+// compare_num_false = 1 != 1 ? "correct" : "wrong"
+//
+// # NOTE: I don't think evals in locals is valid in TF, but the parser will eval it
+// default_not_taken = local.a != "" ? local.a : "default value"
+// default_taken = local.empty != "" ? local.a : "default value"
+
+ type = bool
+ default = true
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/ternary_793/expected.json b/tests/terraform/parser/resources/parser_scenarios/ternary_793/expected.json
new file mode 100644
index 0000000000..e79a25be9e
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/ternary_793/expected.json
@@ -0,0 +1,33 @@
+{
+ "main.tf": {
+ "variable": [
+ {
+ "metadata_http_tokens_required": {
+ "type": ["bool"],
+ "default": [true],
+ "description": ["Whether or not the metadata service requires session tokens"]
+ }
+ }
+ ],
+ "resource": [
+ {
+ "aws_instance": {
+ "foo": {
+ "ami": ["ami-005e54dee72cc1d00"],
+ "instance_type": ["t2.micro"],
+ "root_block_device": [
+ {
+ "encrypted": [true]
+ }
+ ],
+ "metadata_options": [
+ {
+ "http_tokens": ["required"]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/ternary_793/main.tf b/tests/terraform/parser/resources/parser_scenarios/ternary_793/main.tf
new file mode 100644
index 0000000000..52ec0bc40b
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/ternary_793/main.tf
@@ -0,0 +1,18 @@
+variable "metadata_http_tokens_required" {
+ type = bool
+ default = true
+ description = "Whether or not the metadata service requires session tokens"
+}
+
+resource "aws_instance" "foo" {
+ ami = "ami-005e54dee72cc1d00" # us-west-2
+ instance_type = "t2.micro"
+
+ root_block_device {
+ encrypted = true
+ }
+
+ metadata_options {
+ http_tokens = (var.metadata_http_tokens_required) ? "required" : "optional"
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/tfvars/expected.json b/tests/terraform/parser/resources/parser_scenarios/tfvars/expected.json
new file mode 100644
index 0000000000..0ec3b500bb
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/tfvars/expected.json
@@ -0,0 +1,26 @@
+{
+ "main.tf": {
+ "variable": [
+ {
+ "foo": {}
+ },
+ {
+ "list_data": {}
+ },
+ {
+ "map_data": {}
+ }
+ ],
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "my_bucket": {
+ "bucket": [
+ "fü-one-dev"
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/tfvars/main.tf b/tests/terraform/parser/resources/parser_scenarios/tfvars/main.tf
new file mode 100644
index 0000000000..346f40e779
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/tfvars/main.tf
@@ -0,0 +1,7 @@
+variable "foo" {}
+variable "list_data" {}
+variable "map_data" {}
+
+resource "aws_s3_bucket" "my_bucket" {
+ bucket = "${var.foo}-${var.list_data[0]}-${var.map_data[stage]}"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/tfvars/terraform.tfvars b/tests/terraform/parser/resources/parser_scenarios/tfvars/terraform.tfvars
new file mode 100644
index 0000000000..5edf6d6350
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/tfvars/terraform.tfvars
@@ -0,0 +1,12 @@
+foo = "fü"
+
+list_data = [
+ "one",
+ "two"
+]
+
+map_data = {
+ namespace = "customer"
+ stage = "dev"
+ name = "app"
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/eval.json b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/eval.json
new file mode 100644
index 0000000000..a1c1a3dd6a
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/eval.json
@@ -0,0 +1,15 @@
+{
+ "main.tf": {
+ "BUCKET_NAME": {
+ "var_file": "variables.tf",
+ "value": "this-is-my-default",
+ "definitions": [
+ {
+ "definition_name": "BUCKET_NAME",
+ "definition_expression": "${var.BUCKET_NAME}",
+ "definition_path": "resource/0/aws_s3_bucket/test/bucket/0"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/expected.json b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/expected.json
new file mode 100644
index 0000000000..0ec9a3979f
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/expected.json
@@ -0,0 +1,25 @@
+{
+ "main.tf": {
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "test": {
+ "bucket": [
+ "this-is-my-default"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "variables.tf": {
+ "variable": [
+ {
+ "BUCKET_NAME": {
+ "type": ["string"],
+ "default": ["this-is-my-default"]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/main.tf b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/main.tf
new file mode 100644
index 0000000000..4c47448fb0
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/main.tf
@@ -0,0 +1,3 @@
+resource "aws_s3_bucket" "test" {
+ bucket = var.BUCKET_NAME
+}
diff --git a/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/variables.tf b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/variables.tf
new file mode 100644
index 0000000000..d15d82b898
--- /dev/null
+++ b/tests/terraform/parser/resources/parser_scenarios/variable_defaults_separate_files/variables.tf
@@ -0,0 +1,4 @@
+variable "BUCKET_NAME" {
+ type = string
+ default = "this-is-my-default"
+}
\ No newline at end of file
diff --git a/tests/terraform/evaluation/test_hcl2_load_assumptions.py b/tests/terraform/parser/test_hcl2_load_assumptions.py
similarity index 75%
rename from tests/terraform/evaluation/test_hcl2_load_assumptions.py
rename to tests/terraform/parser/test_hcl2_load_assumptions.py
index 6f03944520..e790cd181b 100644
--- a/tests/terraform/evaluation/test_hcl2_load_assumptions.py
+++ b/tests/terraform/parser/test_hcl2_load_assumptions.py
@@ -7,6 +7,38 @@
# This group of tests is used to confirm assumptions about how the hcl2 library parses into json.
# We want to make sure important assumptions are caught if behavior changes.
class TestHCL2LoadAssumptions(unittest.TestCase):
+ def test_ternary(self):
+ # Ternary and removal of parens are interesting things here
+ tf = '''
+ resource "aws_instance" "foo" {
+ metadata_options {
+ http_tokens = (var.metadata_http_tokens_required) ? "required" : "optional"
+ }
+ }'''
+ expect = {
+ "resource": [{
+ "aws_instance": {
+ "foo": {
+ "metadata_options": [{
+ "http_tokens": ['${var.metadata_http_tokens_required ? "required" : "optional"}']
+ }]
+ }
+ }
+ }]
+ }
+ self.go(tf, expect)
+
+ def test_tfvars(self):
+ tf = '''
+ VERSIONING = true
+ CHECKOV = "awesome"
+ '''
+ expect = {
+ "VERSIONING": [True],
+ "CHECKOV": ["awesome"]
+ }
+ self.go(tf, expect)
+
def test_multiline_function(self):
tf = '''
locals {
@@ -55,6 +87,24 @@ def test_inner_quoting(self):
}
self.go(tf, expect)
+ def test_merge_with_inner_var(self):
+ tf = '''
+ resource "aws_s3_bucket" "foo" {
+ tags = merge(local.common_tags, local.common_data_tags, {Name = "my-thing-${var.ENVIRONMENT}-${var.REGION}"})
+ }'''
+ expect = {
+ "resource": [
+ {
+ "aws_s3_bucket": {
+ "foo": {
+ "tags": ["${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}"]
+ }
+ }
+ }
+ ]
+ }
+ self.go(tf, expect)
+
def test_variable_block(self):
tf = '''
variable "my_var" {
@@ -226,3 +276,20 @@ def go(tf, expected_result):
f"{json.dumps(expected_result, indent=2)}\n" \
f"** ACTUAL **\n" \
f"{json.dumps(actual_result, indent=2)}"
+
+ def test_math(self):
+ tf = "four = 2 + 2"
+ expect = {
+ "four": ["${2 + 2}"]
+ }
+ self.go(tf, expect)
+
+ def test_weird_ternary_string_clipping(self):
+ tf = 'bool_string_false = "false" ? "wrong" : "correct"'
+ expect = {
+ "bool_string_false": ["false\" ? \"wrong\" : \"correct"]
+ # -- --
+ # | |
+ # missing quotes on outer tokens :-(
+ }
+ self.go(tf, expect)
diff --git a/tests/terraform/parser/test_parser_internals.py b/tests/terraform/parser/test_parser_internals.py
new file mode 100644
index 0000000000..fd13abdda3
--- /dev/null
+++ b/tests/terraform/parser/test_parser_internals.py
@@ -0,0 +1,9 @@
+import unittest
+
+from checkov.terraform import parser
+
+
+class TestParserInternals(unittest.TestCase):
+ def test_eval_string_to_list(self):
+ expected = ["a", "b", "c"]
+ assert parser.eval_string('["a", "b", "c"]') == expected
diff --git a/tests/terraform/parser/test_parser_modules.py b/tests/terraform/parser/test_parser_modules.py
index 5095c8335e..1e9a07d13e 100644
--- a/tests/terraform/parser/test_parser_modules.py
+++ b/tests/terraform/parser/test_parser_modules.py
@@ -75,3 +75,15 @@ def test_invalid_module_sources(self):
external_modules_download_path=DEFAULT_EXTERNAL_MODULES_DIR)
# check that only the original file was parsed successfully without getting bad external modules
self.assertEqual(1, len(list(out_definitions.keys())))
+
+ def test_malformed_output_blocks(self):
+ parser = Parser()
+ directory = os.path.join(self.resources_dir, "malformed_outputs")
+ self.external_module_path = os.path.join(directory, DEFAULT_EXTERNAL_MODULES_DIR)
+ out_definitions = {}
+ parser.parse_directory(directory=directory, out_definitions=out_definitions,
+ out_evaluations_context={},
+ download_external_modules=True,
+ external_modules_download_path=DEFAULT_EXTERNAL_MODULES_DIR)
+ file_path, entity_definitions = next(iter(out_definitions.items()))
+ self.assertEqual(2, len(list(out_definitions[file_path]['output'])))
diff --git a/tests/terraform/parser/test_parser_scenarios.py b/tests/terraform/parser/test_parser_scenarios.py
deleted file mode 100644
index 19b36301e7..0000000000
--- a/tests/terraform/parser/test_parser_scenarios.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import dataclasses
-import json
-import os
-import re
-import unittest
-
-import jmespath
-
-from checkov.terraform.parser import Parser
-
-
-def json_encoder(val):
- if dataclasses.is_dataclass(val):
- return dataclasses.asdict(val)
- if isinstance(val, set):
- return list(sorted(["__this_is_a_set__"] + list(val)))
- return val
-
-
-class TestParserScenarios(unittest.TestCase):
-
- def test_empty_file(self):
- self.go("empty_file")
-
- def test_simple_bucket_single_file(self):
- self.go("simple_bucket_single_file")
-
- def test_variable_defaults(self):
- self.go("variable_defaults")
-
- def test_local_block(self):
- self.go("local_block")
-
- def test_local_bool_string_conversion(self):
- self.go("local_bool_string_conversion")
-
- def test_compound_local(self):
- self.go("compound_local")
-
- def test_merge_function(self):
- self.go("merge_function")
-
- def test_tobool_function(self):
- self.go("tobool_function")
-
- def test_tolist_function(self):
- self.go("tolist_function")
-
- def test_tomap_function(self):
- self.go("tomap_function")
-
- def test_tonumber_function(self):
- self.go("tonumber_function")
-
- def test_toset_function(self):
- self.go("toset_function")
-
- def test_tostring_function(self):
- self.go("tostring_function")
-
- def test_module_simple(self):
- self.go("module_simple")
-
- def test_module_matryoshka(self):
- self.go("module_matryoshka")
-
- def test_list_default_622(self): # see https://github.com/bridgecrewio/checkov/issues/622
- self.go("list_default_622")
-
- # TODO ROB - Implementation in progress
- # def test_formatting(self):
- # self.go("formatting")
-
- def test_maze_of_variables(self):
- self.go("maze_of_variables")
-
- def test_module_reference(self):
- self.go("module_reference")
-
- def test_bad_ref_fallbacks(self):
- self.go("bad_ref_fallbacks")
-
- def test_doc_evaluations_verify(self):
- self.go("doc_evaluations_verify")
-
- def test_bad_tf(self):
- self.go("bad_tf")
-
- def test_null_variables_651(self):
- self.go("null_variables_651")
-
- @unittest.skip
- def test_count_index_scenario(self):
- # Run only manually, this test currently fails on multiple issues
- self.go("count_eval")
-
- @staticmethod
- def go(dir_name):
- dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
- f"resources/parser_scenarios/{dir_name}")
- assert os.path.exists(dir_path)
-
- expected_data = TestParserScenarios.load_expected_data("expected.json", dir_path)
- assert expected_data is not None, f"{dir_name}: expected.json file not found"
-
- evaluation_data = TestParserScenarios.load_expected_data("eval.json", dir_path)
-
- actual_data = {}
- actual_eval_data = {}
- errors = {}
- parser = Parser()
- parser.parse_directory(dir_path, actual_data, actual_eval_data, errors, download_external_modules=True)
- assert not errors, f"{dir_name}: Unexpected errors: {errors}"
- definition_string = json.dumps(actual_data, indent=2, default=json_encoder)
- definition_encoded = json.loads(definition_string)
- assert definition_encoded == expected_data, \
- f"{dir_name}: Data mismatch:\n" \
- f" Expected: \n{json.dumps(expected_data, indent=2, default=json_encoder)}\n\n" \
- f" Actual: \n{definition_string}"
-
- if evaluation_data is not None:
- definition_string = json.dumps(actual_eval_data, indent=2, default=json_encoder)
- definition_encoded = json.loads(definition_string)
- assert definition_encoded == evaluation_data, \
- f"{dir_name}: Evaluation data mismatch:\n" \
- f" Expected: \n{json.dumps(evaluation_data, indent=2, default=json_encoder)}\n\n" \
- f" Actual: \n{definition_string}"
-
- @staticmethod
- def load_expected_data(source_file_name, dir_path):
- expected_path = os.path.join(dir_path, source_file_name)
- if not os.path.exists(expected_path):
- return None
-
- with open(expected_path, "r") as f:
- expected_data = json.load(f)
-
- # Convert to absolute path: "buckets/bucket.tf[main.tf#0]"
- # ^^^^^^^^^^^^^^^^^ ^^^^^^^
- # HERE & HERE
- #
- resolved_pattern = re.compile(r"(.+)\[(.+)#(\d+)]") # groups: location (1), referrer (2), index (3)
-
- # Expected files should have the filenames relative to their base directory, but the parser will
- # use the absolute path. This loop with replace relative filenames with absolute.
- keys = list(expected_data.keys())
- for key in keys:
- # NOTE: Sometimes keys have module referrers, sometimes they don't
-
- match = resolved_pattern.match(key)
- if match:
- new_key = _make_module_ref_absolute(match, dir_path)
- else:
- if os.path.isabs(key):
- continue
- new_key = os.path.join(dir_path, key)
- expected_data[new_key] = expected_data[key]
- del expected_data[key]
-
- for resolved_list in jmespath.search("*.module[].*[].__resolved__", expected_data):
- for list_index in range(0, len(resolved_list)):
- match = resolved_pattern.match(resolved_list[list_index])
- assert match is not None, f"Unexpected module resolved data: {resolved_list[list_index]}"
- resolved_list[list_index] = _make_module_ref_absolute(match, dir_path)
- # print(f"{match[0]} -> {resolved_list[list_index]}")
-
- return expected_data
-
-
-def _make_module_ref_absolute(match, dir_path) -> str:
- module_location = match[1]
- if not os.path.isabs(module_location):
- module_location = os.path.join(dir_path, module_location)
-
- module_referrer = match[2]
- if not os.path.isabs(module_referrer):
- module_referrer = os.path.join(dir_path, module_referrer)
- return f"{module_location}[{module_referrer}#{match[3]}]"
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/terraform/parser/test_parser_var_blocks.py b/tests/terraform/parser/test_parser_var_blocks.py
new file mode 100644
index 0000000000..53730d9dce
--- /dev/null
+++ b/tests/terraform/parser/test_parser_var_blocks.py
@@ -0,0 +1,195 @@
+import pprint
+import unittest
+
+from typing import List, Tuple
+
+from checkov.terraform.parser_utils import VarBlockMatch as VBM, split_merge_args, find_var_blocks
+
+
+class TestParserInternals(unittest.TestCase):
+ def test_split_merge_args(self):
+ cases: List[Tuple[str, List[str]]] = [
+ ("local.one, local.two",
+ ["local.one", "local.two"]),
+ ("{Tag4 = \"four\"}, {Tag5 = \"five\"}",
+ ["{Tag4 = \"four\"}", "{Tag5 = \"five\"}"]),
+ ("{a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3}",
+ ["{a=\"b\"}", "{a=[1,2], c=\"z\"}", "{d=3}"]),
+ ("local.common_tags, merge({Tag4 = \"four\"}, {Tag5 = \"five\"})",
+ ["local.common_tags", "merge({Tag4 = \"four\"}, {Tag5 = \"five\"})"]),
+ (", ",
+ None),
+ ("",
+ None),
+ (", leading_comma",
+ ["leading_comma"]),
+ ("kinda_maybe_shouldnt_work_but_we_will_roll_with_it, ", # <-- trailing comma
+ ["kinda_maybe_shouldnt_work_but_we_will_roll_with_it"]),
+ ("local.one",
+ ["local.one"]),
+ ('{"a": "}, evil"}', # bracket inside string, should not be split
+ ['{"a": "}, evil"}']),
+ ("{'a': '}, evil'}", # bracket inside string, should not be split
+ ["{'a': '}, evil'}"]), # Note: these happen with native maps (see merge tests)
+ ('${merge({\'a\': \'}, evil\'})}',
+ ['${merge({\'a\': \'}, evil\'})}']),
+ ('local.common_tags,,{\'Tag4\': \'four\'},,{\'Tag2\': \'Dev\'},',
+ ["local.common_tags", "{\'Tag4\': \'four\'}", "{\'Tag2\': \'Dev\'}"])
+ ]
+ for case in cases:
+ actual = split_merge_args(case[0])
+ assert actual == case[1], f"Case \"{case[0]}\" failed. Expected: {case[1]} Actual: {actual}"
+
+ def test_find_var_blocks(self):
+ cases: List[Tuple[str, List[VBM]]] = [
+ (
+ "${local.one}",
+ [
+ VBM("${local.one}", "local.one")
+ ]
+ ),
+ (
+ "${merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})}",
+ [
+ VBM("${merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})}",
+ "merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})")
+ ]
+ ),
+ (
+ "\"string$ ${tomap({key=\"value\"})[key]} are fun\"",
+ [
+ VBM("${tomap({key=\"value\"})[key]}", "tomap({key=\"value\"})[key]")
+ ]
+ ),
+ # This case highlights that inner evals should be returned
+ (
+ "${filemd5(\"${path.module}/templates/some-file.json\")}",
+ [
+ VBM("${path.module}", "path.module"),
+ VBM("${filemd5(\"${path.module}/templates/some-file.json\")}",
+ "filemd5(\"${path.module}/templates/some-file.json\")")
+ ]
+ ),
+ (
+ "${local.NAME[foo]}-${local.TAIL}${var.gratuitous_var_default}",
+ [
+ VBM("${local.NAME[foo]}", "local.NAME[foo]"),
+ VBM("${local.TAIL}", "local.TAIL"),
+ VBM("${var.gratuitous_var_default}", "var.gratuitous_var_default")
+ ]
+ ),
+ (
+ "${tostring(\"annoying {\")}",
+ [
+ VBM("${tostring(\"annoying {\")}", "tostring(\"annoying {\")")
+ ]
+ ),
+ (
+ "${tostring(\"annoying }\")}",
+ [
+ VBM("${tostring(\"annoying }\")}", "tostring(\"annoying }\")")
+ ]
+ ),
+ (
+ "${this-is-unterminated",
+ []
+ ),
+ (
+ "${merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})}",
+ [
+ VBM("${merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})}",
+ "merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})")
+ ]
+ ),
+ (
+ "$${foo}", # escape interpolation
+ []
+ ),
+ (
+ '${merge({\'a\': \'}, evil\'})}',
+ [
+ VBM('${merge({\'a\': \'}, evil\'})}', 'merge({\'a\': \'}, evil\'})')
+ ]
+ ),
+
+ # Ordered returning of sub-vars and then outer var.
+ (
+ "${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ [
+ VBM("local.common_tags", "local.common_tags"),
+ VBM("local.common_data_tags", "local.common_data_tags"),
+ VBM("${var.ENVIRONMENT}", "var.ENVIRONMENT"),
+ VBM("${var.REGION}", "var.REGION"),
+ VBM("${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ "merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})")
+ ]
+ ),
+ (
+ "${merge(${local.common_tags},${local.common_data_tags},{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ [
+ VBM("${local.common_tags}", "local.common_tags"),
+ VBM("${local.common_data_tags}", "local.common_data_tags"),
+ VBM("${var.ENVIRONMENT}", "var.ENVIRONMENT"),
+ VBM("${var.REGION}", "var.REGION"),
+ VBM("${merge(${local.common_tags},${local.common_data_tags},{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})}",
+ "merge(${local.common_tags},${local.common_data_tags},{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})")
+ ]
+ ),
+ (
+ '${merge(var.tags, map("Name", "${var.name}", "data_classification", "none"))}',
+ [
+ VBM("var.tags", "var.tags"),
+ VBM("${var.name}", "var.name"),
+ VBM('map("Name", "${var.name}", "data_classification", "none")',
+ 'map("Name", "${var.name}", "data_classification", "none")'),
+ VBM('${merge(var.tags, map("Name", "${var.name}", "data_classification", "none"))}',
+ 'merge(var.tags, map("Name", "${var.name}", "data_classification", "none"))')
+ ]
+ ),
+
+ # Ternaries
+ (
+ '${var.metadata_http_tokens_required ? "required" : "optional"}',
+ [
+ VBM('var.metadata_http_tokens_required', 'var.metadata_http_tokens_required'),
+ VBM('${var.metadata_http_tokens_required ? "required" : "optional"}',
+ 'var.metadata_http_tokens_required ? "required" : "optional"')
+ ]
+ ),
+ (
+ '${1 + 1 == 2 ? "required" : "optional"}',
+ [
+ VBM('1 + 1 == 2', '1 + 1 == 2'),
+ VBM('${1 + 1 == 2 ? "required" : "optional"}', '1 + 1 == 2 ? "required" : "optional"')
+ ]
+ ),
+ (
+ '${true ? "required" : "optional"}',
+ [
+ VBM('${true ? "required" : "optional"}', 'true ? "required" : "optional"')
+ ]
+ ),
+ (
+ '${false ? "required" : "optional"}',
+ [
+ VBM('${false ? "required" : "optional"}', 'false ? "required" : "optional"')
+ ]
+ ),
+ # TODO: var -> comparison -> ternary
+ # (
+ # '${local.empty != "" ? local.a : "default value"}',
+ # [
+ # VBM("local.empty", "local.empty"),
+ # VBM('local.empty != ""', 'local.empty != ""'),
+ # VBM('${local.empty != "" ? local.a : "default value"}',
+ # 'local.empty != "" ? local.a : "default value"')
+ # ]
+ # )
+ ]
+ for case in cases:
+ actual = find_var_blocks(case[0])
+ assert actual == case[1], \
+ f"Case \"{case[0]}\" failed ❌:\n" \
+ f" Expected: \n{pprint.pformat(case[1], indent=2)}\n\n" \
+ f" Actual: \n{pprint.pformat(actual, indent=2)}"
+ print(f"Case \"{case[0]}: ✅")
diff --git a/tests/terraform/parser/test_plan_parser.py b/tests/terraform/parser/test_plan_parser.py
index ad3c3526df..f757cc8bd6 100644
--- a/tests/terraform/parser/test_plan_parser.py
+++ b/tests/terraform/parser/test_plan_parser.py
@@ -15,7 +15,7 @@ def test_tags_values_are_flattened(self):
resource_tags = resource_attributes['tags'][0]
for tag_key, tag_value in resource_tags.items():
if tag_key not in ['start_line', 'end_line']:
- self.assertTrue(isinstance(tag_value, str))
+ self.assertIsInstance(tag_value, str)
if __name__ == '__main__':
diff --git a/tests/terraform/runner/extra_checks/S3EnvironmentCheck.py b/tests/terraform/runner/extra_checks/S3EnvironmentCheck.py
new file mode 100644
index 0000000000..c85ebd5106
--- /dev/null
+++ b/tests/terraform/runner/extra_checks/S3EnvironmentCheck.py
@@ -0,0 +1,21 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class S3EnvironmentCheck(BaseResourceCheck):
+ def __init__(self):
+ name = "Ensure s3 has environment tag of developemnt/staging/production"
+ id = "CUSTOM_AWS_1"
+ supported_resources = ['aws_s3_bucket']
+ categories = [CheckCategories.GENERAL_SECURITY]
+ super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+ def scan_resource_conf(self, conf):
+ if conf.get("tags") and isinstance(conf['tags'][0], dict):
+ env = conf["tags"][0].get("Environment",{})
+ if env in ["Developemnt","Staging","Production"]:
+ return CheckResult.PASSED
+ return CheckResult.FAILED
+
+
+scanner = S3EnvironmentCheck()
diff --git a/tests/terraform/runner/extra_checks/__init__.py b/tests/terraform/runner/extra_checks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/terraform/runner/extra_yaml_checks/bucket_versioned_owned.yaml b/tests/terraform/runner/extra_yaml_checks/bucket_versioned_owned.yaml
new file mode 100644
index 0000000000..5c76828bea
--- /dev/null
+++ b/tests/terraform/runner/extra_yaml_checks/bucket_versioned_owned.yaml
@@ -0,0 +1,17 @@
+metadata:
+ id: "CKV2_CUSTOM_1"
+ name: "Ensure bucket has versioning and owner tag"
+ category: "BACKUP_AND_RECOVERY"
+definition:
+ and:
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "tags.Owner"
+ operator: "exists"
+ - cond_type: "attribute"
+ resource_types:
+ - "aws_s3_bucket"
+ attribute: "versioning.enabled"
+ operator: "equals"
+ value: "true"
\ No newline at end of file
diff --git a/tests/terraform/runner/extra_yaml_checks/test_tag.yaml b/tests/terraform/runner/extra_yaml_checks/test_tag.yaml
new file mode 100644
index 0000000000..2758c0708b
--- /dev/null
+++ b/tests/terraform/runner/extra_yaml_checks/test_tag.yaml
@@ -0,0 +1,12 @@
+metadata:
+ name: "Ensure all resources are tagged with the relevant env"
+ id: "CUSTOM_GRAPH_AWS_1"
+ category: "GENERAL_SECURITY"
+scope:
+ provider: "AWS"
+definition:
+ cond_type: "attribute"
+ resource_types:
+ - "all"
+ attribute: "tags.env"
+ operator: "exists"
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/duplicate_violations/modules/main.tf b/tests/terraform/runner/resources/duplicate_violations/modules/main.tf
new file mode 100644
index 0000000000..69bb59d0a8
--- /dev/null
+++ b/tests/terraform/runner/resources/duplicate_violations/modules/main.tf
@@ -0,0 +1,17 @@
+
+data "aws_iam_policy_document" "restrictions" {
+
+ # do not allow the account to leave the org except for the exempt
+ statement {
+ effect = "Deny"
+ resources = [
+ "*",
+ ]
+ }
+
+}
+
+resource "aws_organizations_policy" "restrictions" {
+ name = "${var.account_name}-restrictions"
+ content = data.aws_iam_policy_document.restrictions.json
+}
diff --git a/tests/terraform/runner/resources/duplicate_violations/src/main1.tf b/tests/terraform/runner/resources/duplicate_violations/src/main1.tf
new file mode 100644
index 0000000000..dfacb1bda1
--- /dev/null
+++ b/tests/terraform/runner/resources/duplicate_violations/src/main1.tf
@@ -0,0 +1,4 @@
+module "module1" {
+ source = "../modules/"
+
+}
diff --git a/tests/terraform/runner/resources/duplicate_violations/src/main2.tf b/tests/terraform/runner/resources/duplicate_violations/src/main2.tf
new file mode 100644
index 0000000000..65e440afdc
--- /dev/null
+++ b/tests/terraform/runner/resources/duplicate_violations/src/main2.tf
@@ -0,0 +1,4 @@
+module "module2" {
+ source = "../modules/"
+
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/example/example.tf b/tests/terraform/runner/resources/example/example.tf
index ec56153d5b..bf6f396be9 100644
--- a/tests/terraform/runner/resources/example/example.tf
+++ b/tests/terraform/runner/resources/example/example.tf
@@ -71,7 +71,6 @@ resource "aws_s3_bucket" "foo-bucket" {
tags = {
Name = "foo-${data.aws_caller_identity.current.account_id}"
}
- #checkov:skip=CKV_AWS_52
#checkov:skip=CKV_AWS_20:The bucket is a public static content host
versioning {
enabled = true
@@ -359,7 +358,6 @@ resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = "${aws_s3_bucket.b.bucket_regional_domain_name}"
origin_id = "${local.s3_origin_id}"
- #checkov:skip=CKV_AWS_52
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
diff --git a/tests/terraform/runner/resources/extra_check_test/s3.tf b/tests/terraform/runner/resources/extra_check_test/s3.tf
new file mode 100644
index 0000000000..37db57ba0b
--- /dev/null
+++ b/tests/terraform/runner/resources/extra_check_test/s3.tf
@@ -0,0 +1,27 @@
+resource "aws_s3_bucket" "a" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Production"
+ }
+}
+
+resource "aws_s3_bucket" "b" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+
+ tags = {
+ Name = "My bucket"
+ Environment = "Dev"
+ }
+}
+
+
+resource "aws_s3_bucket" "c" {
+ bucket = "my-tf-test-bucket"
+ acl = "private"
+}
+
+
diff --git a/tests/terraform/runner/resources/malformed_857/main.tf b/tests/terraform/runner/resources/malformed_857/main.tf
new file mode 100644
index 0000000000..dd933d1e29
--- /dev/null
+++ b/tests/terraform/runner/resources/malformed_857/main.tf
@@ -0,0 +1,4 @@
+resource "aws_instance" {
+ ami = "amiid"
+ instance_type = "t3.micro"
+}
diff --git a/tests/terraform/runner/resources/module_failure_reporting_772/main.tf b/tests/terraform/runner/resources/module_failure_reporting_772/main.tf
new file mode 100644
index 0000000000..e2854a43c5
--- /dev/null
+++ b/tests/terraform/runner/resources/module_failure_reporting_772/main.tf
@@ -0,0 +1,13 @@
+#
+# WARNING: Line numbers mater in this test!
+# Update test_module_failure_reporting_772 if a change is made!
+#
+
+module "test_module" {
+ source = "./module"
+}
+
+# Bucket that will fail (no encryption) defined OUTSIDE a module
+resource "aws_s3_bucket" "outside" {
+ bucket = "outside-bucket"
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/module_failure_reporting_772/module/module.tf b/tests/terraform/runner/resources/module_failure_reporting_772/module/module.tf
new file mode 100644
index 0000000000..1590c246b1
--- /dev/null
+++ b/tests/terraform/runner/resources/module_failure_reporting_772/module/module.tf
@@ -0,0 +1,9 @@
+#
+# WARNING: Line numbers mater in this test!
+# Update test_module_failure_reporting_772 if a change is made!
+#
+
+# Bucket that will fail (no encryption) defined INSIDE a module
+resource "aws_s3_bucket" "inside" {
+ bucket = "inside-bucket"
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/plan_with_resource_reference/tfplan.json b/tests/terraform/runner/resources/plan_with_resource_reference/tfplan.json
new file mode 100644
index 0000000000..0030c6c3ed
--- /dev/null
+++ b/tests/terraform/runner/resources/plan_with_resource_reference/tfplan.json
@@ -0,0 +1,243 @@
+{
+ "format_version": "0.1",
+ "terraform_version": "0.12.25",
+ "planned_values": {
+ "root_module": {
+ "resources": [
+ {
+ "address": "aws_cloudwatch_log_group.audit",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "audit",
+ "provider_name": "aws",
+ "schema_version": 0,
+ "values": {
+ "kms_key_id": null,
+ "name": "audit",
+ "name_prefix": null,
+ "retention_in_days": 0,
+ "tags": null
+ }
+ },
+ {
+ "address": "aws_cloudwatch_log_group.es_application",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "es_application",
+ "provider_name": "aws",
+ "schema_version": 0,
+ "values": {
+ "kms_key_id": null,
+ "name": "es_application",
+ "name_prefix": null,
+ "retention_in_days": 0,
+ "tags": null
+ }
+ },
+ {
+ "address": "aws_elasticsearch_domain.es",
+ "mode": "managed",
+ "type": "aws_elasticsearch_domain",
+ "name": "es",
+ "provider_name": "aws",
+ "schema_version": 0,
+ "values": {
+ "cluster_config": [
+ {
+ "dedicated_master_count": null,
+ "dedicated_master_enabled": false,
+ "dedicated_master_type": null,
+ "instance_count": 1,
+ "instance_type": "r5.large.elasticsearch",
+ "warm_count": null,
+ "warm_enabled": null,
+ "warm_type": null,
+ "zone_awareness_config": [],
+ "zone_awareness_enabled": null
+ }
+ ],
+ "cognito_options": [],
+ "domain_endpoint_options": [
+ {
+ "custom_endpoint": null,
+ "custom_endpoint_certificate_arn": null,
+ "custom_endpoint_enabled": false,
+ "enforce_https": true
+ }
+ ],
+ "domain_name": "test",
+ "elasticsearch_version": "7.0",
+ "encrypt_at_rest": [{"enabled": true}],
+ "log_publishing_options": [
+ {"enabled": true, "log_type": "AUDIT_LOGS"},
+ {"enabled": true, "log_type": "ES_APPLICATION_LOGS"}
+ ],
+ "snapshot_options": [],
+ "tags": null,
+ "timeouts": null,
+ "vpc_options": [{"security_group_ids": null, "subnet_ids": ["subnet-efc0c6a2"]}]
+ }
+ }
+ ]
+ }
+ },
+ "resource_changes": [
+ {
+ "address": "aws_cloudwatch_log_group.audit",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "audit",
+ "provider_name": "aws",
+ "change": {
+ "actions": ["create"],
+ "before": null,
+ "after": {
+ "kms_key_id": null,
+ "name": "audit",
+ "name_prefix": null,
+ "retention_in_days": 0,
+ "tags": null
+ },
+ "after_unknown": {"arn": true, "id": true}
+ }
+ },
+ {
+ "address": "aws_cloudwatch_log_group.es_application",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "es_application",
+ "provider_name": "aws",
+ "change": {
+ "actions": ["create"],
+ "before": null,
+ "after": {
+ "kms_key_id": null,
+ "name": "es_application",
+ "name_prefix": null,
+ "retention_in_days": 0,
+ "tags": null
+ },
+ "after_unknown": {"arn": true, "id": true}
+ }
+ },
+ {
+ "address": "aws_elasticsearch_domain.es",
+ "mode": "managed",
+ "type": "aws_elasticsearch_domain",
+ "name": "es",
+ "provider_name": "aws",
+ "change": {
+ "actions": ["create"],
+ "before": null,
+ "after": {
+ "cluster_config": [
+ {
+ "dedicated_master_count": null,
+ "dedicated_master_enabled": false,
+ "dedicated_master_type": null,
+ "instance_count": 1,
+ "instance_type": "r5.large.elasticsearch",
+ "warm_count": null,
+ "warm_enabled": null,
+ "warm_type": null,
+ "zone_awareness_config": [],
+ "zone_awareness_enabled": null
+ }
+ ],
+ "cognito_options": [],
+ "domain_endpoint_options": [
+ {
+ "custom_endpoint": null,
+ "custom_endpoint_certificate_arn": null,
+ "custom_endpoint_enabled": false,
+ "enforce_https": true
+ }
+ ],
+ "domain_name": "test",
+ "elasticsearch_version": "7.0",
+ "encrypt_at_rest": [{"enabled": true}],
+ "log_publishing_options": [
+ {"enabled": true, "log_type": "AUDIT_LOGS"},
+ {"enabled": true, "log_type": "ES_APPLICATION_LOGS"}
+ ],
+ "snapshot_options": [],
+ "tags": null,
+ "timeouts": null,
+ "vpc_options": [{"security_group_ids": null, "subnet_ids": ["subnet-efc0c6a2"]}]
+ },
+ "after_unknown": {
+ "access_policies": true,
+ "advanced_options": true,
+ "advanced_security_options": true,
+ "arn": true,
+ "cluster_config": [{"zone_awareness_config": []}],
+ "cognito_options": [],
+ "domain_endpoint_options": [{"tls_security_policy": true}],
+ "domain_id": true,
+ "ebs_options": true,
+ "encrypt_at_rest": [{"kms_key_id": true}],
+ "endpoint": true,
+ "id": true,
+ "kibana_endpoint": true,
+ "log_publishing_options": [{"cloudwatch_log_group_arn": true}, {"cloudwatch_log_group_arn": true}],
+ "node_to_node_encryption": true,
+ "snapshot_options": [],
+ "vpc_options": [{"availability_zones": true, "subnet_ids": [false], "vpc_id": true}]
+ }
+ }
+ }
+ ],
+ "configuration": {
+ "provider_config": {"aws": {"name": "aws", "expressions": {"region": {"constant_value": "eu-central-1"}}}},
+ "root_module": {
+ "resources": [
+ {
+ "address": "aws_cloudwatch_log_group.audit",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "audit",
+ "provider_config_key": "aws",
+ "expressions": {"name": {"constant_value": "audit"}},
+ "schema_version": 0
+ },
+ {
+ "address": "aws_cloudwatch_log_group.es_application",
+ "mode": "managed",
+ "type": "aws_cloudwatch_log_group",
+ "name": "es_application",
+ "provider_config_key": "aws",
+ "expressions": {"name": {"constant_value": "es_application"}},
+ "schema_version": 0
+ },
+ {
+ "address": "aws_elasticsearch_domain.es",
+ "mode": "managed",
+ "type": "aws_elasticsearch_domain",
+ "name": "es",
+ "provider_config_key": "aws",
+ "expressions": {
+ "cluster_config": [{"instance_type": {"constant_value": "r5.large.elasticsearch"}}],
+ "domain_endpoint_options": [{"enforce_https": {"constant_value": true}}],
+ "domain_name": {"constant_value": "test"},
+ "elasticsearch_version": {"constant_value": "7.0"},
+ "encrypt_at_rest": [{"enabled": {"constant_value": true}}],
+ "log_publishing_options": [
+ {
+ "cloudwatch_log_group_arn": {"references": ["aws_cloudwatch_log_group.audit"]},
+ "enabled": {"constant_value": true},
+ "log_type": {"constant_value": "AUDIT_LOGS"}
+ },
+ {
+ "cloudwatch_log_group_arn": {"references": ["aws_cloudwatch_log_group.es_application"]},
+ "enabled": {"constant_value": true},
+ "log_type": {"constant_value": "ES_APPLICATION_LOGS"}
+ }
+ ],
+ "vpc_options": [{"subnet_ids": {"constant_value": ["subnet-efc0c6a2"]}}]
+ },
+ "schema_version": 0
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/terraform/runner/resources/resource_negative_value_without_var/main.tf b/tests/terraform/runner/resources/resource_negative_value_without_var/main.tf
new file mode 100644
index 0000000000..cab01589be
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_negative_value_without_var/main.tf
@@ -0,0 +1,48 @@
+# pass
+resource "aws_s3_bucket" "passed_bucket" {
+ bucket = "passed_bucket"
+ acl = var.private_acl
+}
+
+# fail
+resource "aws_s3_bucket" "failed_bucket" {
+ bucket = "failed_bucket"
+ acl = var.public_read_write_acl
+}
+
+### variables not in scope or dont exist ###
+
+resource "aws_s3_bucket" "unknown_acl_bucket" {
+ bucket = "unknown_acl_bucket"
+ acl = var.var_doesnt_exist
+}
+
+resource "aws_s3_bucket" "unknown_acl_bucket_2" {
+ bucket = "unknown_acl_bucket_2"
+ acl = var.unscoped_private_acl
+}
+
+resource "aws_s3_bucket" "unknown_acl_bucket_3" {
+ bucket = "unknown_acl_bucket_3"
+ acl = var.unscoped_public_read_write_acl
+}
+
+resource "aws_s3_bucket" "unknown_acl_bucket_4" {
+ bucket = "unknown_acl_bucket_4"
+ acl = local.unscoped_private_acl
+}
+
+resource "aws_s3_bucket" "unknown_acl_bucket_5" {
+ bucket = "unknown_acl_bucket_5"
+ acl = local.unscoped_public_read_write_acl
+}
+
+resource "aws_s3_bucket" "unknown_data_acl_bucket" {
+ bucket = "unknown_acl_bucket"
+ acl = data.doesnt_exist
+}
+
+resource "aws_s3_bucket" "unknown_data_acl_bucket" {
+ bucket = "unknown_acl_bucket"
+ acl = module.doesnt_exist
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/resource_negative_value_without_var/variables.tf b/tests/terraform/runner/resources/resource_negative_value_without_var/variables.tf
new file mode 100644
index 0000000000..11ca42425e
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_negative_value_without_var/variables.tf
@@ -0,0 +1,7 @@
+variable "private_acl" {
+ default = "private"
+}
+
+variable "public_read_write_acl" {
+ default = "public-read-write"
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/resource_negative_value_without_var/variables_unscoped.tf b/tests/terraform/runner/resources/resource_negative_value_without_var/variables_unscoped.tf
new file mode 100644
index 0000000000..fbb6c422e7
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_negative_value_without_var/variables_unscoped.tf
@@ -0,0 +1,12 @@
+variable "unscoped_private_acl" {
+ default = "private"
+}
+
+variable "unscoped_public_read_write_acl" {
+ default = "public-read-write"
+}
+
+locals {
+ unscoped_private_acl = "private"
+ unscoped_public_read_write_acl = "public-read-write"
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/resource_value_without_var/main.tf b/tests/terraform/runner/resources/resource_value_without_var/main.tf
new file mode 100644
index 0000000000..c330c9e114
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_value_without_var/main.tf
@@ -0,0 +1,84 @@
+# pass
+resource "aws_s3_bucket" "enabled_bucket" {
+ bucket = "enabled_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = var.versioning_enabled
+ }
+}
+
+# fail
+resource "aws_s3_bucket" "disabled_bucket" {
+ bucket = "disabled_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = var.versioning_disabled
+ }
+}
+
+### variables not in scope or dont exist ###
+
+resource "aws_s3_bucket" "unknown_var_bucket" {
+ bucket = "unknown_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = var.versioning_unknown
+ }
+}
+
+resource "aws_s3_bucket" "unknown_var_2_bucket" {
+ bucket = "unknown_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = var.versioning_disabled_2
+ }
+}
+
+resource "aws_s3_bucket" "unknown_local_bucket" {
+ bucket = "unknown_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = local.versioning_disabled
+ }
+}
+
+resource "aws_s3_bucket" "unknown_enabled_bucket" {
+ bucket = "unknown_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = var.versioning_enabled_2
+ }
+}
+
+resource "aws_s3_bucket" "unknown_enabled_local_bucket" {
+ bucket = "unknown_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = local.versioning_enabled
+ }
+}
+
+resource "aws_s3_bucket" "unknown_data_acl_bucket" {
+ bucket = "unknown_acl_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = data.doesnt_exist
+ }
+}
+
+resource "aws_s3_bucket" "unknown_data_acl_bucket" {
+ bucket = "unknown_acl_bucket"
+ acl = "private"
+
+ versioning {
+ enabled = module.doesnt_exist
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/resource_value_without_var/variables.tf b/tests/terraform/runner/resources/resource_value_without_var/variables.tf
new file mode 100644
index 0000000000..229990b290
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_value_without_var/variables.tf
@@ -0,0 +1,7 @@
+variable "versioning_enabled" {
+ default = true
+}
+
+variable "versioning_disabled" {
+ default = false
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/resource_value_without_var/variables_unscoped.tf b/tests/terraform/runner/resources/resource_value_without_var/variables_unscoped.tf
new file mode 100644
index 0000000000..d13768b1e1
--- /dev/null
+++ b/tests/terraform/runner/resources/resource_value_without_var/variables_unscoped.tf
@@ -0,0 +1,11 @@
+variable "versioning_enabled_2" {
+ default = true
+}
+variable "versioning_disabled_2" {
+ default = false
+}
+
+locals {
+ versioning_enabled = true
+ versioning_disabled = false
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/unexpected/eks_node_group_remote_access.json b/tests/terraform/runner/resources/unexpected/eks_node_group_remote_access.json
new file mode 100644
index 0000000000..8a1e7b22b7
--- /dev/null
+++ b/tests/terraform/runner/resources/unexpected/eks_node_group_remote_access.json
@@ -0,0 +1,138 @@
+{
+ "format_version": "0.1",
+ "terraform_version": "0.14.6",
+ "planned_values": {
+ "root_module": {
+ "resources": [
+ {
+ "address": "aws_eks_node_group.test",
+ "mode": "managed",
+ "type": "aws_eks_node_group",
+ "name": "test",
+ "provider_name": "registry.terraform.io/hashicorp/aws",
+ "schema_version": 0,
+ "values": {
+ "cluster_name": "test",
+ "force_update_version": null,
+ "labels": null,
+ "launch_template": [],
+ "node_group_name": "example",
+ "node_role_arn": "example-arn",
+ "remote_access": [],
+ "scaling_config": [
+ {
+ "desired_size": 1,
+ "max_size": 1,
+ "min_size": 1
+ }
+ ],
+ "subnet_ids": [
+ "subnet-ids"
+ ],
+ "tags": null,
+ "timeouts": null
+ }
+ }
+ ]
+ }
+ },
+ "resource_changes": [
+ {
+ "address": "aws_eks_node_group.test",
+ "mode": "managed",
+ "type": "aws_eks_node_group",
+ "name": "test",
+ "provider_name": "registry.terraform.io/hashicorp/aws",
+ "change": {
+ "actions": [
+ "create"
+ ],
+ "before": null,
+ "after": {
+ "cluster_name": "test",
+ "force_update_version": null,
+ "labels": null,
+ "launch_template": [],
+ "node_group_name": "example",
+ "node_role_arn": "example-arn",
+ "remote_access": [],
+ "scaling_config": [
+ {
+ "desired_size": 1,
+ "max_size": 1,
+ "min_size": 1
+ }
+ ],
+ "subnet_ids": [
+ "subnet-ids"
+ ],
+ "tags": null,
+ "timeouts": null
+ },
+ "after_unknown": {
+ "ami_type": true,
+ "arn": true,
+ "capacity_type": true,
+ "disk_size": true,
+ "id": true,
+ "instance_types": true,
+ "launch_template": [],
+ "release_version": true,
+ "remote_access": [],
+ "resources": true,
+ "scaling_config": [
+ {}
+ ],
+ "status": true,
+ "subnet_ids": [
+ false
+ ],
+ "version": true
+ }
+ }
+ }
+ ],
+ "configuration": {
+ "root_module": {
+ "resources": [
+ {
+ "address": "aws_eks_node_group.test",
+ "mode": "managed",
+ "type": "aws_eks_node_group",
+ "name": "test",
+ "provider_config_key": "aws",
+ "expressions": {
+ "cluster_name": {
+ "constant_value": "test"
+ },
+ "node_group_name": {
+ "constant_value": "example"
+ },
+ "node_role_arn": {
+ "constant_value": "example-arn"
+ },
+ "scaling_config": [
+ {
+ "desired_size": {
+ "constant_value": 1
+ },
+ "max_size": {
+ "constant_value": 1
+ },
+ "min_size": {
+ "constant_value": 1
+ }
+ }
+ ],
+ "subnet_ids": {
+ "constant_value": [
+ "subnet-ids"
+ ]
+ }
+ },
+ "schema_version": 0
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/terraform/runner/resources/unexpected/unexpected.md b/tests/terraform/runner/resources/unexpected/unexpected.md
new file mode 100644
index 0000000000..8646ef65df
--- /dev/null
+++ b/tests/terraform/runner/resources/unexpected/unexpected.md
@@ -0,0 +1,26 @@
+# Unexpected
+This folder is for different cases of test runner test data where the input HCL is maybe unexpectedly transformed when you see the json representation.
+
+This area can be used to verify that certain checks are robust in catching issues which can't be caught by unit testing the HCL input level alone.
+
+## eks_node_group_remote_access
+### Description
+`remote_access` is ommitted in HCL. But is represented as `remote_access: [ ]` in the Plan.
+
+This needs to be taken in to account when writing the check.
+### HCL Input
+```
+resource "aws_eks_node_group" "test" {
+ cluster_name = "test"
+ node_group_name = "example"
+ node_role_arn = "example-arn"
+ subnet_ids = ["subnet-ids"]
+ scaling_config {
+ desired_size = 1
+ max_size = 1
+ min_size = 1
+ }
+}
+```
+### JSON Output
+[eks_node_group_remote_access.json](eks_node_group_remote_access.json)
diff --git a/tests/terraform/runner/resources/valid_tf_only_passed_checks/example_skip_acl.tf b/tests/terraform/runner/resources/valid_tf_only_passed_checks/example_skip_acl.tf
index 4173c9609e..8063b4a42d 100644
--- a/tests/terraform/runner/resources/valid_tf_only_passed_checks/example_skip_acl.tf
+++ b/tests/terraform/runner/resources/valid_tf_only_passed_checks/example_skip_acl.tf
@@ -3,7 +3,7 @@ resource "aws_s3_bucket" "foo-bucket" {
bucket = local.bucket_name
force_destroy = true
#checkov:skip=CKV_AWS_20:The bucket is a public static content host
- #bridgecrew:skip=CKV_AWS_52: foo
+
tags = {
Name = "foo-${data.aws_caller_identity.current.account_id}"
}
diff --git a/tests/terraform/runner/test_plan_runner.py b/tests/terraform/runner/test_plan_runner.py
index 7a2c4cf5bc..5bcec734eb 100644
--- a/tests/terraform/runner/test_plan_runner.py
+++ b/tests/terraform/runner/test_plan_runner.py
@@ -6,16 +6,19 @@
class TestRunnerValid(unittest.TestCase):
-
def test_runner_two_checks_only(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_plan_path = current_dir + "/resources/plan/tfplan.json"
runner = Runner()
- checks_allowlist = ['CKV_AWS_21']
- report = runner.run(root_folder=None, files=[valid_plan_path], external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all', checks=checks_allowlist))
+ checks_allowlist = ["CKV_AWS_21"]
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all", checks=checks_allowlist),
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -30,10 +33,14 @@ def test_runner_child_modules(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_plan_path = current_dir + "/resources/plan_with_child_modules/tfplan.json"
runner = Runner()
- report = runner.run(root_folder=None, files=[valid_plan_path], external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all'))
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all"),
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -46,26 +53,48 @@ def test_runner_nested_child_modules(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_plan_path = current_dir + "/resources/plan_nested_child_modules/tfplan.json"
runner = Runner()
- report = runner.run(root_folder=None, files=[valid_plan_path], external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all'))
+ runner.graph_registry.checks = []
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=[current_dir + "/extra_yaml_checks"],
+ runner_filter=RunnerFilter(framework="all"),
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
self.assertEqual(report.get_exit_code(soft_fail=True), 0)
- self.assertEqual(report.get_summary()["failed"], 12)
+ self.assertEqual(report.get_summary()["failed"], 18)
self.assertEqual(report.get_summary()["passed"], 0)
+ failed_check_ids = set([c.check_id for c in report.failed_checks])
+ expected_failed_check_ids = {
+ "CKV_AWS_37",
+ "CKV_AWS_38",
+ "CKV_AWS_39",
+ "CKV_AWS_58",
+ "CKV_AWS_151",
+ "CUSTOM_GRAPH_AWS_1"
+ }
+
+ assert failed_check_ids == expected_failed_check_ids
+
def test_runner_root_module_resources_no_values(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_plan_path = current_dir + "/resources/plan_root_module_resources_no_values/tfplan.json"
runner = Runner()
- report = runner.run(root_folder=None, files=[valid_plan_path], external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all'))
+ runner.graph_registry.checks = []
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all"),
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -74,9 +103,20 @@ def test_runner_root_module_resources_no_values(self):
# 4 checks fail on test data for single eks resource as of present
# If more eks checks are added then this number will need to increase correspondingly to reflect
# This reasoning holds for all current pass/fails in these tests
- self.assertEqual(report.get_summary()["failed"], 4)
+ self.assertEqual(report.get_summary()["failed"], 5)
self.assertEqual(report.get_summary()["passed"], 0)
+ failed_check_ids = set([c.check_id for c in report.failed_checks])
+ expected_failed_check_ids = {
+ "CKV_AWS_37",
+ "CKV_AWS_38",
+ "CKV_AWS_39",
+ "CKV_AWS_58",
+ "CKV_AWS_151",
+ }
+
+ assert failed_check_ids == expected_failed_check_ids
+
def test_runner_data_resource_partial_values(self):
# In rare circumstances a data resource with partial values in the plan could cause false negatives
# Often 'data' does not even appear in the *_modules[x].resouces field within planned_values and is not scanned as expected
@@ -88,40 +128,56 @@ def test_runner_data_resource_partial_values(self):
# This test verifies that such a circumstance stops occurring
# There is a EKS Managed Resource and a EKS Data Resource
# The EKS Managed Resource should have 4 failures corresponding with EKS checks.
- # The EKS Data Resource should not be scanned. Previously this would cause 8 failures.
+ # The EKS Data Resource should not be scanned. Previously this would cause 8 failures.
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_plan_path = current_dir + "/resources/plan_data_resource_partial_values/tfplan.json"
runner = Runner()
- report = runner.run(root_folder=None, files=[valid_plan_path], external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all'))
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all"),
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
self.assertEqual(report.get_exit_code(soft_fail=True), 0)
- self.assertEqual(report.get_summary()["failed"], 4)
+ self.assertEqual(report.get_summary()["failed"], 5)
self.assertEqual(report.get_summary()["passed"], 0)
+ failed_check_ids = set([c.check_id for c in report.failed_checks])
+ expected_failed_check_ids = {
+ "CKV_AWS_37",
+ "CKV_AWS_38",
+ "CKV_AWS_39",
+ "CKV_AWS_58",
+ "CKV_AWS_151",
+ }
+
+ assert failed_check_ids == expected_failed_check_ids
+
def test_runner_root_dir(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = current_dir + "/resources"
runner = Runner()
- report = runner.run(root_folder=root_dir, files=None, external_checks_dir=None,
- runner_filter=RunnerFilter(framework='all'))
+ report = runner.run(
+ root_folder=root_dir, files=None, external_checks_dir=None, runner_filter=RunnerFilter(framework="all")
+ )
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
self.assertEqual(report.get_exit_code(soft_fail=True), 0)
- self.assertEqual(60, report.get_summary()["failed"])
- self.assertEqual(61, report.get_summary()["passed"])
+ self.assertGreaterEqual(report.get_summary()["failed"], 88)
+ self.assertGreaterEqual(report.get_summary()["passed"], 76)
files_scanned = list(set(map(lambda rec: rec.file_path, report.failed_checks)))
- self.assertGreaterEqual(5, len(files_scanned))
+ self.assertGreaterEqual(len(files_scanned), 6)
def test_record_relative_path_with_relative_dir(self):
@@ -135,9 +191,12 @@ def test_record_relative_path_with_relative_dir(self):
dir_rel_path = os.path.relpath(scan_dir_path)
runner = Runner()
- checks_allowlist = ['CKV_AWS_20']
- report = runner.run(root_folder=dir_rel_path, external_checks_dir=None,
- runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
+ checks_allowlist = ["CKV_AWS_20"]
+ report = runner.run(
+ root_folder=dir_rel_path,
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="terraform", checks=checks_allowlist),
+ )
all_checks = report.failed_checks + report.passed_checks
for record in all_checks:
@@ -155,9 +214,12 @@ def test_record_relative_path_with_abs_dir(self):
dir_abs_path = os.path.abspath(scan_dir_path)
runner = Runner()
- checks_allowlist = ['CKV_AWS_20']
- report = runner.run(root_folder=dir_abs_path, external_checks_dir=None,
- runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
+ checks_allowlist = ["CKV_AWS_20"]
+ report = runner.run(
+ root_folder=dir_abs_path,
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="terraform", checks=checks_allowlist),
+ )
all_checks = report.failed_checks + report.passed_checks
for record in all_checks:
@@ -176,9 +238,13 @@ def test_record_relative_path_with_relative_file(self):
file_rel_path = os.path.relpath(scan_file_path)
runner = Runner()
- checks_allowlist = ['CKV_AWS_20']
- report = runner.run(root_folder=None, external_checks_dir=None, files=[file_rel_path],
- runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
+ checks_allowlist = ["CKV_AWS_20"]
+ report = runner.run(
+ root_folder=None,
+ external_checks_dir=None,
+ files=[file_rel_path],
+ runner_filter=RunnerFilter(framework="terraform", checks=checks_allowlist),
+ )
all_checks = report.failed_checks + report.passed_checks
for record in all_checks:
@@ -196,15 +262,61 @@ def test_record_relative_path_with_abs_file(self):
file_abs_path = os.path.abspath(scan_file_path)
runner = Runner()
- checks_allowlist = ['CKV_AWS_20']
- report = runner.run(root_folder=None, external_checks_dir=None, files=[file_abs_path],
- runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
+ checks_allowlist = ["CKV_AWS_20"]
+ report = runner.run(
+ root_folder=None,
+ external_checks_dir=None,
+ files=[file_abs_path],
+ runner_filter=RunnerFilter(framework="terraform", checks=checks_allowlist),
+ )
all_checks = report.failed_checks + report.passed_checks
for record in all_checks:
# The plan runner sets file_path to be relative from the CWD already, so this is easy
self.assertEqual(record.repo_file_path, record.file_path)
+ def test_runner_unexpected_eks_node_group_remote_access(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_plan_path = current_dir + "/resources/unexpected/eks_node_group_remote_access.json"
+ runner = Runner()
+ report = runner.run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all"),
+ )
+ report_json = report.get_json()
+ self.assertIsInstance(report_json, str)
+ self.assertIsNotNone(report_json)
+ self.assertIsNotNone(report.get_test_suites())
+ self.assertEqual(report.get_exit_code(soft_fail=False), 0)
+ self.assertEqual(report.get_exit_code(soft_fail=True), 0)
+
+ self.assertEqual(report.get_summary()["failed"], 0)
+ self.assertEqual(report.get_summary()["passed"], 1)
+
+ def test_runner_with_resource_reference(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ valid_plan_path = current_dir + "/resources/plan_with_resource_reference/tfplan.json"
+ allowed_checks = ["CKV_AWS_84"]
+
+ report = Runner().run(
+ root_folder=None,
+ files=[valid_plan_path],
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(framework="all", checks=allowed_checks),
+ )
+
+ report_json = report.get_json()
+ self.assertIsInstance(report_json, str)
+ self.assertIsNotNone(report_json)
+ self.assertIsNotNone(report.get_test_suites())
+ self.assertEqual(report.get_exit_code(soft_fail=False), 0)
+ self.assertEqual(report.get_exit_code(soft_fail=True), 0)
+
+ self.assertEqual(report.get_summary()["failed"], 0)
+ self.assertEqual(report.get_summary()["passed"], 1)
+
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/tests/terraform/runner/test_runner.py b/tests/terraform/runner/test_runner.py
index f6342f5f89..6716e2fa99 100644
--- a/tests/terraform/runner/test_runner.py
+++ b/tests/terraform/runner/test_runner.py
@@ -1,11 +1,18 @@
+import inspect
+import json
import os
import unittest
+import dis
+from pathlib import Path
+from checkov.common.checks_infra.registry import get_graph_checks_registry
+from checkov.common.output.report import Report
from checkov.runner_filter import RunnerFilter
from checkov.terraform.context_parsers.registry import parser_registry
-from checkov.terraform.runner import Runner
-from checkov.common.output.report import Report
from checkov.terraform.parser import Parser
+from checkov.terraform.runner import Runner, resource_registry
+
+CUSTOM_GRAPH_CHECK_ID = 'CKV2_CUSTOM_1'
class TestRunnerValid(unittest.TestCase):
@@ -18,7 +25,7 @@ def test_runner_two_checks_only(self):
report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
runner_filter=RunnerFilter(framework='all', checks=checks_allowlist))
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
for record in report.failed_checks:
@@ -32,7 +39,7 @@ def test_runner_denylist_checks(self):
report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
runner_filter=RunnerFilter(framework='all', skip_checks=checks_denylist))
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -46,7 +53,7 @@ def test_runner_valid_tf(self):
runner = Runner()
report = runner.run(root_folder=valid_dir_path, external_checks_dir=None)
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -58,6 +65,7 @@ def test_runner_valid_tf(self):
report.print_json()
report.print_console()
report.print_console(is_quiet=True)
+ report.print_console(is_quiet=True, is_compact=True)
report.print_junit_xml()
report.print_failed_github_md()
@@ -70,15 +78,76 @@ def test_runner_passing_valid_tf(self):
runner = Runner()
report = runner.run(root_folder=passing_tf_dir_path, external_checks_dir=None)
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
- # self.assertEqual(report.get_exit_code(), 0)
+ self.assertEqual(report.get_exit_code(False), 1)
summary = report.get_summary()
self.assertGreaterEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 0)
- self.assertEqual(summary['skipped'], 2)
- self.assertEqual(summary["parsing_errors"], 0)
+ self.assertEqual(3, summary['failed'])
+ self.assertEqual(1, summary['skipped'])
+ self.assertEqual(0, summary["parsing_errors"])
+
+ def test_runner_extra_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ tf_dir_path = current_dir + "/resources/extra_check_test"
+ extra_checks_dir_path = [current_dir + "/extra_checks"]
+
+ print("testing dir" + tf_dir_path)
+ runner = Runner()
+ report = runner.run(root_folder=tf_dir_path, external_checks_dir=extra_checks_dir_path)
+ report_json = report.get_json()
+ for check in resource_registry.checks["aws_s3_bucket"]:
+ if check.id == "CUSTOM_AWS_1":
+ resource_registry.checks["aws_s3_bucket"].remove(check)
+ self.assertIsInstance(report_json, str)
+ self.assertIsNotNone(report_json)
+ self.assertIsNotNone(report.get_test_suites())
+
+ passing_custom = 0
+ failed_custom = 0
+ for record in report.passed_checks:
+ if record.check_id == "CUSTOM_AWS_1":
+ passing_custom = passing_custom + 1
+ for record in report.failed_checks:
+ if record.check_id == "CUSTOM_AWS_1":
+ failed_custom = failed_custom + 1
+
+ self.assertEqual(1, passing_custom)
+ self.assertEqual(2, failed_custom)
+ # Remove external checks from registry.
+ runner.graph_registry.checks[:] = [check for check in runner.graph_registry.checks if "CUSTOM" not in check.id]
+
+ def test_runner_extra_yaml_check(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ tf_dir_path = current_dir + "/resources/extra_check_test"
+ extra_checks_dir_path = [current_dir + "/extra_yaml_checks"]
+
+ runner = Runner()
+ report = runner.run(root_folder=tf_dir_path, external_checks_dir=extra_checks_dir_path)
+ report_json = report.get_json()
+ for check in resource_registry.checks["aws_s3_bucket"]:
+ if check.id == "CKV2_CUSTOM_1":
+ resource_registry.checks["aws_s3_bucket"].remove(check)
+ self.assertIsInstance(report_json, str)
+ self.assertIsNotNone(report_json)
+ self.assertIsNotNone(report.get_test_suites())
+
+ passing_custom = 0
+ failed_custom = 0
+ for record in report.passed_checks:
+ if record.check_id == "CKV2_CUSTOM_1":
+ passing_custom = passing_custom + 1
+ for record in report.failed_checks:
+ if record.check_id == "CKV2_CUSTOM_1":
+ failed_custom = failed_custom + 1
+
+ self.assertEqual(passing_custom, 0)
+ self.assertEqual(failed_custom, 3)
+ # Remove external checks from registry.
+ runner.graph_registry.checks[:] = [check for check in runner.graph_registry.checks if "CUSTOM" not in check.id]
def test_runner_specific_file(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
@@ -88,14 +157,14 @@ def test_runner_specific_file(self):
runner = Runner()
report = runner.run(root_folder=None, external_checks_dir=None, files=[passing_tf_file_path])
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
# self.assertEqual(report.get_exit_code(), 0)
summary = report.get_summary()
self.assertGreaterEqual(summary['passed'], 1)
- self.assertEqual(summary['failed'], 0)
- self.assertEqual(summary["parsing_errors"], 0)
+ self.assertEqual(2, summary['failed'])
+ self.assertEqual(0, summary["parsing_errors"])
def test_check_ids_dont_collide(self):
runner = Runner()
@@ -110,7 +179,7 @@ def test_check_ids_dont_collide(self):
# A single check can have multiple resource blocks it checks, which means it will show up multiple times in the registry
bad_checks.append(f'{check.id}: {check.name}')
print(f'{check.id}: {check.name}')
- self.assertEqual(len(bad_checks), 0)
+ self.assertEqual(len(bad_checks), 0, f'Bad checks: {bad_checks}')
def test_no_missing_ids(self):
runner = Runner()
@@ -119,32 +188,77 @@ def test_no_missing_ids(self):
checks = [check for entity_type in list(registry.checks.values()) for check in entity_type]
for check in checks:
unique_checks.add(check.id)
- aws_checks = list(filter(lambda check_id: '_AWS_' in check_id, unique_checks))
- for i in range(1, len(aws_checks)):
+ aws_checks = sorted(list(filter(lambda check_id: '_AWS_' in check_id, unique_checks)), reverse=True, key=lambda s: int(s.split('_')[-1]))
+ for i in range(1, len(aws_checks) + 4):
if f'CKV_AWS_{i}' == 'CKV_AWS_4':
# CKV_AWS_4 was deleted due to https://github.com/bridgecrewio/checkov/issues/371
continue
- if f'CKV_AWS_{i}' == 'CKV_AWS_95':
+ if f'CKV_AWS_{i}' in ('CKV_AWS_132', 'CKV_AWS_125'):
+ # These checks were removed because they were duplicates
+ continue
+ if f'CKV_AWS_{i}' in 'CKV_AWS_95':
# CKV_AWS_95 is currently implemented just on cfn
continue
+ if f'CKV_AWS_{i}' == 'CKV_AWS_52':
+ # CKV_AWS_52 was deleted since it cannot be toggled in terraform.
+ continue
self.assertIn(f'CKV_AWS_{i}', aws_checks, msg=f'The new AWS violation should have the ID "CKV_AWS_{i}"')
- gcp_checks = list(filter(lambda check_id: '_GCP_' in check_id, unique_checks))
- for i in range(1, len(gcp_checks)):
+ gcp_checks = sorted(list(filter(lambda check_id: '_GCP_' in check_id, unique_checks)), reverse=True, key=lambda s: int(s.split('_')[-1]))
+ for i in range(1, len(gcp_checks) + 1):
+ if f'CKV_GCP_{i}' == 'CKV_GCP_5':
+ # CKV_GCP_5 is no longer a valid platform check
+ continue
+
self.assertIn(f'CKV_GCP_{i}', gcp_checks, msg=f'The new GCP violation should have the ID "CKV_GCP_{i}"')
- azure_checks = list(filter(lambda check_id: '_AZURE_' in check_id, unique_checks))
- for i in range(1, len(azure_checks)):
+ azure_checks = sorted(list(filter(lambda check_id: '_AZURE_' in check_id, unique_checks)), reverse=True, key=lambda s: int(s.split('_')[-1]))
+ for i in range(1, len(azure_checks) + 1):
if f'CKV_AZURE_{i}' == 'CKV_AZURE_43':
continue # Pending merge; blocked by another issue https://github.com/bridgecrewio/checkov/pull/429
+ if f'CKV_AZURE_{i}' == 'CKV_AZURE_51':
+ continue # https://github.com/bridgecrewio/checkov/pull/983
self.assertIn(f'CKV_AZURE_{i}', azure_checks,
msg=f'The new Azure violation should have the ID "CKV_AZURE_{i}"')
+ graph_registry = get_graph_checks_registry("terraform")
+ graph_registry.load_checks()
+ graph_checks = list(filter(lambda check: 'CKV2_' in check.id, graph_registry.checks))
+
+ # add cloudformation checks to graph checks
+ graph_registry = get_graph_checks_registry("cloudformation")
+ graph_registry.load_checks()
+ graph_checks.extend(list(filter(lambda check: 'CKV2_' in check.id, graph_registry.checks)))
+
+ aws_checks, gcp_checks, azure_checks = [], [], []
+ for check in graph_checks:
+ if '_AWS_' in check.id:
+ aws_checks.append(check.id)
+ elif '_GCP_' in check.id:
+ gcp_checks.append(check.id)
+ elif '_AZURE_' in check.id:
+ azure_checks.append(check.id)
+
+ for check_list in [aws_checks, gcp_checks, azure_checks]:
+ check_list.sort(reverse=True, key=lambda s: int(s.split('_')[-1]))
+
+ for i in range(1, len(aws_checks) + 1):
+ if f'CKV2_AWS_{i}' == 'CKV2_AWS_17':
+ # CKV2_AWS_17 was overly keen and those resources it checks are created by default
+ continue
+ self.assertIn(f'CKV2_AWS_{i}', aws_checks,
+ msg=f'The new AWS violation should have the ID "CKV2_AWS_{i}"')
+ for i in range(1, len(gcp_checks) + 1):
+ self.assertIn(f'CKV2_GCP_{i}', gcp_checks,
+ msg=f'The new GCP violation should have the ID "CKV2_GCP_{i}"')
+ for i in range(1, len(azure_checks) + 1):
+ self.assertIn(f'CKV2_AZURE_{i}', azure_checks,
+ msg=f'The new Azure violation should have the ID "CKV2_AZURE_{i}"')
+
def test_provider_uniqueness(self):
current_dir = os.path.dirname(os.path.realpath(__file__))
valid_dir_path = current_dir + "/resources/many_providers"
- tf_file = f"{valid_dir_path}/main.tf"
runner = Runner()
result = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
runner_filter=RunnerFilter(checks='CKV_AWS_41'))
@@ -238,7 +352,8 @@ def test_parser_error_handled_for_file_target(self):
invalid_dir_abs_path = os.path.abspath(invalid_dir_path)
runner = Runner()
- result = runner.run(files=[os.path.join(invalid_dir_path, file) for file in file_names], root_folder=None, external_checks_dir=None)
+ result = runner.run(files=[os.path.join(invalid_dir_path, file) for file in file_names], root_folder=None,
+ external_checks_dir=None)
self.assertEqual(len(result.parsing_errors), 2)
for file in file_names:
@@ -262,7 +377,7 @@ def __init__(self):
categories = []
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
- def scan_resource_conf(self, conf, entity_type):
+ def scan_entity_conf(self, conf, entity_type):
if entity_type == 'type_1':
test_self.assertIn('a', conf)
test_self.assertEqual([1], conf['a'])
@@ -274,6 +389,9 @@ def scan_resource_conf(self, conf, entity_type):
f'other resources are defined in the files inside of {test_dir}.')
return CheckResult.PASSED
+ def scan_resource_conf(self, conf):
+ pass
+
check = ResourceCheck()
current_dir = os.path.dirname(os.path.realpath(__file__))
@@ -320,8 +438,8 @@ def test_external_definitions_context(self):
'provider': {'kubernetes': {'default': {'start_line': 49, 'end_line': 55,
'code_lines': [(49, 'provider "kubernetes" {\n'),
(50, ' version = "1.10.0"\n'), (
- 51,
- ' host = module.aks_cluster.kube_config.0.host\n'),
+ 51,
+ ' host = module.aks_cluster.kube_config.0.host\n'),
(52,
' client_certificate = base64decode(module.aks_cluster.kube_config.0.client_certificate)\n'),
(53,
@@ -367,13 +485,14 @@ def test_external_definitions_context(self):
{'id': 'CKV_AWS_20', 'suppress_comment': 'The bucket is a public static content host'},
{'id': 'CKV_AWS_52', 'suppress_comment': ' foo'}]}}}, 'data': {'aws_caller_identity': {
'current': {'start_line': 27, 'end_line': 0, 'code_lines': [], 'skipped_checks': []}}}}}
- tf_definitions = {}
+ tf_definitions = {'/mock/os/checkov_v2/tests/terraform/runner/resources/valid_tf_only_passed_checks/example.tf': {'resource': [{'aws_s3_bucket': {'foo-bucket': {'region': ['${var.region}'], 'bucket': ['${local.bucket_name}'], 'force_destroy': [True], 'versioning': [{'enabled': [True], 'mfa_delete': [True]}], 'logging': [{'target_bucket': ['${aws_s3_bucket.log_bucket.id}'], 'target_prefix': ['log/']}], 'server_side_encryption_configuration': [{'rule': [{'apply_server_side_encryption_by_default': [{'kms_master_key_id': ['${aws_kms_key.mykey.arn}'], 'sse_algorithm': ['aws:kms']}]}]}], 'acl': ['private'], 'tags': ['${merge\n (\n var.common_tags,\n map(\n "name", "VM Virtual Machine",\n "group", "foo"\n )\n )\n }']}}}], 'data': [{'aws_caller_identity': {'current': {}}}], 'provider': [{'kubernetes': {'version': ['1.10.0'], 'host': ['${module.aks_cluster.kube_config[0].host}'], 'client_certificate': ['${base64decode(module.aks_cluster.kube_config[0].client_certificate)}'], 'client_key': ['${base64decode(module.aks_cluster.kube_config[0].client_key)}'], 'cluster_ca_certificate': ['${base64decode(module.aks_cluster.kube_config[0].cluster_ca_certificate)}']}}], 'module': [{'new_relic': {'source': ['s3::https://s3.amazonaws.com/my-artifacts/new-relic-k8s-0.2.5.zip'], 'kubernetes_host': ['${module.aks_cluster.kube_config[0].host}'], 'kubernetes_client_certificate': ['${base64decode(module.aks_cluster.kube_config[0].client_certificate)}'], 'kubernetes_client_key': ['${base64decode(module.aks_cluster.kube_config[0].client_key)}'], 'kubernetes_cluster_ca_certificate': ['${base64decode(module.aks_cluster.kube_config[0].cluster_ca_certificate)}'], 'cluster_name': ['${module.naming_conventions.aks_name}'], 'new_relic_license': ['${data.vault_generic_secret.new_relic_license.data["license"]}'], 'cluster_ca_bundle_b64': ['${module.aks_cluster.kube_config[0].cluster_ca_certificate}'], 'module_depends_on': [['${null_resource.delay_aks_deployments}']]}}]}, '/mock/os/checkov_v2/tests/terraform/runner/resources/valid_tf_only_passed_checks/example_skip_acl.tf': {'resource': [{'aws_s3_bucket': {'foo-bucket': {'region': ['${var.region}'], 'bucket': ['${local.bucket_name}'], 'force_destroy': [True], 'tags': [{'Name': 'foo-${data.aws_caller_identity.current.account_id}'}], 'versioning': [{'enabled': [True]}], 'logging': [{'target_bucket': ['${aws_s3_bucket.log_bucket.id}'], 'target_prefix': ['log/']}], 'server_side_encryption_configuration': [{'rule': [{'apply_server_side_encryption_by_default': [{'kms_master_key_id': ['${aws_kms_key.mykey.arn}'], 'sse_algorithm': ['aws:kms']}]}]}], 'acl': ['public-read']}}}], 'data': [{'aws_caller_identity': {'current': {}}}]}}
runner = Runner()
parser = Parser()
- runner.tf_definitions = tf_definitions
+ runner.definitions = tf_definitions
+ runner.set_external_data(tf_definitions, external_definitions_context, breadcrumbs={})
parser.parse_directory(tf_dir_path, tf_definitions)
report = Report('terraform')
- runner.check_tf_definition(root_folder=tf_dir_path, report=report, runner_filter=RunnerFilter(), external_definitions_context=external_definitions_context)
+ runner.check_tf_definition(root_folder=tf_dir_path, report=report, runner_filter=RunnerFilter())
self.assertGreaterEqual(len(report.passed_checks), 1)
def test_failure_in_resolved_module(self):
@@ -385,7 +504,7 @@ def test_failure_in_resolved_module(self):
report = runner.run(root_folder=valid_dir_path, external_checks_dir=None,
runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
report_json = report.get_json()
- self.assertTrue(isinstance(report_json, str))
+ self.assertIsInstance(report_json, str)
self.assertIsNotNone(report_json)
self.assertIsNotNone(report.get_test_suites())
self.assertEqual(report.get_exit_code(soft_fail=False), 1)
@@ -415,7 +534,9 @@ def test_record_relative_path_with_relative_dir(self):
runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
+
for record in all_checks:
# no need to join with a '/' because the TF runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -426,10 +547,9 @@ def test_record_relative_path_with_abs_dir(self):
# this is just constructing the scan dir as normal
current_dir = os.path.dirname(os.path.realpath(__file__))
- scan_dir_path = os.path.join(current_dir, "resources", "nested_dir")
+ scan_dir_path = os.path.join(current_dir, "resources", "nested_dir")
dir_rel_path = os.path.relpath(scan_dir_path)
-
dir_abs_path = os.path.abspath(scan_dir_path)
runner = Runner()
@@ -438,7 +558,9 @@ def test_record_relative_path_with_abs_dir(self):
runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
+
for record in all_checks:
# no need to join with a '/' because the TF runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{dir_rel_path}{record.file_path}')
@@ -460,7 +582,9 @@ def test_record_relative_path_with_relative_file(self):
runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
+
for record in all_checks:
# no need to join with a '/' because the TF runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
@@ -482,14 +606,239 @@ def test_record_relative_path_with_abs_file(self):
runner_filter=RunnerFilter(framework='terraform', checks=checks_allowlist))
all_checks = report.failed_checks + report.passed_checks
- self.assertTrue(len(all_checks) > 0) # ensure that the assertions below are going to do something
+
+ self.assertGreater(len(all_checks), 0) # ensure that the assertions below are going to do something
+
for record in all_checks:
# no need to join with a '/' because the TF runner adds it to the start of the file path
self.assertEqual(record.repo_file_path, f'/{file_rel_path}')
+ def test_runner_malformed_857(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ passing_tf_file_path = current_dir + "/resources/malformed_857/main.tf"
+
+ runner = Runner()
+ runner.run(root_folder=None, external_checks_dir=None, files=[passing_tf_file_path])
+ # If we get here all is well. :-) Failure would throw an exception.
+
+ def test_module_failure_reporting_772(self):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ report = Runner().run(root_folder=f"{current_dir}/resources/module_failure_reporting_772",
+ external_checks_dir=None,
+ runner_filter=RunnerFilter(checks="CKV_AWS_19")) # bucket encryption
+
+ self.assertEqual(len(report.failed_checks), 2)
+ self.assertEqual(len(report.passed_checks), 0)
+
+ found_inside = False
+ found_outside = False
+ for record in report.failed_checks:
+ # "outside" bucket (not defined in a module) should be a direct resource path and
+ # should not have caller file info.
+ if "outside" in record.resource:
+ found_outside = True
+ self.assertEqual(record.resource, "aws_s3_bucket.outside")
+ assert record.file_path == "/main.tf"
+ self.assertEqual(record.file_line_range, [11, 13])
+ self.assertIsNone(record.caller_file_path)
+ self.assertIsNone(record.caller_file_line_range)
+
+ if "inside" in record.resource:
+ found_inside = True
+ self.assertEqual(record.resource, "module.test_module.aws_s3_bucket.inside")
+ assert record.file_path == "/module/module.tf"
+ self.assertEqual(record.file_line_range, [7, 9])
+ assert record.caller_file_path == "/main.tf"
+ # ATTENTION!! If this breaks, see the "HACK ALERT" comment in runner.run_block.
+ # A bug might have been fixed.
+ self.assertEqual(record.caller_file_line_range, [6, 8])
+
+ self.assertTrue(found_inside)
+ self.assertTrue(found_outside)
+
+ def test_loading_external_checks_yaml(self):
+ runner = Runner()
+ runner.graph_registry.checks = []
+ runner.graph_registry.load_checks()
+ base_len = len(runner.graph_registry.checks)
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ extra_checks_dir_path = current_dir + "/extra_yaml_checks"
+ runner.load_external_checks([extra_checks_dir_path])
+ self.assertEqual(len(runner.graph_registry.checks), base_len + 2)
+ runner.graph_registry.checks = runner.graph_registry.checks[:base_len]
+
+ def test_loading_external_checks_yaml_multiple_times(self):
+ runner = Runner()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ runner.graph_registry.checks = []
+ extra_checks_dir_path = [current_dir + "/extra_yaml_checks"]
+ runner.load_external_checks(extra_checks_dir_path)
+ self.assertEqual(len(runner.graph_registry.checks), 2)
+ runner.load_external_checks(extra_checks_dir_path)
+ self.assertEqual(len(runner.graph_registry.checks), 2)
+ self.assertIn('CUSTOM_GRAPH_AWS_1', [x.id for x in runner.graph_registry.checks])
+ self.assertIn('CKV2_CUSTOM_1', [x.id for x in runner.graph_registry.checks])
+ runner.graph_registry.checks = []
+
+ def test_loading_external_checks_python(self):
+ runner = Runner()
+ from tests.terraform.runner.extra_checks.S3EnvironmentCheck import scanner
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ extra_checks_dir_paths = [current_dir + "/extra_checks"]
+ runner.load_external_checks(extra_checks_dir_paths)
+ found = 0
+ for resource_type in scanner.supported_resources:
+ checks = resource_registry.checks[resource_type]
+ self.assertIn(scanner, checks)
+ found += 1
+ self.assertEqual(found, len(scanner.supported_resources))
+
+ def test_loading_external_checks_python_multiple_times(self):
+ runner = Runner()
+ from tests.terraform.runner.extra_checks.S3EnvironmentCheck import scanner
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ extra_checks_dir_paths = [current_dir + "/extra_checks", current_dir + "/extra_checks"]
+ runner.load_external_checks(extra_checks_dir_paths)
+ found = 0
+ for resource_type in scanner.supported_resources:
+ checks = resource_registry.checks[resource_type]
+ self.assertIn(scanner, checks)
+ instances = list(filter(lambda c: c.id == scanner.id, checks))
+ self.assertEqual(len(instances), 1)
+ found += 1
+
+ self.assertEqual(found, len(scanner.supported_resources))
+
+ def test_loading_external_checks_python_and_yaml(self):
+ runner = Runner()
+ from tests.terraform.runner.extra_checks.S3EnvironmentCheck import scanner
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ extra_checks_dir_paths = [current_dir + "/extra_checks", current_dir + "/extra_yaml_checks"]
+ runner.load_external_checks(extra_checks_dir_paths)
+ found = 0
+ for resource_type in scanner.supported_resources:
+ checks = resource_registry.checks[resource_type]
+ self.assertIn(scanner, checks)
+ found += 1
+ self.assertEqual(found, len(scanner.supported_resources))
+ self.assertEqual(len(list(filter(lambda c: c.id == CUSTOM_GRAPH_CHECK_ID, runner.graph_registry.checks))), 1)
+ # Remove external checks from registry.
+ runner.graph_registry.checks[:] = [check for check in runner.graph_registry.checks if "CUSTOM" not in check.id]
+
+ def test_wrong_check_imports(self):
+ wrong_imports = ["arm", "cloudformation", "dockerfile", "helm", "kubernetes", "serverless"]
+ check_imports = []
+
+ checks_path = Path(inspect.getfile(Runner)).parent.joinpath("checks")
+ for file in checks_path.rglob("*.py"):
+ with file.open() as f:
+ instructions = dis.get_instructions(f.read())
+ import_names = [instr.argval for instr in instructions if "IMPORT_NAME" == instr.opname]
+
+ for import_name in import_names:
+ wrong_import = next((import_name for x in wrong_imports if x in import_name), None)
+ if wrong_import:
+ check_imports.append({file.name: wrong_import})
+
+ assert len(check_imports) == 0, f"Wrong imports were added: {check_imports}"
+
+ def test_entity_context_fetching(self):
+ runner = Runner()
+ runner.context = {'/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf': {'module': {'module': {'vpc': {'start_line': 1, 'end_line': 7, 'code_lines': [(1, 'module "vpc" {\n'), (2, ' source = "../../"\n'), (3, ' cidr = var.cidr\n'), (4, ' zone = var.zone\n'), (5, ' common_tags = var.common_tags\n'), (6, ' account_name = var.account_name\n'), (7, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/example/examplea/provider.aws.tf': {'provider': {'aws': {'default': {'start_line': 1, 'end_line': 3, 'code_lines': [(1, 'provider "aws" {\n'), (2, ' region = "eu-west-2"\n'), (3, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/example/examplea/variables.tf': {'variable': {'cidr': {'start_line': 1, 'end_line': 3, 'code_lines': [(1, 'variable "cidr" {\n'), (2, ' type = string\n'), (3, '}\n')], 'skipped_checks': []}, 'zone': {'start_line': 5, 'end_line': 7, 'code_lines': [(5, 'variable "zone" {\n'), (6, ' type = list(any)\n'), (7, '}\n')], 'skipped_checks': []}, 'account_name': {'start_line': 9, 'end_line': 11, 'code_lines': [(9, 'variable "account_name" {\n'), (10, ' type = string\n'), (11, '}\n')], 'skipped_checks': []}, 'common_tags': {'start_line': 13, 'end_line': 15, 'code_lines': [(13, 'variable "common_tags" {\n'), (14, ' type = map(any)\n'), (15, '}\n')], 'skipped_checks': []}}}, '/mock/os/terraform-aws-vpc/aws_eip.nateip.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_eip': {'nateip': {'start_line': 1, 'end_line': 4, 'code_lines': [(1, 'resource "aws_eip" "nateip" {\n'), (2, ' count = var.subnets\n'), (3, ' tags = var.common_tags\n'), (4, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_internet_gateway.gw.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_internet_gateway': {'gw': {'start_line': 1, 'end_line': 6, 'code_lines': [(1, 'resource "aws_internet_gateway" "gw" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, '\n'), (4, ' tags = merge(var.common_tags,\n'), (5, ' tomap({ "Name" = "${upper(var.account_name)}-IGW" }))\n'), (6, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_nat_gateway.natgateway.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_nat_gateway': {'natgateway': {'start_line': 1, 'end_line': 8, 'code_lines': [(1, 'resource "aws_nat_gateway" "natgateway" {\n'), (2, ' count = var.subnets\n'), (3, ' allocation_id = element(aws_eip.nateip.*.id, count.index)\n'), (4, ' depends_on = [aws_internet_gateway.gw]\n'), (5, ' subnet_id = element(aws_subnet.public.*.id, count.index)\n'), (6, ' tags = merge(var.common_tags,\n'), (7, ' tomap({ "Name" = "${upper(var.account_name)}-AZ${count.index + 1}" }))\n'), (8, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_network_acl.NetworkAclPrivate.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_network_acl': {'networkaclprivate': {'start_line': 1, 'end_line': 25, 'code_lines': [(1, 'resource "aws_network_acl" "networkaclprivate" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, ' subnet_ids = aws_subnet.private.*.id\n'), (4, '\n'), (5, ' egress {\n'), (6, ' rule_no = 100\n'), (7, ' action = "allow"\n'), (8, ' cidr_block = "0.0.0.0/0"\n'), (9, ' from_port = 0\n'), (10, ' to_port = 0\n'), (11, ' protocol = "all"\n'), (12, ' }\n'), (13, '\n'), (14, ' ingress {\n'), (15, ' rule_no = 100\n'), (16, ' action = "allow"\n'), (17, ' cidr_block = "0.0.0.0/0"\n'), (18, ' from_port = 0\n'), (19, ' to_port = 0\n'), (20, ' protocol = "all"\n'), (21, ' }\n'), (22, '\n'), (23, ' tags = merge(var.common_tags,\n'), (24, ' tomap({ "Name" = "${var.account_name}-NetworkAcl-Private" }))\n'), (25, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_network_acl.NetworkAclPublic.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_network_acl': {'networkaclpublic': {'start_line': 1, 'end_line': 25, 'code_lines': [(1, 'resource "aws_network_acl" "networkaclpublic" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, ' subnet_ids = aws_subnet.public.*.id\n'), (4, '\n'), (5, ' egress {\n'), (6, ' rule_no = 100\n'), (7, ' action = "allow"\n'), (8, ' cidr_block = "0.0.0.0/0"\n'), (9, ' from_port = 0\n'), (10, ' to_port = 0\n'), (11, ' protocol = "all"\n'), (12, ' }\n'), (13, '\n'), (14, ' ingress {\n'), (15, ' rule_no = 100\n'), (16, ' action = "allow"\n'), (17, ' cidr_block = "0.0.0.0/0"\n'), (18, ' from_port = 0\n'), (19, ' to_port = 0\n'), (20, ' protocol = "all"\n'), (21, ' }\n'), (22, '\n'), (23, ' tags = merge(var.common_tags,\n'), (24, ' tomap({ "Name" = "${var.account_name}-NetworkAcl-Public" }))\n'), (25, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_route_table.private.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_route_table': {'private': {'start_line': 1, 'end_line': 8, 'code_lines': [(1, 'resource "aws_route_table" "private" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, '\n'), (4, ' propagating_vgws = [aws_vpn_gateway.vpn_gw.id]\n'), (5, '\n'), (6, ' tags = merge(var.common_tags,\n'), (7, ' tomap({ "Name" = "${var.account_name}-Private-${element(aws_subnet.private.*.id, 0)}" }))\n'), (8, '}\n')], 'skipped_checks': []}}, 'aws_route': {'private': {'start_line': 10, 'end_line': 14, 'code_lines': [(10, 'resource "aws_route" "private" {\n'), (11, ' route_table_id = aws_route_table.private.id\n'), (12, ' destination_cidr_block = "0.0.0.0/0"\n'), (13, ' nat_gateway_id = element(aws_nat_gateway.natgateway.*.id, 0)\n'), (14, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_route_table.public.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_route_table': {'public': {'start_line': 1, 'end_line': 6, 'code_lines': [(1, 'resource "aws_route_table" "public" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, '\n'), (4, ' tags = merge(var.common_tags,\n'), (5, ' tomap({ "Name" = "${upper(var.account_name)}-Public" }))\n'), (6, '}\n')], 'skipped_checks': []}}, 'aws_route': {'public': {'start_line': 8, 'end_line': 12, 'code_lines': [(8, 'resource "aws_route" "public" {\n'), (9, ' route_table_id = aws_route_table.public.id\n'), (10, ' destination_cidr_block = "0.0.0.0/0"\n'), (11, ' gateway_id = aws_internet_gateway.gw.id\n'), (12, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_route_table_association.private.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_route_table_association': {'private': {'start_line': 1, 'end_line': 5, 'code_lines': [(1, 'resource "aws_route_table_association" "private" {\n'), (2, ' count = var.subnets\n'), (3, ' subnet_id = element(aws_subnet.private.*.id, count.index)\n'), (4, ' route_table_id = aws_route_table.private.id\n'), (5, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_route_table_association.public.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_route_table_association': {'public': {'start_line': 1, 'end_line': 5, 'code_lines': [(1, 'resource "aws_route_table_association" "public" {\n'), (2, ' count = var.subnets\n'), (3, ' subnet_id = element(aws_subnet.public.*.id, count.index)\n'), (4, ' route_table_id = aws_route_table.public.id\n'), (5, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_subnet.private.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_subnet': {'private': {'start_line': 1, 'end_line': 10, 'code_lines': [(1, 'resource "aws_subnet" "private" {\n'), (2, ' count = var.subnets\n'), (3, ' vpc_id = aws_vpc.main.id\n'), (4, ' cidr_block = local.private_cidrs[count.index]\n'), (5, ' availability_zone = data.aws_availability_zones.available.names[count.index]\n'), (6, '\n'), (7, ' tags = merge(var.common_tags,\n'), (8, ' tomap({ "Type" = "Private" }),\n'), (9, ' tomap({ "Name" = "${upper(var.account_name)}-Private-${var.zone[count.index]}" }))\n'), (10, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_subnet.public.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_subnet': {'public': {'start_line': 1, 'end_line': 10, 'code_lines': [(1, 'resource "aws_subnet" "public" {\n'), (2, ' count = var.subnets\n'), (3, ' vpc_id = aws_vpc.main.id\n'), (4, ' cidr_block = local.public_cidrs[count.index]\n'), (5, ' availability_zone = data.aws_availability_zones.available.names[count.index]\n'), (6, '\n'), (7, ' tags = merge(var.common_tags,\n'), (8, ' tomap({ "Type" = "Public" }),\n'), (9, ' tomap({ "Name" = "${upper(var.account_name)}-Public-${var.zone[count.index]}" }))\n'), (10, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_vpc.main.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'locals': {'start_line': 10, 'end_line': 12, 'code_lines': [(10, 'locals {\n'), (11, ' tags = merge(var.common_tags, tomap({ "Name" = upper(var.account_name) }))\n'), (12, '}\n')], 'assignments': {'tags': "merge([],tomap({'Name':'upper(test)'}))"}, 'skipped_checks': []}, 'resource': {'aws_vpc': {'main': {'start_line': 1, 'end_line': 7, 'code_lines': [(1, 'resource "aws_vpc" "main" {\n'), (2, ' cidr_block = var.cidr\n'), (3, ' enable_dns_support = true\n'), (4, ' enable_dns_hostnames = true\n'), (5, '\n'), (6, ' tags = local.tags\n'), (7, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/aws_vpn_gateway.vpn_gw.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'resource': {'aws_vpn_gateway': {'vpn_gw': {'start_line': 1, 'end_line': 6, 'code_lines': [(1, 'resource "aws_vpn_gateway" "vpn_gw" {\n'), (2, ' vpc_id = aws_vpc.main.id\n'), (3, '\n'), (4, ' tags = merge(var.common_tags,\n'), (5, ' tomap({ "Name" = "${upper(var.account_name)}-VGW" }))\n'), (6, '}\n')], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/data.aws_availability_zones.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'data': {'aws_availability_zones': {'available': {'start_line': 1, 'end_line': 0, 'code_lines': [], 'skipped_checks': []}}}}, '/mock/os/terraform-aws-vpc/variables.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]': {'locals': {'start_line': 27, 'end_line': 30, 'code_lines': [(27, 'locals {\n'), (28, ' public_cidrs = [cidrsubnet(var.cidr, 3, 0), cidrsubnet(var.cidr, 3, 1), cidrsubnet(var.cidr, 3, 2)]\n'), (29, ' private_cidrs = [cidrsubnet(var.cidr, 3, 3), cidrsubnet(var.cidr, 3, 4), cidrsubnet(var.cidr, 3, 5)]\n'), (30, '}\n')], 'skipped_checks': []}, 'variable': {'account_name': {'start_line': 1, 'end_line': 4, 'code_lines': [(1, 'variable "account_name" {\n'), (2, ' type = string\n'), (3, ' description = "The Name of the Account"\n'), (4, '}\n')], 'skipped_checks': []}, 'cidr': {'start_line': 6, 'end_line': 9, 'code_lines': [(6, 'variable "cidr" {\n'), (7, ' type = string\n'), (8, ' description = "The range to be associated with the VPC and cleaved into the subnets"\n'), (9, '}\n')], 'skipped_checks': []}, 'common_tags': {'start_line': 11, 'end_line': 14, 'code_lines': [(11, 'variable "common_tags" {\n'), (12, ' type = map(any)\n'), (13, ' description = "A tagging scheme"\n'), (14, '}\n')], 'skipped_checks': []}, 'zone': {'start_line': 16, 'end_line': 19, 'code_lines': [(16, 'variable "zone" {\n'), (17, ' type = list(any)\n'), (18, ' description = "Availability zone names"\n'), (19, '}\n')], 'skipped_checks': []}, 'subnets': {'start_line': 21, 'end_line': 25, 'code_lines': [(21, 'variable "subnets" {\n'), (22, ' type = number\n'), (23, ' default = 3\n'), (24, ' description = "The number of subnets required, less than or equal to the number of availability zones"\n'), (25, '}\n')], 'skipped_checks': []}, 'assignments': {'subnets': 3}}}}
+ entity_with_non_found_path = {'block_name_': 'aws_vpc.main', 'block_type_': 'resource', 'file_path_': '/mock/os/terraform-aws-vpc/aws_vpc.main.tf', 'config_': {'aws_vpc': {'main': {'cidr_block': ['10.0.0.0/21'], 'enable_dns_hostnames': [True], 'enable_dns_support': [True], 'tags': ["merge([],tomap({'Name':'upper(test)'}))"]}}}, 'label_': 'BlockType.RESOURCE: aws_vpc.main', 'id_': 'aws_vpc.main', 'source_': 'Terraform', 'cidr_block': '10.0.0.0/21', 'enable_dns_hostnames': True, 'enable_dns_support': True, 'tags': "merge([],tomap({'Name':'upper(test)'}))", 'resource_type': 'aws_vpc', 'rendering_breadcrumbs_': {'cidr_block': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf', 'module_connection': False}, {'type': 'variable', 'name': 'cidr', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'public_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'public_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'output', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/outputs.tf', 'module_connection': False}], 'source_module_': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf'}], 'tags': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf', 'module_connection': False}, {'type': 'variable', 'name': 'account_name', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'tags', 'path': '/mock/os/terraform-aws-vpc/aws_vpc.main.tf', 'module_connection': False}]}, 'hash': 'bac3bb7d21610be9ad786c1e9b5a2b3f6f13e60699fa935b32bb1f9f10a792e4'}
+ entity_context, entity_evaluations = runner.get_entity_context_and_evaluations(entity_with_non_found_path)
+
+ assert entity_context is not None
+ assert entity_context['start_line'] == 1 and entity_context['end_line']==7
+
+ entity_with_found_path = {'block_name_': 'aws_vpc.main', 'block_type_': 'resource', 'file_path_': '/mock/os/terraform-aws-vpc/aws_vpc.main.tf[/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf#0]', 'config_': {'aws_vpc': {'main': {'cidr_block': ['10.0.0.0/21'], 'enable_dns_hostnames': [True], 'enable_dns_support': [True], 'tags': ["merge([],tomap({'Name':'upper(test)'}))"]}}}, 'label_': 'BlockType.RESOURCE: aws_vpc.main', 'id_': 'aws_vpc.main', 'source_': 'Terraform', 'cidr_block': '10.0.0.0/21', 'enable_dns_hostnames': True, 'enable_dns_support': True, 'tags': "merge([],tomap({'Name':'upper(test)'}))", 'resource_type': 'aws_vpc', 'rendering_breadcrumbs_': {'cidr_block': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf', 'module_connection': False}, {'type': 'variable', 'name': 'cidr', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'public_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'public_cidrs', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'output', 'name': 'private_cidrs', 'path': '/mock/os/terraform-aws-vpc/outputs.tf', 'module_connection': False}], 'source_module_': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf'}], 'tags': [{'type': 'module', 'name': 'vpc', 'path': '/mock/os/terraform-aws-vpc/example/examplea/module.vpc.tf', 'module_connection': False}, {'type': 'variable', 'name': 'account_name', 'path': '/mock/os/terraform-aws-vpc/variables.tf', 'module_connection': False}, {'type': 'locals', 'name': 'tags', 'path': '/mock/os/terraform-aws-vpc/aws_vpc.main.tf', 'module_connection': False}]}, 'hash': 'bac3bb7d21610be9ad786c1e9b5a2b3f6f13e60699fa935b32bb1f9f10a792e4'}
+ entity_context, entity_evaluations = runner.get_entity_context_and_evaluations(entity_with_found_path)
+
+ assert entity_context is not None
+ assert entity_context['start_line'] == 1 and entity_context['end_line']==7
+
+ def test_resource_values_dont_exist(self):
+ resources_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "resources", "resource_value_without_var")
+ checks_allow_list = ['CKV_AWS_21']
+ skip_checks = ['CUSTOM_AWS_1']
+ source_files = ["main.tf", "variables.tf"]
+
+ runner = Runner()
+ report = runner.run(root_folder=None, external_checks_dir=None,
+ files=list(map(lambda f: f'{resources_path}/{f}', source_files)),
+ runner_filter=RunnerFilter(framework='terraform',
+ checks=checks_allow_list, skip_checks=skip_checks))
+
+ self.assertEqual(len(report.passed_checks), 1)
+ self.assertEqual(len(report.failed_checks), 1)
+
+ def test_resource_values_do_exist(self):
+ resources_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "resources", "resource_value_without_var")
+ checks_allow_list = ['CKV_AWS_21']
+ skip_checks = ['CUSTOM_AWS_1']
+ source_files = ["main.tf", "variables.tf", "variables_unscoped.tf"]
+
+ runner = Runner()
+ report = runner.run(root_folder=None, external_checks_dir=None,
+ files=list(map(lambda f: f'{resources_path}/{f}', source_files)),
+ runner_filter=RunnerFilter(framework='terraform',
+ checks=checks_allow_list, skip_checks=skip_checks))
+
+ self.assertEqual(len(report.passed_checks), 3)
+ self.assertEqual(len(report.failed_checks), 3)
+
+ def test_resource_negative_values_dont_exist(self):
+ resources_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "resources", "resource_negative_value_without_var")
+ checks_allow_list = ['CKV_AWS_57']
+ skip_checks = ['CUSTOM_AWS_1']
+ source_files = ["main.tf", "variables.tf"]
+
+ runner = Runner()
+ report = runner.run(root_folder=None, external_checks_dir=None,
+ files=list(map(lambda f: f'{resources_path}/{f}', source_files)),
+ runner_filter=RunnerFilter(framework='terraform',
+ checks=checks_allow_list, skip_checks=skip_checks))
+
+ self.assertEqual(len(report.passed_checks), 1)
+ self.assertEqual(len(report.failed_checks), 1)
+
+ def test_resource_negative_values_do_exist(self):
+ resources_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "resources", "resource_negative_value_without_var")
+ checks_allow_list = ['CKV_AWS_57']
+ skip_checks = ['CUSTOM_AWS_1']
+ source_files = ["main.tf", "variables.tf", "variables_unscoped.tf"]
+
+ runner = Runner()
+ report = runner.run(root_folder=None, external_checks_dir=None,
+ files=list(map(lambda f: f'{resources_path}/{f}', source_files)),
+ runner_filter=RunnerFilter(framework='terraform',
+ checks=checks_allow_list, skip_checks=skip_checks))
+
+ self.assertEqual(len(report.passed_checks), 3)
+ self.assertEqual(len(report.failed_checks), 3)
+
+ def test_no_duplicate_results(self):
+ resources_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "resources", "duplicate_violations")
+ runner = Runner()
+ report = runner.run(root_folder=resources_path, external_checks_dir=None,
+ runner_filter=RunnerFilter(framework='terraform'))
+
+ unique_checks = []
+ for record in report.passed_checks:
+ check_unique = f"{record.check_id}.{record.resource}"
+ if check_unique in unique_checks:
+ self.fail(f"found duplicate results in report: {record.to_string()}")
+ unique_checks.append(check_unique)
def tearDown(self):
- parser_registry.definitions_context = {}
+ parser_registry.context = {}
if __name__ == '__main__':
diff --git a/tests/terraform/test_parser_internals.py b/tests/terraform/test_parser_internals.py
deleted file mode 100644
index abc52b1f5c..0000000000
--- a/tests/terraform/test_parser_internals.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import unittest
-
-from typing import List, Optional, Tuple
-
-from checkov.terraform import parser
-from checkov.terraform.parser import VarBlockMatch as VBM
-
-
-class TestParserInternals(unittest.TestCase):
- def test_eval_string_to_list(self):
- expected = ["a", "b", "c"]
- assert parser._eval_string('["a", "b", "c"]') == expected
-
- def test_split_merge_args(self):
- cases: List[Tuple[str, List[str]]] = [
- ("local.one, local.two",
- ["local.one", "local.two"]),
- ("{Tag4 = \"four\"}, {Tag5 = \"five\"}",
- ["{Tag4 = \"four\"}", "{Tag5 = \"five\"}"]),
- ("{a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3}",
- ["{a=\"b\"}", "{a=[1,2], c=\"z\"}", "{d=3}"]),
- ("local.common_tags, merge({Tag4 = \"four\"}, {Tag5 = \"five\"})",
- ["local.common_tags", "merge({Tag4 = \"four\"}, {Tag5 = \"five\"})"]),
- (", ",
- None),
- ("",
- None),
- (", leading_comma",
- ["leading_comma"]),
- ("kinda_maybe_shouldnt_work_but_we_will_roll_with_it, ", # <-- trailing comma
- ["kinda_maybe_shouldnt_work_but_we_will_roll_with_it"]),
- ("local.one",
- ["local.one"]),
- ('{"a": "}, evil"}', # bracket inside string, should not be split
- ['{"a": "}, evil"}']),
- ("{'a': '}, evil'}", # bracket inside string, should not be split
- ["{'a': '}, evil'}"]), # Note: these happen with native maps (see merge tests)
- ('${merge({\'a\': \'}, evil\'})}',
- ['${merge({\'a\': \'}, evil\'})}']),
- ('local.common_tags,,{\'Tag4\': \'four\'},,{\'Tag2\': \'Dev\'},',
- ["local.common_tags", "{\'Tag4\': \'four\'}", "{\'Tag2\': \'Dev\'}"])
- ]
- for case in cases:
- actual = parser._split_merge_args(case[0])
- assert actual == case[1], f"Case \"{case[0]}\" failed. Expected: {case[1]} Actual: {actual}"
-
- def test_find_var_blocks(self):
- cases: List[Tuple[str, List[parser.VarBlockMatch]]] = [
- ("${local.one}",
- [VBM("${local.one}", "local.one")]),
- ("${merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})}",
- [VBM("${merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})}",
- "merge({a=\"b\"}, {a=[1,2], c=\"z\"}, {d=3})")]),
- ("\"string$ ${tomap({key=\"value\"})[key]} are fun\"",
- [VBM("${tomap({key=\"value\"})[key]}", "tomap({key=\"value\"})[key]")]),
- # This case highlights that inner evals should be returned
- ("${filemd5(\"${path.module}/templates/some-file.json\")}",
- [VBM("${path.module}", "path.module")]),
- ("${local.NAME[foo]}-${local.TAIL}${var.gratuitous_var_default}",
- [
- VBM("${local.NAME[foo]}", "local.NAME[foo]"),
- VBM("${local.TAIL}", "local.TAIL"),
- VBM("${var.gratuitous_var_default}", "var.gratuitous_var_default")
- ]),
- ("${tostring(\"annoying {\")}",
- [VBM("${tostring(\"annoying {\")}", "tostring(\"annoying {\")")]),
- ("${this-is-unterminated", []),
- ("${merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})}",
- [VBM("${merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})}",
- "merge({\"a\": \"}, evil\"},{\"b\": \"\\\" , evil\"})")]),
- ("$${foo}", []), # escape interpolation
- ('${merge({\'a\': \'}, evil\'})}',
- [VBM('${merge({\'a\': \'}, evil\'})}', 'merge({\'a\': \'}, evil\'})')])
- ]
- for case in cases:
- actual = parser._find_var_blocks(case[0])
- assert actual == case[1], f"Case \"{case[0]}\" failed. Expected: {case[1]} Actual: {actual}"
diff --git a/tests/terraform/test_scanner_registry.py b/tests/terraform/test_scanner_registry.py
index aef46b75d8..3899389fe6 100644
--- a/tests/terraform/test_scanner_registry.py
+++ b/tests/terraform/test_scanner_registry.py
@@ -1,7 +1,9 @@
import unittest
from checkov.terraform.checks.resource.registry import resource_registry as registry
-
+from checkov.common.checks_infra.checks_parser import NXGraphCheckParser
+from checkov.common.checks_infra.registry import Registry
+from pathlib import Path
class TestScannerRegistry(unittest.TestCase):
@@ -21,6 +23,16 @@ def test_non_colliding_check_ids(self):
for check_id, check_classes in check_id_check_class_map.items():
self.assertEqual(len(set(check_classes)), 1,"collision on check_id={}".format(check_id))
+ def test_non_colliding_graph_check_ids(self):
+ check_id_check_class_map = {}
+ graph_registry = Registry(parser=NXGraphCheckParser(), checks_dir=str(Path(__file__).parent.parent.parent / "checkov" / "terraform" / "checks" / "graph_checks"))
+ graph_registry.load_checks()
+ for check in graph_registry.checks:
+ check_id_check_class_map.setdefault(check.id, []).append(check)
+
+ for check_id, check_classes in check_id_check_class_map.items():
+ self.assertEqual(len(set(check_classes)), 1,"collision on check_id={}".format(check_id))
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/terraform/util/test_iam_converter.py b/tests/terraform/util/test_iam_converter.py
new file mode 100644
index 0000000000..1da2369ef5
--- /dev/null
+++ b/tests/terraform/util/test_iam_converter.py
@@ -0,0 +1,19 @@
+import unittest
+
+from checkov.terraform.checks.utils.iam_terraform_document_to_policy_converter import \
+ convert_terraform_conf_to_iam_policy
+
+
+class TestIAMConverter(unittest.TestCase):
+
+ def test_iam_converter(self):
+ conf = {'version': ['2012-10-17'], 'statement': [{'actions': [['*']], 'resources': [['*']]}]}
+ expected_result = {'version': ['2012-10-17'], 'Statement': [{'Action': ['*'], 'Resource': ['*'], 'Effect': 'Allow'}]}
+ result = convert_terraform_conf_to_iam_policy(conf)
+ self.assertDictEqual(result,expected_result)
+ self.assertNotEqual(result,conf)
+
+
+
+if __name__ == '__main__':
+ unittest.main()