├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── rule.md ├── PULL_REQUEST_TEMPLATE │ └── rule.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── publish.yml │ ├── release-drafter.yml │ ├── tests.yml │ └── update-docs.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── cfn-lint-serverless ├── Makefile ├── README.md ├── cfn_lint_serverless │ ├── __init__.py │ ├── rules │ │ ├── __init__.py │ │ ├── api_gateway.py │ │ ├── appsync.py │ │ ├── eventbridge.py │ │ ├── lambda_.py │ │ ├── sns.py │ │ ├── sqs.py │ │ └── step_functions.py │ └── utils.py ├── pyproject.toml ├── pytest.ini ├── tests │ ├── __init__.py │ ├── templates │ │ ├── es1001-missing.fail.yaml │ │ ├── es1001.pass.yaml │ │ ├── es1005.fail.yaml │ │ ├── es1005.pass.yaml │ │ ├── es1006.fail.yaml │ │ ├── es1006.pass.yaml │ │ ├── es1007.fail.yaml │ │ ├── es1007.pass.yaml │ │ ├── es2000-http-empty.fail.yaml │ │ ├── es2000-http-missing.fail.yaml │ │ ├── es2000-rest-empty.fail.yaml │ │ ├── es2000-rest-missing.fail.yaml │ │ ├── es2003-http-missing.fail.yaml │ │ ├── es2003-http-nothrottle.fail.yaml │ │ ├── es2003-http.pass.yaml │ │ ├── es2003-rest-missing.fail.yaml │ │ ├── es2003-rest-nodefault.fail.yaml │ │ ├── es2003-rest-nothrottle.fail.yaml │ │ ├── es2003-rest.pass.yaml │ │ ├── es4000-missing.fail.yaml │ │ ├── es4000-partial.fail.yaml │ │ ├── es4000.pass.yaml │ │ ├── es6000-multiple.pass.yaml │ │ ├── es6000.fail.yaml │ │ ├── es6000.pass.yaml │ │ ├── es7000.fail.yaml │ │ ├── es7000.pass.yaml │ │ ├── ws1000-missing.fail.yaml │ │ ├── ws1000-passthrough.fail.yaml │ │ ├── ws1000.pass.yaml │ │ ├── ws1002-permissions.fail.yaml │ │ ├── ws1002-sam.fail.yaml │ │ ├── ws1002-sub-principal.pass.yaml │ │ ├── ws1002.pass.yaml │ │ ├── ws1003-fullstar.fail.yaml │ │ ├── ws1003-substar.pass.yaml │ │ ├── ws1003.fail.yaml │ │ ├── ws1003.pass.yaml │ │ ├── ws1004-hardcoded.pass.yaml │ │ ├── ws1004-join.pass.yaml │ │ ├── ws1004-missing.fail.yaml │ │ ├── ws1004-noretention.fail.yaml │ │ ├── ws1004-sub-vars-hardcoded.pass.yaml │ │ ├── ws1004-sub-vars.pass.yaml │ │ ├── ws1004-sub.pass.yaml │ │ ├── ws2001-http-json.pass.yaml │ │ ├── ws2001-http-nojson.fail.yaml │ │ ├── ws2001-rest-json.pass.yaml │ │ ├── ws2001-rest-nojson.fail.yaml │ │ ├── ws2002-http-false.pass.yaml │ │ ├── ws2002-http-missing.pass.yaml │ │ ├── ws2002-rest-false.fail.yaml │ │ ├── ws2002-rest-missing.fail.yaml │ │ ├── ws2002-rest.pass.yaml │ │ ├── ws3000-false.fail.yaml │ │ ├── ws3000-missing.fail.yaml │ │ ├── ws3000.pass.yaml │ │ ├── ws5000-false.fail.yaml │ │ ├── ws5000-missing.fail.yaml │ │ └── ws5000.pass.yaml │ ├── test_templates.py │ └── test_utils.py └── uv.lock ├── docs ├── cfn-lint.md ├── contributing │ └── create_rule.md ├── images │ ├── aws-logo-light.svg │ └── cfn_lint_vscode.png ├── index.md ├── rules │ ├── api_gateway │ │ ├── default_throttling.md │ │ ├── logging.md │ │ ├── structured_logging.md │ │ └── tracing.md │ ├── appsync │ │ └── tracing.md │ ├── eventbridge │ │ └── rule_without_dlq.md │ ├── index.md │ ├── lambda │ │ ├── async_failure_destination.md │ │ ├── default_memory_size.md │ │ ├── default_timeout.md │ │ ├── end_of_life_runtime.md │ │ ├── eventsourcemapping_failure_destination.md │ │ ├── log_retention.md │ │ ├── permission_multiple_principals.md │ │ ├── star_permissions.md │ │ └── tracing.md │ ├── sns │ │ └── redrive_policy.md │ ├── sqs │ │ └── redrive_policy.md │ └── step_functions │ │ └── tracing.md ├── stylesheets │ └── extra.css └── tflint.md ├── examples ├── Makefile ├── cdk │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── app.py │ ├── cdk.json │ ├── cdk │ │ ├── __init__.py │ │ └── cdk_stack.py │ ├── requirements.txt │ ├── setup.py │ ├── source.bat │ └── src │ │ └── hello │ │ └── main.py ├── sam │ ├── Makefile │ └── template.yaml ├── serverless-framework │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── handler.py │ └── serverless.yml └── tflint │ ├── .gitignore │ ├── .tflint.hcl │ ├── Makefile │ └── main.tf ├── mkdocs.yml ├── requirements-dev.txt ├── ruff.toml └── tflint-ruleset-aws-serverless ├── .goreleaser.yml ├── Makefile ├── go.mod ├── go.sum ├── main.go ├── rules ├── aws_api_gateway_method_settings_throttling.go ├── aws_api_gateway_method_settings_throttling_test.go ├── aws_api_gateway_stage_logging.go ├── aws_api_gateway_stage_logging_test.go ├── aws_api_gateway_stage_structured_logging.go ├── aws_api_gateway_stage_structured_logging_test.go ├── aws_api_gateway_stage_tracing.go ├── aws_api_gateway_stage_tracing_test.go ├── aws_apigatewayv2_stage_logging.go ├── aws_apigatewayv2_stage_logging_test.go ├── aws_apigatewayv2_stage_structured_logging.go ├── aws_apigatewayv2_stage_structured_logging_test.go ├── aws_apigatewayv2_stage_throttling.go ├── aws_apigatewayv2_stage_throttling_test.go ├── aws_appsync_graphql_api_tracing.go ├── aws_appsync_graphql_api_tracing_test.go ├── aws_cloudwatch_event_target_no_dlq.go ├── aws_cloudwatch_event_target_no_dlq_test.go ├── aws_cloudwatch_log_group_lambda_retention.go ├── aws_cloudwatch_log_group_lambda_retention_test.go ├── aws_iam_role_lambda_no_star.go ├── aws_iam_role_lambda_no_star_test.go ├── aws_lambda_event_invoke_config_async_on_failure.go ├── aws_lambda_event_invoke_config_async_on_failure_test.go ├── aws_lambda_event_source_mapping_failure_destination.go ├── aws_lambda_event_source_mapping_failure_destination_test.go ├── aws_lambda_function_default_memory.go ├── aws_lambda_function_default_memory_test.go ├── aws_lambda_function_default_timeout.go ├── aws_lambda_function_default_timeout_test.go ├── aws_lambda_function_eol_runtime.go ├── aws_lambda_function_eol_runtime_test.go ├── aws_lambda_function_tracing.go ├── aws_lambda_function_tracing_test.go ├── aws_lambda_permission_multiple_principals.go ├── aws_lambda_permission_multiple_principals_test.go ├── aws_sfn_state_machine_tracing.go ├── aws_sfn_state_machine_tracing_test.go ├── aws_sns_topic_subscription_redrive_policy.go ├── aws_sns_topic_subscription_redrive_policy_test.go ├── aws_sqs_queue_redrive_policy.go ├── aws_sqs_queue_redrive_policy_test.go └── provider.go ├── scratch_file.tf └── templates ├── rule.go.tmpl └── rule_test.go.tmpl /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug, triage 6 | assignees: "" 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **What were you trying to accomplish?** 14 | 15 | ## Expected Behavior 16 | 17 | 18 | 19 | ## Current Behavior 20 | 21 | 22 | 23 | ## Possible Solution 24 | 25 | 26 | 27 | ## Steps to Reproduce (for bugs) 28 | 29 | 30 | 1. 31 | 2. 32 | 3. 33 | 4. 34 | 35 | ## Environment 36 | 37 | * **Infrastructure as code technology used**: 38 | * **(for `cfn-lint`) Python, cfn-lint, and cfn-lint-serverless versions**: 39 | * **(for `tflint`) Go, tflint versions**: 40 | * **Debugging logs** 41 | 42 | ``` 43 | # paste logs here 44 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request, triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/rule.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Rule 3 | about: Suggest a new rule 4 | title: "rule: " 5 | labels: new-rule, triage 6 | assignees: "" 7 | --- 8 | 9 | ## Key information 10 | 11 | * Rule PR: (leave this empty) 12 | * Related issue(s), if known: 13 | * Meets the need of 80% of users: (yes/no) 14 | * Do you need help implementing this rule: (yes/no) 15 | * `cfn-lint` rule ID: 16 | * `tflint` rule name: 17 | * Rule level: _(Error/Warning/Info)_ 18 | * Approved by: 19 | * Reviewed by: 20 | 21 | ## Summary 22 | 23 | > One paragraph explaining the proposed rule. 24 | 25 | ## Rule level 26 | 27 | > Explaination for the desired level for this rule. See [the documentation on rules](https://awslabs.github.io/serverless-rules/rules/) for more information on the rule levels. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/rule.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Rule 3 | about: Pull Request template for new rules 4 | title: "rule: " 5 | labels: new-rule 6 | assignees: "" 7 | --- 8 | 9 | ## Key information 10 | 11 | * Rule issue: _(add link to issue)_ 12 | * `cfn-lint` rule ID: 13 | * `tflint` rule name: 14 | 15 | ## Summary 16 | 17 | > One paragraph explaining the proposed rule. 18 | 19 | ## Checklist 20 | 21 | * [ ] __cfn-lint__: add rule 22 | * [ ] __cfn-lint__: add import in `rules/__init__.py` 23 | * [ ] __cfn-lint__: add test templates 24 | * [ ] __tflint__: add rule 25 | * [ ] __tflint__: add tests 26 | * [ ] __tflint__: add in `rules/provider.go` 27 | * [ ] __docs__: add new section in documentation 28 | * [ ] __docs__: add reference in `rules/index.md` -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | commit-message: 8 | prefix: chore 9 | include: scope 10 | labels: 11 | - chore 12 | 13 | - package-ecosystem: pip 14 | directory: /cfn-lint-serverless 15 | schedule: 16 | interval: daily 17 | commit-message: 18 | prefix: chore 19 | include: scope 20 | labels: 21 | - chore 22 | 23 | - package-ecosystem: gomod 24 | directory: /tflint-ruleset-aws-serverless 25 | schedule: 26 | interval: daily 27 | commit-message: 28 | prefix: chore 29 | include: scope 30 | labels: 31 | - chore -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | change-template: '* $TITLE (#$NUMBER) by @$AUTHOR' 2 | categories: 3 | - title: '📐 New Rules' 4 | labels: 5 | - new-rule 6 | - title: '🚀 Enhancements' 7 | labels: 8 | - feature 9 | - enhancement 10 | - title: '📚 Documentation Updates' 11 | labels: 12 | - documentation 13 | - title: '🐛 Bug Fixes' 14 | labels: 15 | - fix 16 | - bugfix 17 | - bug 18 | - title: '🧰 Maintenance' 19 | label: 20 | - chore 21 | 22 | exclude-labels: 23 | - skip-changelog 24 | 25 | template: | 26 | ## Summary 27 | 28 | _Human readable summary of changes_ 29 | 30 | ## Changes 31 | 32 | $CHANGES 33 | 34 | ## This release was made possible by the following contributors: 35 | 36 | $CONTRIBUTORS -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | # Add concurrency to prevent multiple simultaneous runs 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | run_tests: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set release notes tag 22 | run: | 23 | export RELEASE_TAG_VERSION=${{ github.event.release.tag_name }} 24 | echo "RELEASE_TAG_VERSION=${RELEASE_TAG_VERSION:1}" >> $GITHUB_ENV 25 | - name: Install uv 26 | uses: astral-sh/setup-uv@v5 27 | - name: Check version tags 28 | run: make release-check 29 | - name: Set up Python 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: "3.13" 33 | - name: Set up Go 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: "1.23" 37 | - name: Setup TFLint 38 | uses: terraform-linters/setup-tflint@v4 39 | with: 40 | tflint_version: v0.50.0 41 | - name: Install dependencies 42 | run: make dev 43 | - name: Run all checks 44 | run: make pr 45 | 46 | publish_pypi: 47 | needs: run_tests 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Set release notes tag 52 | run: | 53 | export RELEASE_TAG_VERSION=${{ github.event.release.tag_name }} 54 | echo "RELEASE_TAG_VERSION=${RELEASE_TAG_VERSION:1}" >> $GITHUB_ENV 55 | - name: Install uv 56 | uses: astral-sh/setup-uv@v5 57 | - name: Set up Python 58 | uses: actions/setup-python@v5 59 | with: 60 | python-version: "3.13" 61 | - name: Install dependencies 62 | run: make -C cfn-lint-serverless dev 63 | - name: Upload to PyPi 64 | run: | 65 | cd cfn-lint-serverless/ 66 | uv build 67 | uv publish --token ${{ secrets.PYPI_TOKEN25 }} 68 | publish_go: 69 | needs: run_tests 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v4 73 | - name: Set up Go 74 | uses: actions/setup-go@v5 75 | with: 76 | go-version: "1.23" 77 | - name: Run GoReleaser 78 | uses: goreleaser/goreleaser-action@v6 79 | with: 80 | distribution: goreleaser 81 | version: "~> v2" 82 | args: release --clean 83 | workdir: tflint-ruleset-aws-serverless/ 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: release-drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v6 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | 9 | # Add concurrency to prevent parallel runs on same ref 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | cfn-lint-tests: 16 | strategy: 17 | fail-fast: false 18 | max-parallel: 4 19 | matrix: 20 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 21 | os: [ubuntu-latest] 22 | runs-on: ${{ matrix.os }} 23 | env: 24 | FOLDER: cfn-lint-serverless 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | cache: 'pip' 31 | - name: Install uv 32 | uses: astral-sh/setup-uv@v5 33 | - name: Install ruff 34 | run: pip install ruff 35 | - name: Install dependencies 36 | run: make -C $FOLDER dev 37 | - name: Formatting and Linting 38 | run: make -C $FOLDER lint 39 | - name: Tests 40 | run: make -C $FOLDER test 41 | - name: Security baseline 42 | run: make -C $FOLDER security-baseline 43 | - name: Complexity baseline 44 | run: make -C $FOLDER complexity-baseline 45 | 46 | tflint-tests: 47 | strategy: 48 | fail-fast: false 49 | max-parallel: 4 50 | matrix: 51 | go-version: ['1.21', '1.22', '1.23'] 52 | os: [ubuntu-latest] 53 | runs-on: ${{ matrix.os }} 54 | env: 55 | FOLDER: tflint-ruleset-aws-serverless 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: actions/setup-go@v5 59 | with: 60 | go-version: ${{ matrix.go-version }} 61 | cache: true 62 | - name: Install dependencies 63 | run: make -C $FOLDER dev 64 | - name: Formatting and Linting 65 | run: make -C $FOLDER lint 66 | - name: Tests 67 | run: make -C $FOLDER test 68 | 69 | -------------------------------------------------------------------------------- /.github/workflows/update-docs.yml: -------------------------------------------------------------------------------- 1 | name: update-docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Deploy docs 14 | uses: mhausenblas/mkdocs-deploy-gh-pages@master 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python,go 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,go 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### Python ### 27 | # Byte-compiled / optimized / DLL files 28 | __pycache__/ 29 | *.py[cod] 30 | *$py.class 31 | 32 | # C extensions 33 | 34 | # Distribution / packaging 35 | .Python 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | wheels/ 48 | pip-wheel-metadata/ 49 | share/python-wheels/ 50 | *.egg-info/ 51 | .installed.cfg 52 | *.egg 53 | MANIFEST 54 | 55 | # PyInstaller 56 | # Usually these files are written by a python script from a template 57 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 58 | *.manifest 59 | *.spec 60 | 61 | # Installer logs 62 | pip-log.txt 63 | pip-delete-this-directory.txt 64 | 65 | # Unit test / coverage reports 66 | htmlcov/ 67 | .tox/ 68 | .nox/ 69 | .coverage 70 | .coverage.* 71 | .cache 72 | nosetests.xml 73 | coverage.xml 74 | *.cover 75 | *.py,cover 76 | .hypothesis/ 77 | .pytest_cache/ 78 | pytestdebug.log 79 | 80 | # Translations 81 | *.mo 82 | *.pot 83 | 84 | # Django stuff: 85 | *.log 86 | local_settings.py 87 | db.sqlite3 88 | db.sqlite3-journal 89 | 90 | # Flask stuff: 91 | instance/ 92 | .webassets-cache 93 | 94 | # Scrapy stuff: 95 | .scrapy 96 | 97 | # Sphinx documentation 98 | docs/_build/ 99 | doc/_build/ 100 | 101 | # PyBuilder 102 | target/ 103 | 104 | # Jupyter Notebook 105 | .ipynb_checkpoints 106 | 107 | # IPython 108 | profile_default/ 109 | ipython_config.py 110 | 111 | # pyenv 112 | .python-version 113 | 114 | # pipenv 115 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 116 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 117 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 118 | # install all needed dependencies. 119 | #Pipfile.lock 120 | 121 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 122 | __pypackages__/ 123 | 124 | # Celery stuff 125 | celerybeat-schedule 126 | celerybeat.pid 127 | 128 | # SageMath parsed files 129 | *.sage.py 130 | 131 | # Environments 132 | .env 133 | .venv 134 | env/ 135 | venv/ 136 | ENV/ 137 | env.bak/ 138 | venv.bak/ 139 | pythonenv* 140 | 141 | # Spyder project settings 142 | .spyderproject 143 | .spyproject 144 | 145 | # Rope project settings 146 | .ropeproject 147 | 148 | # mkdocs documentation 149 | /site 150 | 151 | # mypy 152 | .mypy_cache/ 153 | .dmypy.json 154 | dmypy.json 155 | 156 | # Pyre type checker 157 | .pyre/ 158 | 159 | # pytype static type analyzer 160 | .pytype/ 161 | 162 | # profiling data 163 | .prof 164 | 165 | # End of https://www.toptal.com/developers/gitignore/api/python,go 166 | 167 | 168 | 169 | **/.idea/** 170 | .vscode/settings.json 171 | tflint-ruleset-aws-serverless/tflint-ruleset-aws-serverless 172 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFN_LINT = cfn-lint-serverless 2 | TFLINT = tflint-ruleset-aws-serverless 3 | EXAMPLES = examples 4 | 5 | dev: dev-requirements 6 | $(MAKE) -C $(CFN_LINT) dev 7 | $(MAKE) -C $(TFLINT) dev 8 | 9 | dev-requirements: 10 | pip install -r requirements-dev.txt 11 | 12 | pr: 13 | $(MAKE) -C $(CFN_LINT) pr 14 | $(MAKE) -C $(TFLINT) pr 15 | 16 | test: 17 | $(MAKE) -C $(CFN_LINT) test 18 | $(MAKE) -C $(TFLINT) test 19 | 20 | docs-serve: 21 | mkdocs serve 22 | 23 | release-check: 24 | grep "version = \"$$RELEASE_TAG_VERSION\"" cfn-lint-serverless/pyproject.toml 25 | grep "Version: \"$$RELEASE_TAG_VERSION\"" tflint-ruleset-aws-serverless/main.go 26 | grep "version = \"$$RELEASE_TAG_VERSION\"" README.md 27 | grep "version = \"$$RELEASE_TAG_VERSION\"" docs/tflint.md 28 | grep "version = \"$$RELEASE_TAG_VERSION\"" examples/tflint/.tflint.hcl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Serverless Rules 2 | ================ 3 | 4 | The __Serverless Rules__ are a compilation of rules to validate infrastructure as code template against recommended practices. This currently provides a module for [cfn-lint](https://github.com/aws-cloudformation/cfn-python-lint) and a plugin for [tflint](https://github.com/terraform-linters/tflint). 5 | 6 | You can use those rules to get quick feedback on recommended practices while building a serverless application, as part of automated code review process, or as guardrails before deploying to production. 7 | 8 |

9 | 10 | **[📜Documentation](https://awslabs.github.io/serverless-rules/)** | **[🐍PyPi](https://pypi.org/project/cfn-lint-serverless/)** 11 | 12 |

13 | 14 | __PUBLIC PREVIEW__: this project is currently in __public preview__ to get feedback from the serverless community. APIs, tools, and rules might change between the beginning of public preview and version 1. 15 | 16 | You can find a list of currently supported rules [in the documentation](https://awslabs.github.io/serverless-rules/rules/). 17 | 18 | ## Usage guide 19 | ### cfn-lint 20 | 21 | To get started with Serverless Rules and [cfn-lint](https://github.com/aws-cloudformation/cfn-lint), install `cfn-lint-serverless` module: `pip install cfn-lint cfn-lint-serverless` 22 | 23 | You can now instruct `cfn-lint` to use Serverless Rules module installed previously via `--append-rules` or `-a` for short: 24 | 25 | ```bash 26 | cfn-lint my_template.yaml -a cfn_lint_serverless.rules 27 | ``` 28 | 29 | You can try with a Serverless Application Model (SAM) example provided in this repository by running: 30 | 31 | ```bash 32 | cfn-lint examples/sam/template.yaml -a cfn_lint_serverless.rules 33 | ``` 34 | 35 | ### tflint 36 | 37 | This plugin depends on [tflint](https://github.com/terraform-linters/tflint#installation). If you use `tflint` version 0.29 or newer, you can leverage the `tflint --init` command to automatically install the plugin. Otherwise, you will need to download the `tflint-ruleset-aws-serverless` binary corresponding to your system from the [releases page](https://github.com/awslabs/serverless-rules/releases). 38 | 39 | You can enable the Serverless Rules plugin by adding a plugin section in the `.tflint.hcl` file in your project: 40 | 41 | ```terraform 42 | plugin "aws-serverless" { 43 | enabled = true 44 | version = "0.3.3" 45 | source = "github.com/awslabs/serverless-rules" 46 | } 47 | ``` 48 | 49 | ## Contributing 50 | 51 | See [CONTRIBUTING](CONTRIBUTING.md) to learn how to contribute to this project. 52 | 53 | ## Security 54 | 55 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 56 | 57 | ## License 58 | 59 | This library is licensed under the MIT-0 License. See the [LICENSE](./LICENSE) file. 60 | -------------------------------------------------------------------------------- /cfn-lint-serverless/Makefile: -------------------------------------------------------------------------------- 1 | # Development environment setup 2 | dev: 3 | uv sync --all-groups 4 | 5 | format: 6 | ruff format 7 | 8 | lint: format 9 | ruff check 10 | 11 | test: 12 | PYTHONPATH=".:$$PYTHONPATH" uv run pytest -vvv --cov=./ --cov-report=xml 13 | 14 | test-template: 15 | PYTHONPATH=".:$$PYTHONPATH" cfn-lint $(TEMPLATE) -a cfn_lint_serverless.rules 16 | 17 | security-baseline: 18 | uv run bandit -f json -r cfn_lint_serverless -o bandit.baseline || true 19 | 20 | complexity-baseline: 21 | $(info Maintenability index) 22 | uv run radon mi cfn_lint_serverless 23 | $(info Cyclomatic complexity index) 24 | uv run xenon --max-absolute C --max-modules B --max-average A cfn_lint_serverless 25 | 26 | pr: test security-baseline complexity-baseline 27 | 28 | release: 29 | uv build 30 | uv publish --token ${PYPI_TOKEN25} 31 | -------------------------------------------------------------------------------- /cfn-lint-serverless/README.md: -------------------------------------------------------------------------------- 1 | # cfn-lint-serverless 2 | 3 | Ruleset for [cfn-lint](https://github.com/aws-cloudformation/cfn-lint) to validate CloudFormation templates for serverless applications against recommended best practices. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pip install cfn-lint cfn-lint-serverless 9 | ``` 10 | 11 | ## Usage 12 | 13 | Run cfn-lint with the serverless rules module: 14 | 15 | ```bash 16 | cfn-lint template.yaml -a cfn_lint_serverless.rules 17 | ``` 18 | 19 | ## Supported Rules 20 | 21 | This module provides validation rules for various AWS serverless resources: 22 | 23 | - Lambda Functions 24 | - API Gateway 25 | - Step Functions 26 | - SQS 27 | - SNS 28 | - EventBridge 29 | - AppSync 30 | 31 | For a detailed list of rules, refer to the [documentation](https://awslabs.github.io/serverless-rules/rules/). 32 | 33 | ## Examples 34 | 35 | Try it with the examples provided in the repository: 36 | 37 | ```bash 38 | # For SAM templates 39 | cfn-lint examples/sam/template.yaml -a cfn_lint_serverless.rules 40 | 41 | # For Serverless Framework templates 42 | cfn-lint examples/serverless-framework/template.yaml -a cfn_lint_serverless.rules 43 | ``` 44 | 45 | ## Contributing 46 | 47 | Contributions are welcome! Please see the [CONTRIBUTING.md](../CONTRIBUTING.md) file for guidelines. 48 | 49 | ## License 50 | 51 | This project is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file for details. 52 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CloudFormation Lint Rules for Serverless applications 3 | """ 4 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/__init__.py: -------------------------------------------------------------------------------- 1 | from .api_gateway import ( 2 | ApiGatewayDefaultThrottlingRule, 3 | ApiGatewayLoggingRule, 4 | ApiGatewayStructuredLoggingRule, 5 | ApiGatewayTracingRule, 6 | ) 7 | from .appsync import AppSyncTracingRule 8 | from .eventbridge import EventBridgeDLQRule 9 | from .lambda_ import ( 10 | LambdaAsyncNoDestinationRule, 11 | LambdaDefaultMemorySizeRule, 12 | LambdaDefaultTimeoutRule, 13 | LambdaESMDestinationRule, 14 | LambdaLogRetentionRule, 15 | LambdaPermissionPrincipalsRule, 16 | LambdaStarPermissionRule, 17 | LambdaTracingRule, 18 | ) 19 | from .sns import SnsNoRedrivePolicyRule 20 | from .sqs import SqsNoRedrivePolicyRule 21 | from .step_functions import StepFunctionsTracingRule 22 | 23 | __all__ = [ 24 | "ApiGatewayLoggingRule", 25 | "ApiGatewayStructuredLoggingRule", 26 | "ApiGatewayDefaultThrottlingRule", 27 | "ApiGatewayTracingRule", 28 | "AppSyncTracingRule", 29 | "EventBridgeDLQRule", 30 | "LambdaESMDestinationRule", 31 | "LambdaLogRetentionRule", 32 | "LambdaPermissionPrincipalsRule", 33 | "LambdaStarPermissionRule", 34 | "LambdaTracingRule", 35 | "LambdaDefaultMemorySizeRule", 36 | "LambdaDefaultTimeoutRule", 37 | "LambdaAsyncNoDestinationRule", 38 | "SnsNoRedrivePolicyRule", 39 | "SqsNoRedrivePolicyRule", 40 | "StepFunctionsTracingRule", 41 | ] 42 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/appsync.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rules for AppSync resources 3 | """ 4 | 5 | from cfnlint.rules import CloudFormationLintRule, RuleMatch 6 | 7 | 8 | class AppSyncTracingRule(CloudFormationLintRule): 9 | """ 10 | Ensure AppSync GraphQL APIs have tracing enabled 11 | """ 12 | 13 | id = "WS3000" # noqa: N815 14 | shortdesc = "AppSync Tracing" 15 | description = "Ensure AppSync GraphQL APIs have tracing enabled" 16 | source_url = "https://awslabs.github.io/serverless-rules/rules/appsync/tracing/" 17 | tags = ["appsync"] 18 | 19 | _message = "AppSync GraphQL API {} should have XrayEnabled set to true." 20 | 21 | def match(self, cfn): 22 | """ 23 | Match against AppSync GraphQL Apis without tracing enabled 24 | """ 25 | 26 | matches = [] 27 | 28 | for key, value in cfn.get_resources(["AWS::AppSync::GraphQLApi"]).items(): 29 | xray_enabled = value.get("Properties", {}).get("XrayEnabled", False) 30 | 31 | if not xray_enabled: 32 | matches.append(RuleMatch(["Resources", key], self._message.format(key))) 33 | 34 | return matches 35 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/eventbridge.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rules for EventBridge resources 3 | """ 4 | 5 | from cfnlint.rules import CloudFormationLintRule, RuleMatch 6 | 7 | 8 | class EventBridgeDLQRule(CloudFormationLintRule): 9 | """ 10 | Ensure Event Bridge rules have a DLQ configured 11 | """ 12 | 13 | id = "ES4000" # noqa: N815 14 | shortdesc = "EventBridge DLQ" 15 | description = "Ensure Event Bridge rules have a DLQ configured" 16 | source_url = "https://awslabs.github.io/serverless-rules/rules/eventbridge/rule_without_dlq/" 17 | tags = ["stepfunctions"] 18 | 19 | _message = "EventBridge rule {} should have a DeadLetterConfig.Arn property for all its Targets." 20 | 21 | def match(self, cfn): 22 | """ 23 | Match against EventBridge rules without a DeadLetterConfig for all targets 24 | """ 25 | 26 | matches = [] 27 | 28 | for key, value in cfn.get_resources(["AWS::Events::Rule"]).items(): 29 | targets = value.get("Properties", {}).get("Targets", []) 30 | 31 | if not all(["Arn" in t.get("DeadLetterConfig", {}) for t in targets]): 32 | matches.append(RuleMatch(["Resources", key], self._message.format(key))) 33 | 34 | return matches 35 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/sns.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rules for SNS resources 3 | """ 4 | 5 | from cfnlint.rules import CloudFormationLintRule, RuleMatch 6 | 7 | 8 | class SnsNoRedrivePolicyRule(CloudFormationLintRule): 9 | """ 10 | Ensure SNS subscriptions have a redrive policy configured 11 | """ 12 | 13 | id = "ES7000" # noqa: N815 14 | shortdesc = "SNS No Redrive Policy" 15 | description = "Ensure SNS subscriptions have a redrive policy configured" 16 | source_url = "https://awslabs.github.io/serverless-rules/rules/sns/redrive_policy/" 17 | tags = ["sns"] 18 | 19 | _message = "SNS Subscription {} should have a RedrivePolicy property configured." 20 | 21 | def match(self, cfn): 22 | """ 23 | Match against SNS subscriptions without RedrivePolicy 24 | """ 25 | 26 | matches = [] 27 | 28 | for key, value in cfn.get_resources(["AWS::SNS::Subscription"]).items(): 29 | redrive_policy = value.get("Properties", {}).get("RedrivePolicy", None) 30 | 31 | if redrive_policy is None: 32 | matches.append(RuleMatch(["Resources", key], self._message.format(key))) 33 | 34 | return matches 35 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/sqs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rules for SQS resources 3 | """ 4 | 5 | from cfnlint.rules import CloudFormationLintRule, RuleMatch 6 | 7 | from ..utils import Value 8 | 9 | 10 | class SqsNoRedrivePolicyRule(CloudFormationLintRule): 11 | """ 12 | Ensure SQS queues have a redrive policy configured 13 | """ 14 | 15 | id = "ES6000" # noqa: N815 16 | shortdesc = "SQS No Redrive Policy" 17 | description = "Ensure SQS queues have a redrive policy configured" 18 | source_url = "https://awslabs.github.io/serverless-rules/rules/sqs/redrive_policy/" 19 | tags = ["sqs"] 20 | 21 | _message = "SQS Queue {} should have a RedrivePolicy property configured." 22 | 23 | def match(self, cfn): 24 | """ 25 | Match against SQS queues without RedrivePolicy 26 | """ 27 | 28 | matches = {} 29 | dlqs = [] 30 | 31 | for key, value in cfn.get_resources(["AWS::SQS::Queue"]).items(): 32 | redrive_policy = value.get("Properties", {}).get("RedrivePolicy", None) 33 | 34 | if redrive_policy is None: 35 | matches[key] = RuleMatch(["Resources", key], self._message.format(key)) 36 | 37 | else: 38 | redrive_policy = Value(redrive_policy) 39 | # If a queue is used as a DLQ, it doesn't need a redrive policy 40 | # See https://github.com/awslabs/serverless-rules/issues/79 41 | dlqs.extend(redrive_policy.references) 42 | 43 | return [v for k, v in matches.items() if k not in dlqs] 44 | -------------------------------------------------------------------------------- /cfn-lint-serverless/cfn_lint_serverless/rules/step_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rules for Step Functions resources 3 | """ 4 | 5 | from cfnlint.rules import CloudFormationLintRule, RuleMatch 6 | 7 | 8 | class StepFunctionsTracingRule(CloudFormationLintRule): 9 | """ 10 | Ensure Step Functions state machines have tracing enabled 11 | """ 12 | 13 | id = "WS5000" # noqa: N815 14 | shortdesc = "Step Functions Tracing" 15 | description = "Ensure that Step Functions state machines have tracing enabled" 16 | source_url = "https://awslabs.github.io/serverless-rules/rules/step_functions/tracing/" 17 | tags = ["stepfunctions"] 18 | 19 | _message = "Step Functions state machine {} should have TracingConfiguration.Enabled set to true." 20 | 21 | def match(self, cfn): 22 | """ 23 | Match against Step Functions state machine without tracing enabled 24 | """ 25 | 26 | matches = [] 27 | 28 | for key, value in cfn.get_resources(["AWS::StepFunctions::StateMachine"]).items(): 29 | tracing_enabled = value.get("Properties", {}).get("TracingConfiguration", {}).get("Enabled", False) 30 | 31 | if not tracing_enabled: 32 | matches.append(RuleMatch(["Resources", key], self._message.format(key))) 33 | 34 | return matches 35 | -------------------------------------------------------------------------------- /cfn-lint-serverless/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cfn_lint_serverless" 3 | version = "0.3.3" 4 | description = "Serverless rules for cfn-lint" 5 | authors = [{name = "Amazon Web Service"}] 6 | readme = "README.md" 7 | license = "MIT-0" 8 | requires-python = ">=3.9.1,<4" 9 | dependencies = [ 10 | "cfn-lint>=1.33.0" 11 | ] 12 | 13 | [dependency-groups] 14 | dev = [ 15 | "bandit>=1.8.0", 16 | "coverage>=7.6.10", 17 | "pytest>=8.3.5", 18 | "pytest-cov>=6.1.1", 19 | "radon>=6.0.1", 20 | "ruff>=0.11.5", 21 | "xenon>=0.9.3", 22 | ] 23 | 24 | [build-system] 25 | requires = ["hatchling"] 26 | build-backend = "hatchling.build" 27 | -------------------------------------------------------------------------------- /cfn-lint-serverless/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -ra --cov --cov-config=.coveragerc 3 | testpaths = ./tests -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/serverless-rules/a827a1a5b99655cc6df458ac7bdab4bf5aba25aa/cfn-lint-serverless/tests/__init__.py -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1001-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | EventSourceMapping: 5 | Type: AWS::Lambda::EventSourceMapping 6 | Properties: 7 | Enabled: false 8 | FunctionName: my-function 9 | EventSourceArn: arn:aws:dynamodb:us-east-1:111122223333:table/my-table/stream/my-stream 10 | StartingPosition: LATEST 11 | -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1001.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | EventSourceMapping: 5 | Type: AWS::Lambda::EventSourceMapping 6 | Properties: 7 | Enabled: false 8 | FunctionName: my-function 9 | EventSourceArn: arn:aws:dynamodb:us-east-1:111122223333:table/my-table/stream/my-stream 10 | StartingPosition: LATEST 11 | DestinationConfig: 12 | OnFailure: 13 | Destination: arn:aws:sqs:us-east-1:111122223333:my-dlq 14 | -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1005.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1005.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | MemorySize: 2048 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1006.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1006.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Timeout: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1007.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Globals: 5 | Function: 6 | Runtime: python3.12 7 | Handler: main.handler 8 | 9 | Resources: 10 | CloudWatchEventFunction: 11 | Type: AWS::Serverless::Function 12 | Properties: 13 | CodeUri: . 14 | Events: 15 | CloudWatchEvent: 16 | Type: CloudWatchEvent 17 | Properties: 18 | Pattern: 19 | source: ["my-service"] 20 | 21 | EventBridgeRuleFunction: 22 | Type: AWS::Serverless::Function 23 | Properties: 24 | CodeUri: . 25 | Events: 26 | EventBridgeRule: 27 | Type: EventBridgeRule 28 | Properties: 29 | Pattern: 30 | source: ["my-service"] 31 | 32 | IoTRuleFunction: 33 | Type: AWS::Serverless::Function 34 | Properties: 35 | CodeUri: . 36 | Events: 37 | IoTRule: 38 | Type: IoTRule 39 | Properties: 40 | Sql: "SELECT * FROM 'topic/subtopic'" 41 | 42 | S3Function: 43 | Type: AWS::Serverless::Function 44 | Properties: 45 | CodeUri: . 46 | Events: 47 | S3: 48 | Type: S3 49 | Properties: 50 | Bucket: !Ref MyBucket 51 | Events: s3:ObjectCreated:* 52 | 53 | ScheduleFunction: 54 | Type: AWS::Serverless::Function 55 | Properties: 56 | CodeUri: . 57 | Events: 58 | Schedule: 59 | Type: Schedule 60 | Properties: 61 | Schedule: "rate(5 minutes)" 62 | 63 | SNSFunction: 64 | Type: AWS::Serverless::Function 65 | Properties: 66 | CodeUri: . 67 | Events: 68 | SNS: 69 | Type: SNS 70 | Properties: 71 | Topic: !Sub arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:my-sns-topic 72 | 73 | MyBucket: 74 | Type: AWS::S3::Bucket -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es1007.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Globals: 5 | Function: 6 | Runtime: python3.12 7 | Handler: main.handler 8 | EventInvokeConfig: 9 | DestinationConfig: 10 | OnFailure: 11 | Type: SQS 12 | 13 | Resources: 14 | CloudWatchEventFunction: 15 | Type: AWS::Serverless::Function 16 | Properties: 17 | CodeUri: . 18 | Events: 19 | CloudWatchEvent: 20 | Type: CloudWatchEvent 21 | Properties: 22 | Pattern: 23 | source: ["my-service"] 24 | 25 | EventBridgeRuleFunction: 26 | Type: AWS::Serverless::Function 27 | Properties: 28 | CodeUri: . 29 | Events: 30 | EventBridgeRule: 31 | Type: EventBridgeRule 32 | Properties: 33 | Pattern: 34 | source: ["my-service"] 35 | 36 | IoTRuleFunction: 37 | Type: AWS::Serverless::Function 38 | Properties: 39 | CodeUri: . 40 | Events: 41 | IoTRule: 42 | Type: IoTRule 43 | Properties: 44 | Sql: "SELECT * FROM 'topic/subtopic'" 45 | 46 | S3Function: 47 | Type: AWS::Serverless::Function 48 | Properties: 49 | CodeUri: . 50 | Events: 51 | S3: 52 | Type: S3 53 | Properties: 54 | Bucket: !Ref MyBucket 55 | Events: s3:ObjectCreated:* 56 | 57 | ScheduleFunction: 58 | Type: AWS::Serverless::Function 59 | Properties: 60 | CodeUri: . 61 | Events: 62 | Schedule: 63 | Type: Schedule 64 | Properties: 65 | Schedule: "rate(5 minutes)" 66 | 67 | SNSFunction: 68 | Type: AWS::Serverless::Function 69 | Properties: 70 | CodeUri: . 71 | Events: 72 | SNS: 73 | Type: SNS 74 | Properties: 75 | Topic: !Sub arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:my-sns-topic 76 | 77 | MyBucket: 78 | Type: AWS::S3::Bucket -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2000-http-empty.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: {} 9 | 10 | DefinitionBody: 11 | openapi: "3.0.1" 12 | 13 | info: 14 | title: "test-api" 15 | version: 1.0.0 16 | 17 | paths: 18 | /: 19 | get: 20 | responses: 21 | "404": 22 | description: "404 File Not Found" 23 | x-amazon-apigateway-integration: 24 | requestTemplates: 25 | application/json: '{"statusCode": 200}' 26 | passthroughBehavior: when_no_match 27 | responses: 28 | default: 29 | statusCode: "404" 30 | responseTemplates: 31 | application/json: "" 32 | type: mock 33 | 34 | StageName: prod 35 | DefaultRouteSettings: 36 | ThrottlingBurstLimit: 1000 37 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2000-http-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | DefinitionBody: 9 | openapi: "3.0.1" 10 | 11 | info: 12 | title: "test-api" 13 | version: 1.0.0 14 | 15 | paths: 16 | /: 17 | get: 18 | responses: 19 | "404": 20 | description: "404 File Not Found" 21 | x-amazon-apigateway-integration: 22 | requestTemplates: 23 | application/json: '{"statusCode": 200}' 24 | passthroughBehavior: when_no_match 25 | responses: 26 | default: 27 | statusCode: "404" 28 | responseTemplates: 29 | application/json: "" 30 | type: mock 31 | 32 | StageName: prod 33 | DefaultRouteSettings: 34 | ThrottlingBurstLimit: 1000 35 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2000-rest-empty.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: {} 9 | 10 | DefinitionBody: 11 | openapi: "3.0.1" 12 | 13 | info: 14 | title: "test-api" 15 | version: 1.0.0 16 | 17 | paths: 18 | /: 19 | get: 20 | responses: 21 | "404": 22 | description: "404 File Not Found" 23 | x-amazon-apigateway-integration: 24 | requestTemplates: 25 | application/json: '{"statusCode": 200}' 26 | passthroughBehavior: when_no_match 27 | responses: 28 | default: 29 | statusCode: "404" 30 | responseTemplates: 31 | application/json: "" 32 | type: mock 33 | 34 | StageName: prod 35 | MethodSettings: 36 | - HttpMethod: "*" 37 | ResourcePath: "/*" 38 | ThrottlingRateLimit: 10 39 | ThrottlingBurstLimit: 1000 40 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2000-rest-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | DefinitionBody: 9 | openapi: "3.0.1" 10 | 11 | info: 12 | title: "test-api" 13 | version: 1.0.0 14 | 15 | paths: 16 | /: 17 | get: 18 | responses: 19 | "404": 20 | description: "404 File Not Found" 21 | x-amazon-apigateway-integration: 22 | requestTemplates: 23 | application/json: '{"statusCode": 200}' 24 | passthroughBehavior: when_no_match 25 | responses: 26 | default: 27 | statusCode: "404" 28 | responseTemplates: 29 | application/json: "" 30 | type: mock 31 | 32 | StageName: prod 33 | MethodSettings: 34 | - HttpMethod: "*" 35 | ResourcePath: "/*" 36 | ThrottlingRateLimit: 10 37 | ThrottlingBurstLimit: 1000 38 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-http-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | 28 | DefinitionBody: 29 | openapi: "3.0.1" 30 | 31 | info: 32 | title: "test-api" 33 | version: 1.0.0 34 | 35 | paths: 36 | /: 37 | get: 38 | responses: 39 | "404": 40 | description: "404 File Not Found" 41 | x-amazon-apigateway-integration: 42 | requestTemplates: 43 | application/json: '{"statusCode": 200}' 44 | passthroughBehavior: when_no_match 45 | responses: 46 | default: 47 | statusCode: "404" 48 | responseTemplates: 49 | application/json: "" 50 | type: mock 51 | 52 | StageName: prod -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-http-nothrottle.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | 28 | DefinitionBody: 29 | openapi: "3.0.1" 30 | 31 | info: 32 | title: "test-api" 33 | version: 1.0.0 34 | 35 | paths: 36 | /: 37 | get: 38 | responses: 39 | "404": 40 | description: "404 File Not Found" 41 | x-amazon-apigateway-integration: 42 | requestTemplates: 43 | application/json: '{"statusCode": 200}' 44 | passthroughBehavior: when_no_match 45 | responses: 46 | default: 47 | statusCode: "404" 48 | responseTemplates: 49 | application/json: "" 50 | type: mock 51 | 52 | StageName: prod 53 | DefaultRouteSettings: {} -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-http.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | 28 | DefinitionBody: 29 | openapi: "3.0.1" 30 | 31 | info: 32 | title: "test-api" 33 | version: 1.0.0 34 | 35 | paths: 36 | /: 37 | get: 38 | responses: 39 | "404": 40 | description: "404 File Not Found" 41 | x-amazon-apigateway-integration: 42 | requestTemplates: 43 | application/json: '{"statusCode": 200}' 44 | passthroughBehavior: when_no_match 45 | responses: 46 | default: 47 | statusCode: "404" 48 | responseTemplates: 49 | application/json: "" 50 | type: mock 51 | 52 | StageName: prod 53 | DefaultRouteSettings: 54 | ThrottlingBurstLimit: 1000 55 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-rest-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-rest-nodefault.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | MethodSettings: 53 | - HttpMethod: "GET" 54 | ResourcePath: "/my-path" 55 | ThrottlingRateLimit: 10 56 | ThrottlingBurstLimit: 1000 57 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-rest-nothrottle.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | MethodSettings: 53 | - HttpMethod: "*" 54 | ResourcePath: "/*" 55 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es2003-rest.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | MethodSettings: 53 | - HttpMethod: "*" 54 | ResourcePath: "/*" 55 | ThrottlingRateLimit: 10 56 | ThrottlingBurstLimit: 1000 57 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es4000-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Rule: 5 | Type: AWS::Events::Rule 6 | Properties: 7 | EventBusName: default 8 | EventPattern: | 9 | { 10 | "detail": ["my-detail"] 11 | } 12 | Targets: 13 | - Id: Lambda1 14 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction 15 | - Id: Lambda2 16 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction2 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es4000-partial.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Rule: 5 | Type: AWS::Events::Rule 6 | Properties: 7 | EventBusName: default 8 | EventPattern: | 9 | { 10 | "detail": ["my-detail"] 11 | } 12 | Targets: 13 | - Id: Lambda1 14 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction 15 | - Id: Lambda2 16 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction2 17 | DeadLetterConfig: 18 | Arn: arn:aws:sqs:us-east-1:111122223333:dlq -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es4000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Rule: 5 | Type: AWS::Events::Rule 6 | Properties: 7 | EventBusName: default 8 | EventPattern: | 9 | { 10 | "detail": ["my-detail"] 11 | } 12 | Targets: 13 | - Id: Lambda1 14 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction 15 | DeadLetterConfig: 16 | Arn: arn:aws:sqs:us-east-1:111122223333:dlq 17 | - Id: Lambda2 18 | Arn: arn:aws:lambda:us-east-1:111122223333:function:HelloFunction2 19 | DeadLetterConfig: 20 | Arn: arn:aws:sqs:us-east-1:111122223333:dlq -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es6000-multiple.pass.yaml: -------------------------------------------------------------------------------- 1 | # Test for https://github.com/awslabs/serverless-rules/issues/79 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | 4 | Resources: 5 | Queue: 6 | Type: AWS::SQS::Queue 7 | Properties: 8 | RedrivePolicy: !Sub | 9 | { 10 | "deadLetterTargetArn": "${DLQ}", 11 | "maxReceiveCount": 4 12 | } 13 | 14 | DLQ: 15 | Type: AWS::SQS::Queue -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es6000.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Queue: 5 | Type: AWS::SQS::Queue -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es6000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Queue: 5 | Type: AWS::SQS::Queue 6 | Properties: 7 | RedrivePolicy: | 8 | { 9 | "deadLetterTargetArn": "my-sqs-arn", 10 | "maxReceiveCount": 4 11 | } -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es7000.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Subscription: 5 | Type: AWS::SNS::Subscription 6 | Properties: 7 | Protocol: https 8 | Endpoint: https://example.com/ 9 | TopicArn: "my-topic-arn" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/es7000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | Subscription: 5 | Type: AWS::SNS::Subscription 6 | Properties: 7 | Protocol: https 8 | Endpoint: https://example.com/ 9 | TopicArn: "my-topic-arn" 10 | RedrivePolicy: | 11 | { 12 | "deadLetterTargetArn": "my-sqs-arn" 13 | } -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1000-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1000-passthrough.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: PassThrough -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1002-permissions.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Events: 13 | Topic: 14 | Type: SNS 15 | Properties: 16 | Topic: arn:aws:sns:us-east-1:111122223333:topic 17 | EventBridge: 18 | Type: EventBridgeRule 19 | Properties: 20 | EventBusName: default 21 | Pattern: 22 | detail: 23 | - test 24 | DeadLetterConfig: 25 | Arn: arn:aws:sqs:us-east-1:111122223333:dlq -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1002-sam.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | CodeSigningConfigArn: arn:aws:lambda:eu-east-1:111122223333:code-signing-config:csc-d0a6cc682193458f9 13 | 14 | Permission1: 15 | Type: AWS::Lambda::Permission 16 | Properties: 17 | FunctionName: !GetAtt Function.Arn 18 | Principal: sns.amazonaws.com 19 | Action: lambda:Invoke 20 | SourceArn: arn:aws:sns:us-east-1:111122223333:topic3 21 | 22 | Permission2: 23 | Type: AWS::Lambda::Permission 24 | Properties: 25 | FunctionName: !Ref Function 26 | Principal: events.amazonaws.com 27 | Action: lambda:Invoke 28 | SourceArn: arn:aws:events:us-east-1:111122223333:rule/my-rule -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1002-sub-principal.pass.yaml: -------------------------------------------------------------------------------- 1 | # Test for https://github.com/awslabs/serverless-rules/issues/78 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Transform: "AWS::Serverless-2016-10-31" 4 | 5 | Parameters: 6 | ProjectId: 7 | Type: String 8 | 9 | Resources: 10 | Function: 11 | Type: AWS::Serverless::Function 12 | Properties: 13 | CodeUri: . 14 | Runtime: python3.12 15 | Handler: main.handler 16 | Tracing: Active 17 | 18 | Permission: 19 | Type: AWS::Lambda::Permission 20 | Properties: 21 | FunctionName: !GetAtt Function.Arn 22 | Principal: !Sub pinpoint.${AWS::Region}.amazonaws.com 23 | Action: lambda:Invoke 24 | SourceArn: !Sub 'arn:aws:mobiletargeting:${AWS::Region}:${AWS::AccountId}:/apps/${ProjectId}*' -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1002.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Events: 13 | Topic1: 14 | Type: SNS 15 | Properties: 16 | Topic: arn:aws:sns:us-east-1:111122223333:topic1 17 | Topic2: 18 | Type: SNS 19 | Properties: 20 | Topic: arn:aws:sns:us-east-1:111122223333:topic2 21 | 22 | Permission1: 23 | Type: AWS::Lambda::Permission 24 | Properties: 25 | FunctionName: !GetAtt Function.Arn 26 | Principal: sns.amazonaws.com 27 | Action: lambda:Invoke 28 | SourceArn: arn:aws:sns:us-east-1:111122223333:topic3 29 | 30 | Permission2: 31 | Type: AWS::Lambda::Permission 32 | Properties: 33 | FunctionName: !Ref Function 34 | Principal: sns.amazonaws.com 35 | Action: lambda:Invoke 36 | SourceArn: arn:aws:sns:us-east-1:111122223333:topic4 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1003-fullstar.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Policies: 13 | - Version: "2012-10-17" 14 | Statement: 15 | - Effect: Allow 16 | Action: "*" 17 | Resource: "arn:aws:s3:::my-bucket/*" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1003-substar.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Policies: 13 | - Version: "2012-10-17" 14 | Statement: 15 | - Effect: Allow 16 | Action: s3:Get* 17 | Resource: "arn:aws:s3:::my-bucket/*" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1003.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Policies: 13 | - Version: "2012-10-17" 14 | Statement: 15 | - Effect: Allow 16 | Action: s3:* 17 | Resource: "arn:aws:s3:::my-bucket/*" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1003.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | Policies: 13 | - DynamoDBWritePolicy: 14 | TableName: my-table 15 | - Version: "2012-10-17" 16 | Statement: 17 | - Effect: Allow 18 | Action: s3:GetObject 19 | Resource: "arn:aws:s3:::my-bucket/*" 20 | 21 | Role: 22 | Type: AWS::IAM::Role 23 | Properties: 24 | AssumeRolePolicyDocument: 25 | Version: "2012-10-17" 26 | Statement: 27 | - Effect: Allow 28 | Principal: 29 | Service: 30 | - lambda.amazonaws.com 31 | Action: 32 | - 'sts:AssumeRole' 33 | Policies: 34 | - PolicyName: MyPolicy 35 | PolicyDocument: 36 | Version: "2012-10-17" 37 | Statement: 38 | - Effect: Allow 39 | Action: s3:GetObject 40 | Resource: "arn:aws:s3:::my-bucket/*" 41 | 42 | NonLambdaRole: 43 | Type: AWS::IAM::Role 44 | Properties: 45 | AssumeRolePolicyDocument: 46 | Version: "2012-10-17" 47 | Statement: 48 | - Effect: Allow 49 | Principal: 50 | Service: 51 | - ec2.amazonaws.com 52 | Action: 53 | - 'sts:AssumeRole' 54 | Policies: 55 | - PolicyName: MyPolicy 56 | PolicyDocument: 57 | Version: "2012-10-17" 58 | Statement: 59 | - Effect: Allow 60 | Action: s3:* 61 | Resource: "arn:aws:s3:::my-bucket/*" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-hardcoded.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | FunctionName: my_function_name 9 | CodeUri: . 10 | Runtime: python3.12 11 | Handler: main.handler 12 | Tracing: Active 13 | 14 | LogGroup: 15 | Type: AWS::Logs::LogGroup 16 | Properties: 17 | LogGroupName: "/aws/lambda/my_function_name" 18 | RetentionInDays: 7 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-join.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | 13 | LogGroup: 14 | Type: AWS::Logs::LogGroup 15 | Properties: 16 | LogGroupName: 17 | Fn::Join: 18 | - "" 19 | - - "/aws/lambda/" 20 | - !Ref Function 21 | RetentionInDays: 7 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-noretention.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | 13 | LogGroup: 14 | Type: AWS::Logs::LogGroup 15 | Properties: 16 | LogGroupName: !Sub "/aws/lambda/${Function}" -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-sub-vars-hardcoded.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | FunctionName: my-function-name 9 | CodeUri: . 10 | Runtime: python3.12 11 | Handler: main.handler 12 | Tracing: Active 13 | 14 | LogGroup: 15 | Type: AWS::Logs::LogGroup 16 | Properties: 17 | LogGroupName: !Sub 18 | - "/aws/lambda/${Fn}" 19 | - Fn: my-function-name 20 | 21 | RetentionInDays: 7 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-sub-vars.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | 13 | LogGroup: 14 | Type: AWS::Logs::LogGroup 15 | Properties: 16 | LogGroupName: !Sub 17 | - "/aws/lambda/${Fn}" 18 | - Fn: !Ref Function 19 | 20 | RetentionInDays: 7 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws1004-sub.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Function: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | CodeUri: . 9 | Runtime: python3.12 10 | Handler: main.handler 11 | Tracing: Active 12 | 13 | LogGroup: 14 | Type: AWS::Logs::LogGroup 15 | Properties: 16 | LogGroupName: !Sub "/aws/lambda/${Function}" 17 | RetentionInDays: 7 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2001-http-json.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | 28 | DefinitionBody: 29 | openapi: "3.0.1" 30 | 31 | info: 32 | title: "test-api" 33 | version: 1.0.0 34 | 35 | paths: 36 | /: 37 | get: 38 | responses: 39 | "404": 40 | description: "404 File Not Found" 41 | x-amazon-apigateway-integration: 42 | requestTemplates: 43 | application/json: '{"statusCode": 200}' 44 | passthroughBehavior: when_no_match 45 | responses: 46 | default: 47 | statusCode: "404" 48 | responseTemplates: 49 | application/json: "" 50 | type: mock 51 | 52 | StageName: prod 53 | DefaultRouteSettings: 54 | ThrottlingBurstLimit: 1000 55 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2001-http-nojson.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: "requestId=$context.requestId" 11 | 12 | DefinitionBody: 13 | openapi: "3.0.1" 14 | 15 | info: 16 | title: "test-api" 17 | version: 1.0.0 18 | 19 | paths: 20 | /: 21 | get: 22 | responses: 23 | "404": 24 | description: "404 File Not Found" 25 | x-amazon-apigateway-integration: 26 | requestTemplates: 27 | application/json: '{"statusCode": 200}' 28 | passthroughBehavior: when_no_match 29 | responses: 30 | default: 31 | statusCode: "404" 32 | responseTemplates: 33 | application/json: "" 34 | type: mock 35 | 36 | StageName: prod 37 | DefaultRouteSettings: 38 | ThrottlingBurstLimit: 1000 39 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2001-rest-json.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | 28 | DefinitionBody: 29 | openapi: "3.0.1" 30 | 31 | info: 32 | title: "test-api" 33 | version: 1.0.0 34 | 35 | paths: 36 | /: 37 | get: 38 | responses: 39 | "404": 40 | description: "404 File Not Found" 41 | x-amazon-apigateway-integration: 42 | requestTemplates: 43 | application/json: '{"statusCode": 200}' 44 | passthroughBehavior: when_no_match 45 | responses: 46 | default: 47 | statusCode: "404" 48 | responseTemplates: 49 | application/json: "" 50 | type: mock 51 | 52 | StageName: prod 53 | MethodSettings: 54 | - HttpMethod: "*" 55 | ResourcePath: "/*" 56 | ThrottlingRateLimit: 10 57 | ThrottlingBurstLimit: 1000 58 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2001-rest-nojson.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: "requestId=$context.requestId" 11 | 12 | DefinitionBody: 13 | openapi: "3.0.1" 14 | 15 | info: 16 | title: "test-api" 17 | version: 1.0.0 18 | 19 | paths: 20 | /: 21 | get: 22 | responses: 23 | "404": 24 | description: "404 File Not Found" 25 | x-amazon-apigateway-integration: 26 | requestTemplates: 27 | application/json: '{"statusCode": 200}' 28 | passthroughBehavior: when_no_match 29 | responses: 30 | default: 31 | statusCode: "404" 32 | responseTemplates: 33 | application/json: "" 34 | type: mock 35 | 36 | StageName: prod 37 | MethodSettings: 38 | - HttpMethod: "*" 39 | ResourcePath: "/*" 40 | ThrottlingRateLimit: 10 41 | ThrottlingBurstLimit: 1000 42 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2002-http-false.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | DefaultRouteSettings: 53 | ThrottlingBurstLimit: 1000 54 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2002-http-missing.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::HttpApi 7 | Properties: 8 | AccessLogSettings: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | DefaultRouteSettings: 53 | ThrottlingBurstLimit: 1000 54 | ThrottlingRateLimit: 10 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2002-rest-false.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | MethodSettings: 53 | - HttpMethod: "*" 54 | ResourcePath: "/*" 55 | ThrottlingRateLimit: 10 56 | ThrottlingBurstLimit: 1000 57 | TracingEnabled: false -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2002-rest-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | AccessLogSetting: 9 | DestinationArn: " arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 10 | Format: | 11 | { 12 | "stage" : "$context.stage", 13 | "request_id" : "$context.requestId", 14 | "api_id" : "$context.apiId", 15 | "resource_path" : "$context.resourcePath", 16 | "resource_id" : "$context.resourceId", 17 | "http_method" : "$context.httpMethod", 18 | "source_ip" : "$context.identity.sourceIp", 19 | "user-agent" : "$context.identity.userAgent", 20 | "account_id" : "$context.identity.accountId", 21 | "api_key" : "$context.identity.apiKey", 22 | "caller" : "$context.identity.caller", 23 | "user" : "$context.identity.user", 24 | "user_arn" : "$context.identity.userArn", 25 | "integration_latency": $context.integration.latency 26 | } 27 | DefinitionBody: 28 | openapi: "3.0.1" 29 | 30 | info: 31 | title: "test-api" 32 | version: 1.0.0 33 | 34 | paths: 35 | /: 36 | get: 37 | responses: 38 | "404": 39 | description: "404 File Not Found" 40 | x-amazon-apigateway-integration: 41 | requestTemplates: 42 | application/json: '{"statusCode": 200}' 43 | passthroughBehavior: when_no_match 44 | responses: 45 | default: 46 | statusCode: "404" 47 | responseTemplates: 48 | application/json: "" 49 | type: mock 50 | 51 | StageName: prod 52 | MethodSettings: 53 | - HttpMethod: "*" 54 | ResourcePath: "/*" 55 | ThrottlingRateLimit: 10 56 | ThrottlingBurstLimit: 1000 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws2002-rest.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Resources: 5 | Api: 6 | Type: AWS::Serverless::Api 7 | Properties: 8 | DefinitionUri: openapi.yaml 9 | StageName: prod 10 | TracingEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws3000-false.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | GraphQLApi: 5 | Type: AWS::AppSync::GraphQLApi 6 | Properties: 7 | Name: api 8 | AuthenticationType: AWS_IAM 9 | 10 | XrayEnabled: false -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws3000-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | GraphQLApi: 5 | Type: AWS::AppSync::GraphQLApi 6 | Properties: 7 | Name: api 8 | AuthenticationType: AWS_IAM -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws3000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | GraphQLApi: 5 | Type: AWS::AppSync::GraphQLApi 6 | Properties: 7 | Name: api 8 | AuthenticationType: AWS_IAM 9 | 10 | XrayEnabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws5000-false.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | StateMachine: 5 | Type: AWS::StepFunctions::StateMachine 6 | Properties: 7 | DefinitionString: | 8 | { 9 | "StartAt": "HelloWorld", 10 | "States": { 11 | "HelloWorld": { 12 | "Type": "Task", 13 | "Resource": "arn:aws:lambda:us-east-1:111122223333:function:HelloFunction", 14 | "End": true 15 | } 16 | } 17 | } 18 | RoleArn: arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1 19 | 20 | TracingConfiguration: 21 | Enabled: false -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws5000-missing.fail.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | StateMachine: 5 | Type: AWS::StepFunctions::StateMachine 6 | Properties: 7 | DefinitionString: | 8 | { 9 | "StartAt": "HelloWorld", 10 | "States": { 11 | "HelloWorld": { 12 | "Type": "Task", 13 | "Resource": "arn:aws:lambda:us-east-1:111122223333:function:HelloFunction", 14 | "End": true 15 | } 16 | } 17 | } 18 | RoleArn: arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1 -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/templates/ws5000.pass.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Resources: 4 | StateMachine: 5 | Type: AWS::StepFunctions::StateMachine 6 | Properties: 7 | DefinitionString: | 8 | { 9 | "StartAt": "HelloWorld", 10 | "States": { 11 | "HelloWorld": { 12 | "Type": "Task", 13 | "Resource": "arn:aws:lambda:us-east-1:111122223333:function:HelloFunction", 14 | "End": true 15 | } 16 | } 17 | } 18 | RoleArn: arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1 19 | 20 | TracingConfiguration: 21 | Enabled: true -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/test_templates.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testing templates 3 | """ 4 | 5 | import collections 6 | import os 7 | from typing import List 8 | 9 | import cfnlint.core 10 | import cfnlint.decode.cfn_yaml 11 | import pytest 12 | 13 | # Loading templates 14 | Template = collections.namedtuple("Template", ["filename", "rule", "mode"]) 15 | 16 | 17 | def get_templates() -> List[Template]: 18 | def parse_filename(filename: str) -> Template: 19 | values = filename.split(".") 20 | rule = values[0].split("-")[0].upper() 21 | mode = values[1].lower() 22 | 23 | return Template(filename, rule, mode) 24 | 25 | template_folder = os.path.join(os.path.dirname(__file__), "templates") 26 | templates = [parse_filename(filename) for filename in os.listdir(template_folder)] 27 | 28 | return templates 29 | 30 | 31 | templates = get_templates() 32 | 33 | 34 | @pytest.fixture(scope="session") 35 | def rules(): 36 | return cfnlint.core.get_rules(["cfn_lint_serverless.rules"], [], []) 37 | 38 | 39 | def test_rule_with_templates(rules): 40 | """ 41 | Test that all rules have at least one corresponding template 42 | """ 43 | 44 | # Retrieve all serverless rule IDs 45 | rule_ids = {r.id for r in rules if r.id[1] == "S"} 46 | 47 | # Retrieve all rule IDs for tests 48 | test_rule_ids = {t[1] for t in templates} 49 | 50 | assert rule_ids == test_rule_ids 51 | 52 | 53 | @pytest.mark.parametrize("filename,rule,mode", templates) 54 | def test_template(filename, rule, mode, rules): 55 | """ 56 | Automatically test all templates in the ./templates/ folder 57 | """ 58 | 59 | filename = os.path.join(os.path.dirname(__file__), "templates", filename) 60 | 61 | template = cfnlint.decode.cfn_yaml.load(filename) 62 | matches = cfnlint.core.run_checks( 63 | filename, 64 | template, 65 | rules, 66 | # TODO: parametrize the region 67 | ["eu-west-1"], 68 | ) 69 | 70 | match_ids = [match.rule.id for match in matches] 71 | 72 | # No non-serverless errors 73 | assert not [m for m in match_ids if m[1] != "S"] 74 | 75 | if mode == "fail": 76 | assert rule in match_ids 77 | else: 78 | assert rule not in match_ids 79 | -------------------------------------------------------------------------------- /cfn-lint-serverless/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testing utility functions 3 | """ 4 | 5 | import pytest 6 | 7 | from cfn_lint_serverless import utils 8 | 9 | value_test_cases = [ 10 | # str 11 | {"input": "MyString", "id": "MyString", "references": []}, 12 | # Ref 13 | {"input": {"Ref": "MyResource"}, "id": "MyResource", "references": ["MyResource"]}, 14 | # Fn::GetAtt 15 | {"input": {"Fn::GetAtt": ["MyResource", "Arn"]}, "id": "MyResource.Arn", "references": ["MyResource"]}, 16 | # Fn::Join 17 | {"input": {"Fn::Join": ["/", ["ABC", "DEF"]]}, "id": "ABC/DEF", "references": []}, 18 | # Fn::Join with references 19 | { 20 | "input": {"Fn::Join": ["/", ["ABC", {"Ref": "MyResource"}]]}, 21 | "id": "ABC/MyResource", 22 | "references": ["MyResource"], 23 | }, 24 | # Fn::Sub 25 | {"input": {"Fn::Sub": "abc-${MyResource}"}, "id": "abc-${MyResource}", "references": ["MyResource"]}, 26 | # Fn::Sub with hard-coded variables 27 | {"input": {"Fn::Sub": ["abc-${MyVar}", {"MyVar": "MyResource"}]}, "id": "abc-MyResource", "references": []}, 28 | # Fn::Sub with variables and references 29 | { 30 | "input": {"Fn::Sub": ["abc-${MyVar}", {"MyVar": {"Ref": "MyResource"}}]}, 31 | "id": "abc-${MyVar}", 32 | "references": ["MyResource"], 33 | }, 34 | ] 35 | 36 | 37 | @pytest.mark.parametrize("case", value_test_cases) 38 | def test_value(case): 39 | """ 40 | Test Value() 41 | """ 42 | 43 | print(f"case: {case}") 44 | 45 | output = utils.Value(case["input"]) 46 | 47 | print(f"output id: {output.id}") 48 | print(f"output ref: {output.references}") 49 | 50 | assert case["id"] == output.id 51 | assert case["references"] == output.references 52 | 53 | 54 | def test_none_value(): 55 | """ 56 | Test Value(None) 57 | """ 58 | 59 | output = utils.Value(None) 60 | assert output is None 61 | -------------------------------------------------------------------------------- /docs/contributing/create_rule.md: -------------------------------------------------------------------------------- 1 | Creating rules 2 | ============== 3 | 4 | If you are thinking of creating or proposing a new rule, please follow the process outline below. The first step before adding a new rule is to [submit an issue](#create-an-issue) to collect feedback from other members of the community. 5 | 6 | ## Create an issue 7 | 8 | Before starting the implementation of a new rule, please [create an issue using the _New rule_ template](https://github.com/awslabs/serverless-rules/issues/new?assignees=&labels=feature-request%2C+triage&template=rule.md&title=). This will allow members of the community to provide feedback on its implementation, if it meets the needs of most serverless users, if it's the right level, etc. 9 | 10 | ## Template for `cfn-lint` rules 11 | 12 | ```python 13 | # TODO: set the rule name 14 | class __Rule(CloudFormationLintRule): 15 | # TODO: set docstring 16 | """ 17 | Ensure that ... 18 | """ 19 | 20 | # TODO: update these values 21 | id = "..." # noqa: N815 22 | shortdesc = "..." 23 | description = "Ensure that ..." 24 | source_url = "..." 25 | tags = ["lambda"] 26 | 27 | _message = "... {} ..." 28 | 29 | def match(self, cfn): 30 | # TODO: update docstring 31 | """ 32 | Match against ... 33 | """ 34 | 35 | matches = [] 36 | 37 | # TODO: set resource type 38 | for key, value in cfn.get_resources(["..."]).items(): 39 | # TODO: set property name 40 | prop = value.get("Properties", {}).get("...", None) 41 | 42 | if prop is None: 43 | matches.append(RuleMatch(["Resources", key], self._message.format(key))) 44 | 45 | return matches 46 | ``` 47 | 48 | ## Template for documentation 49 | 50 | Please use the following template when writing documentation for a rule. Each rule goes into a separate markdown file into the relevant service folder. For example, a rule for AWS Lambda would go into the `docs/rules/lambda/` folder. 51 | 52 | ~~~markdown 53 | # _Service Name Rule name_ 54 | 55 | __Level__: _Rule level_ 56 | {: class="badge badge-red" } 57 | 58 | __Initial version__: _release version_ 59 | {: class="badge badge-blue" } 60 | 61 | __cfn-lint__: _cfn-lint rule ID_ 62 | {: class="badge" } 63 | 64 | __tflint__: _tflint rule name_ 65 | {: class="badge" } 66 | 67 | _Short explanation on the rule_ 68 | 69 | ## Implementations 70 | 71 | === "CDK" 72 | 73 | ```typescript 74 | // Imports here 75 | 76 | export class MyStack extends cdk.Stack { 77 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 78 | super(scope, id, props); 79 | 80 | // Implementation 81 | } 82 | } 83 | ``` 84 | 85 | === "CloudFormation (JSON)" 86 | 87 | ```json 88 | { 89 | "Resources": { 90 | // Add resources here 91 | } 92 | } 93 | ``` 94 | 95 | === "CloudFormation (YAML)" 96 | 97 | ```yaml 98 | Resources: 99 | # Add resources here 100 | ``` 101 | 102 | === "Serverless Framework" 103 | 104 | ```yaml 105 | provider: 106 | name: aws 107 | # Add provider-specific configuration here 108 | 109 | resources: 110 | # Add resources here 111 | ``` 112 | 113 | === "Terraform" 114 | 115 | ```tf 116 | # Add Terraform resources here 117 | ``` 118 | 119 | ## See also 120 | 121 | * _List of links to the relevant documentation, from sources such as AWS Well-Architected, service documentation, etc._ 122 | ~~~ -------------------------------------------------------------------------------- /docs/images/aws-logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 31 | 32 | 34 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/images/cfn_lint_vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/serverless-rules/a827a1a5b99655cc6df458ac7bdab4bf5aba25aa/docs/images/cfn_lint_vscode.png -------------------------------------------------------------------------------- /docs/rules/api_gateway/structured_logging.md: -------------------------------------------------------------------------------- 1 | # API Gateway Structured Logging 2 | 3 | __Level__: Warning 4 | {: class="badge badge-yellow" } 5 | 6 | __Initial version__: 0.1.3 7 | {: class="badge badge-blue" } 8 | 9 | __cfn-lint__: WS2001 10 | {: class="badge" } 11 | 12 | __tflint (REST)__: aws_api_gateway_stage_structured_logging 13 | {: class="badge" } 14 | 15 | __tflint (HTTP)__: aws_apigatewayv2_stage_structured_logging 16 | {: class="badge" } 17 | 18 | You can customize the log format that Amazon API Gateway uses to send logs. Structured logging makes it easier to derive queries to answer arbitrary questions about the health of your application. 19 | 20 | ## Why is this a warning? 21 | 22 | The rule in `serverless-rules` only checks if the structured log is JSON-formatted. 23 | 24 | While CloudWatch Logs Insights will automatically discover fields in JSON log entries, you can use the `parse` command to parse custom log entries to extract fields from custom format. 25 | 26 | ## Implementations 27 | 28 | See the implementations for [Logging on API Gateway](logging.md). 29 | 30 | ## See also 31 | 32 | * [Serverless Lens: Centralized and structured logging](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/centralized-and-structured-logging.html) 33 | * [Monitoring REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-monitor.html) 34 | * [Monitoring your HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-monitor.html) 35 | * [Monitoring WebSocket APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-monitor.html) 36 | * [Amazon CloudWatch Logs: Supported Logs and Discovered Fields](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html) 37 | * [Amazon CloudWatch Logs: Logs Insights Query Syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html) -------------------------------------------------------------------------------- /docs/rules/appsync/tracing.md: -------------------------------------------------------------------------------- 1 | # AppSync Tracing 2 | 3 | __Level__: Warning 4 | {: class="badge badge-yellow" } 5 | 6 | __Initial version__: 0.1.3 7 | {: class="badge badge-blue" } 8 | 9 | __cfn-lint__: WS3000 10 | {: class="badge" } 11 | 12 | __tflint__: aws_appsync_graphql_api_tracing_rule 13 | {: class="badge" } 14 | 15 | AWS AppSync can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. 16 | 17 | ## Why is this a warning? 18 | 19 | You might use [third party solutions](https://aws.amazon.com/lambda/partners/) for monitoring serverless applications. If this is the case, enabling tracing for AppSync APIs might be optional. Refer to the documentation of your monitoring solutions to see if you should enable AWS X-Ray tracing or not. 20 | 21 | ## Implementations 22 | 23 | === "CDK" 24 | 25 | ```typescript 26 | import { GraphqlApi } from '@aws-cdk/aws-appsync'; 27 | 28 | export class MyStack extends cdk.Stack { 29 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 30 | super(scope, id, props); 31 | 32 | const myApi = new GraphqlApi( 33 | scope, 'MyApi', 34 | { 35 | name: 'my-api', 36 | // Enable active tracing 37 | xrayEnabled: true, 38 | } 39 | ); 40 | } 41 | } 42 | ``` 43 | 44 | === "CloudFormation (JSON)" 45 | 46 | ```json 47 | { 48 | "Resources": { 49 | "GraphQLApi": { 50 | "Type": "AWS::AppSync::GraphQLApi", 51 | "Properties": { 52 | "Name": "api", 53 | "AuthenticationType": "AWS_IAM", 54 | 55 | // Enable active tracing 56 | "XrayEnabled": true 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | === "CloudFormation (YAML)" 64 | 65 | ```yaml 66 | Resources: 67 | GraphQLApi: 68 | Type: AWS::AppSync::GraphQLApi 69 | Properties: 70 | Name: api 71 | AuthenticationType: AWS_IAM 72 | 73 | # Enable active tracing 74 | XrayEnabled: true 75 | ``` 76 | 77 | === "Serverless Framework" 78 | 79 | ```yaml 80 | resources: 81 | Resources: 82 | GraphQLApi: 83 | Type: AWS::AppSync::GraphQLApi 84 | Properties: 85 | Name: api 86 | AuthenticationType: AWS_IAM 87 | 88 | # Enable active tracing 89 | XrayEnabled: true 90 | ``` 91 | 92 | === "Terraform" 93 | 94 | ```tf 95 | resource "aws_appsync_graphql_api" "this" { 96 | name = "api" 97 | authentication_type = "AWS_IAM" 98 | 99 | # Enable active tracing 100 | xray_enabled = true 101 | } 102 | ``` 103 | 104 | ## See also 105 | 106 | * [Serverless Lens: Distributed Tracing](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/distributed-tracing.html) 107 | * [Tracing with AWS X-Ray](https://docs.aws.amazon.com/appsync/latest/devguide/x-ray-tracing.html) -------------------------------------------------------------------------------- /docs/rules/lambda/end_of_life_runtime.md: -------------------------------------------------------------------------------- 1 | # Lambda End-of-life Runtime 2 | 3 | __Level__: Error 4 | {: class="badge badge-red" } 5 | 6 | __Initial version__: 0.1.7 7 | {: class="badge badge-blue" } 8 | 9 | __cfn-lint__: E2531 10 | {: class="badge" } 11 | 12 | __tflint__: aws_lambda_function_eol_runtime 13 | {: class="badge" } 14 | 15 | Managed Lambda runtimes for .zip file archives are built around a combination of operating system, programming language, and software libraries that are subject to maintenance and security updates. When security updates are no longer available for a component of a runtime, Lambda deprecates the runtime. 16 | 17 | !!! info 18 | 19 | This rule is implemented natively in `cfn-lint` as rule number __E2531__. 20 | 21 | ## Implementations 22 | 23 | === "CDK" 24 | 25 | ```typescript 26 | import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; 27 | 28 | export class MyStack extends cdk.Stack { 29 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 30 | super(scope, id, props); 31 | 32 | const myFunction = new Function( 33 | scope, 'MyFunction', 34 | { 35 | code: Code.fromAsset('src/hello/'), 36 | handler: 'main.handler', 37 | // Select a runtime that is not deprecated 38 | runtime: Runtime.PYTHON_3_8, 39 | } 40 | ); 41 | } 42 | } 43 | ``` 44 | 45 | === "CloudFormation (JSON)" 46 | 47 | ```json 48 | { 49 | "Resources": { 50 | "MyFunction": { 51 | "Type": "AWS::Serverless::Function", 52 | "Properties": { 53 | "CodeUri": ".", 54 | // Select a runtime that is not deprecated 55 | "Runtime": "python3.8", 56 | "Handler": "main.handler" 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | === "CloudFormation (YAML)" 64 | 65 | ```yaml 66 | Resources: 67 | MyFunction: 68 | Type: AWS::Serverless::Function 69 | Properties: 70 | CodeUri: . 71 | # Select a runtime that is not deprecated 72 | Runtime: python3.12 73 | Handler: main.handler 74 | ``` 75 | 76 | === "Serverless Framework" 77 | 78 | ```yaml 79 | provider: 80 | name: aws 81 | # Select a runtime that is not deprecated 82 | runtime: nodejs14.x 83 | 84 | functions: 85 | hello: 86 | handler: handler.hello 87 | ``` 88 | 89 | === "Terraform" 90 | 91 | ```tf 92 | resource "aws_lambda_function" "this" { 93 | function_name = "my-function" 94 | # Select a runtime that is not deprecated 95 | runtime = "python3.8" 96 | handler = "main.handler" 97 | filename = "function.zip" 98 | } 99 | ``` 100 | 101 | ## See also 102 | 103 | * [Runtime support policy](https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html) -------------------------------------------------------------------------------- /docs/rules/lambda/tracing.md: -------------------------------------------------------------------------------- 1 | # Lambda Tracing 2 | 3 | __Level__: Warning 4 | {: class="badge badge-yellow" } 5 | 6 | __Initial version__: 0.1.3 7 | {: class="badge badge-blue" } 8 | 9 | __cfn-lint__: WS1000 10 | {: class="badge" } 11 | 12 | __tflint__: aws_lambda_function_tracing_rule 13 | {: class="badge" } 14 | 15 | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. 16 | 17 | ## Why is this a warning? 18 | 19 | You might use [third party solutions](https://aws.amazon.com/lambda/partners/) for monitoring serverless applications. If this is the case, enabling tracing for your AWS Lambda functions might be optional. Refer to the documentation of your monitoring solutions to see if you should enable AWS X-Ray tracing or not. 20 | 21 | ## Implementations 22 | 23 | === "CDK" 24 | 25 | ```typescript 26 | import { Code, Function, Runtime, Tracing } from '@aws-cdk/aws-lambda'; 27 | 28 | export class MyStack extends cdk.Stack { 29 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 30 | super(scope, id, props); 31 | 32 | const myFunction = new Function( 33 | scope, 'MyFunction', 34 | { 35 | code: Code.fromAsset('src/hello/'), 36 | handler: 'main.handler', 37 | runtime: Runtime.PYTHON_3_8, 38 | // Enable active tracing 39 | tracing: Tracing.ACTIVE, 40 | } 41 | ); 42 | } 43 | } 44 | ``` 45 | 46 | === "CloudFormation (JSON)" 47 | 48 | ```json 49 | { 50 | "Resources": { 51 | "MyFunction": { 52 | "Type": "AWS::Serverless::Function", 53 | "Properties": { 54 | // Required properties 55 | "CodeUri": ".", 56 | "Runtime": "python3.8", 57 | "Handler": "main.handler", 58 | 59 | // Enable active tracing 60 | "Tracing": "Active" 61 | } 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | === "CloudFormation (YAML)" 68 | 69 | ```yaml 70 | Resources: 71 | MyFunction: 72 | Type: AWS::Serverless::Function 73 | Properties: 74 | # Required properties 75 | CodeUri: . 76 | Runtime: python3.12 77 | Handler: main.handler 78 | 79 | # Enable active tracing 80 | Tracing: Active 81 | ``` 82 | 83 | === "Serverless Framework" 84 | 85 | ```yaml 86 | provider: 87 | tracing: 88 | # Enable active tracing for Lambda functions 89 | lambda: true 90 | 91 | functions: 92 | hello: 93 | handler: handler.hello 94 | ``` 95 | 96 | === "Terraform" 97 | 98 | ```tf 99 | resource "aws_lambda_function" "this" { 100 | function_name = "my-function" 101 | runtime = "python3.8" 102 | handler = "main.handler" 103 | filename = "function.zip" 104 | 105 | # Enable active tracing 106 | tracing_config { 107 | mode = "Active" 108 | } 109 | } 110 | ``` 111 | 112 | ## See also 113 | 114 | * [Serverless Lens: Distributed Tracing](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/distributed-tracing.html) 115 | * [Using AWS Lambda with X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html) 116 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | .badge { 2 | display: inline-block; 3 | font-size: 0.65rem; 4 | padding: 0px 6px; 5 | margin: 0px; 6 | 7 | color: #ffffff; 8 | 9 | border-radius: 3px; 10 | 11 | background-color: #78909C; 12 | } 13 | 14 | .badge-red { 15 | background-color: #e53935; 16 | } 17 | .badge-yellow { 18 | background-color: #FFB300; 19 | } 20 | .badge-green { 21 | background-color: #43A047; 22 | } 23 | .badge-blue { 24 | background-color: #039BE5; 25 | } -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | DIRECTORIES = $(wildcard */) 2 | 3 | test: $(patsubst %,test-%, $(DIRECTORIES)) 4 | 5 | test-%: 6 | $(MAKE) -C $* test-example -------------------------------------------------------------------------------- /examples/cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .env 6 | .venv 7 | *.egg-info 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | 13 | template.yaml -------------------------------------------------------------------------------- /examples/cdk/Makefile: -------------------------------------------------------------------------------- 1 | test-example: 2 | cdk synth > template.yaml 3 | PYTHONPATH="../../cfn-lint-serverless/:$$PYTHONPATH" cfn-lint template.yaml -a cfn_lint_serverless.rules -i W3005,W2001 -------------------------------------------------------------------------------- /examples/cdk/README.md: -------------------------------------------------------------------------------- 1 | 2 | CDK Example 3 | =========== 4 | 5 | Example on how to use cfn-lint-serverless with [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html). 6 | 7 | Usage 8 | ----- 9 | 10 | ```bash 11 | # Generate CloudFormation templates 12 | cdk synth > template.yaml 13 | 14 | # Run cfn-lint against the CloudFormation template 15 | cfn-lint template.yaml -a cfn_lint_serverless.rules 16 | ``` -------------------------------------------------------------------------------- /examples/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | from aws_cdk import core as cdk 5 | 6 | # For consistency with TypeScript code, `cdk` is the preferred import name for 7 | # the CDK's core module. The following line also imports it as `core` for use 8 | # with examples from the CDK Developer's Guide, which are in the process of 9 | # being updated to use `cdk`. You may delete this import if you don't need it. 10 | from aws_cdk import core 11 | 12 | from cdk.cdk_stack import CdkStack 13 | 14 | 15 | app = core.App() 16 | CdkStack(app, "CdkStack", 17 | # If you don't specify 'env', this stack will be environment-agnostic. 18 | # Account/Region-dependent features and context lookups will not work, 19 | # but a single synthesized template can be deployed anywhere. 20 | 21 | # Uncomment the next line to specialize this stack for the AWS Account 22 | # and Region that are implied by the current CLI configuration. 23 | 24 | #env=core.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), 25 | 26 | # Uncomment the next line if you know exactly what Account and Region you 27 | # want to deploy the stack to. */ 28 | 29 | #env=core.Environment(account='123456789012', region='us-east-1'), 30 | 31 | # For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html 32 | ) 33 | 34 | app.synth() 35 | -------------------------------------------------------------------------------- /examples/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:enableStackNameDuplicates": "true", 6 | "aws-cdk:enableDiffNoFail": "true", 7 | "@aws-cdk/core:stackRelativeExports": "true", 8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, 15 | "@aws-cdk/aws-lambda:recognizeVersionProps": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cdk/cdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/serverless-rules/a827a1a5b99655cc6df458ac7bdab4bf5aba25aa/examples/cdk/cdk/__init__.py -------------------------------------------------------------------------------- /examples/cdk/cdk/cdk_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import core as cdk 2 | from aws_cdk import aws_apigateway as apigw 3 | from aws_cdk import aws_lambda as lambda_ 4 | from aws_cdk import aws_logs as logs 5 | 6 | 7 | class CdkStack(cdk.Stack): 8 | 9 | def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: 10 | super().__init__(scope, construct_id, **kwargs) 11 | 12 | # Lambda function 13 | 14 | hello_function = lambda_.Function( 15 | self, "hello-function", 16 | code=lambda_.Code.from_asset("src/hello/"), 17 | handler="main.handler", 18 | runtime=lambda_.Runtime.PYTHON_3_8, 19 | tracing=lambda_.Tracing.ACTIVE 20 | ) 21 | 22 | logs.LogGroup( 23 | self, "hello-logs", 24 | log_group_name=f"/aws/lambda/{hello_function.function_name}", 25 | retention=logs.RetentionDays.ONE_WEEK 26 | ) 27 | 28 | # API Gateway 29 | 30 | api_logs = logs.LogGroup( 31 | self, "hello-api-logs", 32 | retention=logs.RetentionDays.ONE_WEEK 33 | ) 34 | 35 | api = apigw.RestApi( 36 | self, "hello-api", 37 | deploy_options=apigw.StageOptions( 38 | access_log_destination=apigw.LogGroupLogDestination(api_logs), 39 | access_log_format=apigw.AccessLogFormat.json_with_standard_fields( 40 | caller=True, 41 | http_method=True, 42 | ip=True, 43 | protocol=True, 44 | request_time=True, 45 | resource_path=True, 46 | response_length=True, 47 | status=True, 48 | user=True 49 | ), 50 | throttling_burst_limit=1000, 51 | throttling_rate_limit=10, 52 | tracing_enabled=True 53 | ) 54 | ) 55 | 56 | hello_integration = apigw.LambdaIntegration(hello_function, proxy=True) 57 | api.root.add_method( 58 | "GET", 59 | hello_integration 60 | ) -------------------------------------------------------------------------------- /examples/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | 3 | aws-cdk.aws_apigateway 4 | aws-cdk.aws_lambda 5 | aws-cdk.aws_logs -------------------------------------------------------------------------------- /examples/cdk/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | with open("README.md") as fp: 5 | long_description = fp.read() 6 | 7 | 8 | setuptools.setup( 9 | name="cdk", 10 | version="0.0.1", 11 | 12 | description="An empty CDK Python app", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | 16 | author="author", 17 | 18 | package_dir={"": "cdk"}, 19 | packages=setuptools.find_packages(where="cdk"), 20 | 21 | install_requires=[ 22 | "aws-cdk.core==1.204.0", 23 | ], 24 | 25 | python_requires=">=3.9", 26 | 27 | classifiers=[ 28 | "Development Status :: 4 - Beta", 29 | 30 | "Intended Audience :: Developers", 31 | 32 | "Programming Language :: JavaScript", 33 | "Programming Language :: Python :: 3 :: Only", 34 | "Programming Language :: Python :: 3.9", 35 | "Programming Language :: Python :: 3.10", 36 | "Programming Language :: Python :: 3.11", 37 | 38 | "Topic :: Software Development :: Code Generators", 39 | "Topic :: Utilities", 40 | 41 | "Typing :: Typed", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /examples/cdk/source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /examples/cdk/src/hello/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def hello(event, context): 5 | body = { 6 | "message": "Hello, world!", 7 | "input": event, 8 | } 9 | 10 | response = {"statusCode": 200, "body": json.dumps(body)} 11 | 12 | return response 13 | -------------------------------------------------------------------------------- /examples/sam/Makefile: -------------------------------------------------------------------------------- 1 | test-example: 2 | PYTHONPATH="../../cfn-lint-serverless/:$$PYTHONPATH" cfn-lint template.yaml -a cfn_lint_serverless.rules 3 | -------------------------------------------------------------------------------- /examples/sam/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Globals: 5 | Api: 6 | AccessLogSetting: 7 | DestinationArn: "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group" 8 | Format: | 9 | { 10 | "stage" : "$context.stage", 11 | "request_id" : "$context.requestId", 12 | "api_id" : "$context.apiId", 13 | "resource_path" : "$context.resourcePath", 14 | "resource_id" : "$context.resourceId", 15 | "http_method" : "$context.httpMethod", 16 | "source_ip" : "$context.identity.sourceIp", 17 | "user-agent" : "$context.identity.userAgent", 18 | "account_id" : "$context.identity.accountId", 19 | "api_key" : "$context.identity.apiKey", 20 | "caller" : "$context.identity.caller", 21 | "user" : "$context.identity.user", 22 | "user_arn" : "$context.identity.userArn", 23 | "integration_latency": $context.integration.latency 24 | } 25 | MethodSettings: 26 | - HttpMethod: "*" 27 | ResourcePath: "/*" 28 | ThrottlingBurstLimit: 1000 29 | ThrottlingRateLimit: 10 30 | TracingEnabled: true 31 | Function: 32 | Runtime: python3.12 33 | Tracing: Active 34 | 35 | Resources: 36 | Function: 37 | Type: AWS::Serverless::Function 38 | Properties: 39 | CodeUri: . 40 | Handler: main.handler 41 | Events: 42 | Api: 43 | Type: Api 44 | Properties: 45 | Method: GET 46 | Path: / 47 | 48 | LogGroup: 49 | Type: AWS::Logs::LogGroup 50 | Properties: 51 | LogGroupName: !Sub "/aws/lambda/${Function}" 52 | RetentionInDays: 7 -------------------------------------------------------------------------------- /examples/serverless-framework/.gitignore: -------------------------------------------------------------------------------- 1 | .serverless -------------------------------------------------------------------------------- /examples/serverless-framework/Makefile: -------------------------------------------------------------------------------- 1 | test-example: 2 | sls package 3 | PYTHONPATH="../../cfn-lint-serverless/:$$PYTHONPATH" cfn-lint .serverless/cloudformation-template-update-stack.json -a cfn_lint_serverless.rules -------------------------------------------------------------------------------- /examples/serverless-framework/README.md: -------------------------------------------------------------------------------- 1 | Serverless Framework Example 2 | ============================ 3 | 4 | Example on how to use cfn-lint-serverless with the [Serverless Framework](https://www.serverless.com/). 5 | 6 | Usage 7 | ----- 8 | 9 | ```bash 10 | # Generate CloudFormation templates 11 | sls package 12 | 13 | # Run cfn-lint against the CloudFormation template 14 | cfn-lint .serverless/cloudformation-template-update-stack.json -a cfn_lint_serverless.rules 15 | ``` -------------------------------------------------------------------------------- /examples/serverless-framework/handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def hello(event, context): 5 | body = { 6 | "message": "Hello, world!", 7 | "input": event, 8 | } 9 | 10 | response = {"statusCode": 200, "body": json.dumps(body)} 11 | 12 | return response 13 | -------------------------------------------------------------------------------- /examples/serverless-framework/serverless.yml: -------------------------------------------------------------------------------- 1 | service: aws-python-rest-api-project 2 | 3 | frameworkVersion: '4' 4 | 5 | provider: 6 | name: aws 7 | Runtime: python3.12 8 | lambdaHashingVersion: '20201221' 9 | logRetentionInDays: 14 10 | tracing: 11 | lambda: true 12 | 13 | functions: 14 | hello: 15 | handler: handler.hello 16 | events: 17 | - http: 18 | path: / 19 | method: get 20 | -------------------------------------------------------------------------------- /examples/tflint/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 12 | # password, private keys, and other secrets. These should not be part of version 13 | # control as they are data points which are potentially sensitive and subject 14 | # to change depending on the environment. 15 | # 16 | *.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | 36 | .tflint.d -------------------------------------------------------------------------------- /examples/tflint/.tflint.hcl: -------------------------------------------------------------------------------- 1 | plugin "aws" { 2 | enabled = true 3 | } 4 | 5 | plugin "aws-serverless" { 6 | enabled = true 7 | # Uncomment those lines if you are using tflint 0.29 or later 8 | # source = "github.com/awslabs/serverless-rules" 9 | # version = "0.3.3" 10 | } -------------------------------------------------------------------------------- /examples/tflint/Makefile: -------------------------------------------------------------------------------- 1 | TFLINT_FOLDER = "../../tflint-ruleset-aws-serverless" 2 | TFLINT_PLUGINS_FOLDER = ".tflint.d/plugins" 3 | TFLINT_BINARY = "tflint-ruleset-aws-serverless" 4 | 5 | test-example: 6 | $(MAKE) -C $(TFLINT_FOLDER) build 7 | mkdir -p $(TFLINT_PLUGINS_FOLDER) 8 | mv -f $(TFLINT_FOLDER)/$(TFLINT_BINARY) $(TFLINT_PLUGINS_FOLDER)/$(TFLINT_BINARY) 9 | tflint -------------------------------------------------------------------------------- /examples/tflint/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "5.40.0" 6 | } 7 | 8 | random = { 9 | source = "hashicorp/random" 10 | version = "3.7.1" 11 | } 12 | } 13 | } 14 | 15 | resource "random_pet" "this" { 16 | length = 2 17 | } 18 | 19 | resource "aws_iam_role" "lambda_role" { 20 | name = "${random_pet.this.id}-lambda-role" 21 | 22 | assume_role_policy = jsonencode({ 23 | Version = "2012-10-17" 24 | Statement = [ 25 | { 26 | Action = "sts:AssumeRole" 27 | Effect = "Allow" 28 | Principal = { 29 | Service = "lambda.amazonaws.com" 30 | } 31 | } 32 | ] 33 | }) 34 | } 35 | 36 | resource "aws_lambda_function" "this" { 37 | function_name = "${random_pet.this.id}-function" 38 | 39 | runtime = "python3.9" 40 | handler = "main.handler" 41 | role = aws_iam_role.lambda_role.arn 42 | 43 | filename = "function.zip" 44 | 45 | tracing_config { 46 | mode = "Active" 47 | } 48 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Serverless Rules 2 | site_description: Serverless Rules for checking infrastructure-as-code templates against serverless recommended practices. 3 | site_author: Amazon Web Services 4 | repo_url: https://github.com/awslabs/serverless-rules 5 | repo_name: awslabs/serverless-rules 6 | edit_uri: edit/develop/docs 7 | 8 | nav: 9 | - Homepage: index.md 10 | - Rules: rules/index.md 11 | - Usage guides: 12 | - "With cfn-lint": cfn-lint.md 13 | - "With tflint": tflint.md 14 | - Contributing: 15 | - "Creating rules": contributing/create_rule.md 16 | 17 | theme: 18 | name: material 19 | palette: 20 | scheme: default 21 | primary: teal 22 | accent: deep orange 23 | logo: images/aws-logo-light.svg 24 | favicon: images/aws-logo-light.svg 25 | features: 26 | - navigation.sections 27 | 28 | plugins: 29 | - search 30 | 31 | markdown_extensions: 32 | - admonition 33 | - attr_list 34 | - pymdownx.details 35 | - pymdownx.highlight: 36 | linenums: true 37 | - pymdownx.superfences 38 | - pymdownx.tabbed 39 | - toc: 40 | permalink: true 41 | 42 | extra_css: 43 | - stylesheets/extra.css 44 | 45 | copyright: Copyright © 2021 Amazon Web Services -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # Exclude files/directories from analysis 2 | exclude = [ 3 | ".bzr", 4 | ".direnv", 5 | ".eggs", 6 | ".git", 7 | ".git-rewrite", 8 | ".hg", 9 | ".ipynb_checkpoints", 10 | ".mypy_cache", 11 | ".nox", 12 | ".pants.d", 13 | ".pyenv", 14 | ".pytest_cache", 15 | ".pytype", 16 | ".ruff_cache", 17 | ".svn", 18 | ".tox", 19 | ".venv", 20 | ".vscode", 21 | "__pypackages__", 22 | "_build", 23 | "buck-out", 24 | "build", 25 | "dist", 26 | "node_modules", 27 | "site-packages", 28 | "venv", 29 | ] 30 | 31 | # Same as Black. 32 | line-length = 120 33 | indent-width = 4 34 | 35 | fix = true 36 | 37 | [format] 38 | # Like Black, use double quotes for strings. 39 | quote-style = "double" 40 | 41 | # Like Black, indent with spaces, rather than tabs. 42 | indent-style = "space" 43 | 44 | # Like Black, respect magic trailing commas. 45 | skip-magic-trailing-comma = false 46 | 47 | # Like Black, automatically detect the appropriate line ending. 48 | line-ending = "auto" 49 | 50 | 51 | [lint] 52 | # 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults. 53 | select = ["E4", "E7", "E9", "F", "B"] 54 | 55 | # 2. Avoid enforcing line-length violations (`E501`) 56 | ignore = ["E501"] 57 | 58 | # 3. Avoid trying to fix flake8-bugbear (`B`) violations. 59 | unfixable = ["B"] 60 | 61 | # 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories. 62 | [lint.per-file-ignores] 63 | "__init__.py" = ["E402"] 64 | "**/{tests,docs,tools}/*" = ["E402"] -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | env: 4 | - CGO_ENABLED=0 5 | builds: 6 | - goos: 7 | - linux 8 | - darwin 9 | - windows 10 | goarch: 11 | - "386" 12 | - amd64 13 | - arm 14 | - arm64 15 | binary: tflint-ruleset-aws-serverless 16 | archives: 17 | - id: zip 18 | name_template: "tflint-ruleset-aws-serverless_{{ .Os }}_{{ .Arch }}" 19 | format: zip 20 | files: 21 | - none* 22 | changelog: 23 | sort: asc 24 | use: github 25 | checksum: 26 | name_template: "checksums.txt" 27 | release: 28 | github: 29 | owner: awslabs 30 | name: serverless-rules 31 | name_template: "Version {{ .Version }}" 32 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/Makefile: -------------------------------------------------------------------------------- 1 | dev: 2 | @echo "Nothing to do here" 3 | 4 | format: 5 | go fmt 6 | 7 | lint: format 8 | 9 | test: 10 | go test ./... 11 | 12 | pr: lint test check-rules 13 | 14 | build: 15 | go build 16 | 17 | install: build 18 | mkdir -p ~/.tflint.d/plugins 19 | mv ./tflint-ruleset-template ~/.tflint.d/plugins 20 | 21 | # Check if the number of rules in provider.go corresponds to the total number of rules 22 | check-rules: 23 | ifneq ($(shell grep -E "^\s+New[A-Za-z0-9]+\(\)," rules/provider.go | wc -l), $(shell grep -E "^func New[A-Za-z0-9]+\(\)" rules/* | wc -l)) 24 | $(error Mismatch in rule count ($(shell grep -E "^\s+New[A-Za-z0-9]+\(\)," rules/provider.go | wc -l) vs $(shell grep -E "^func New[A-Za-z0-9]+\(\)" rules/* | wc -l)) - check rules/provider.go) 25 | else 26 | $(info Match in rule count) 27 | endif 28 | 29 | # Create a new rule 30 | add-rule: 31 | ifeq ($(RULE_NAME),) 32 | $(error Missing RULE_NAME environment variable) 33 | endif 34 | ifeq ($(RULE_NAME_CC),) 35 | $(error Missing RULE_NAME_CC environment variable) 36 | endif 37 | gomplate -f templates/rule.go.tmpl -o rules/$(RULE_NAME).go 38 | gomplate -f templates/rule_test.go.tmpl -o rules/$(RULE_NAME)_test.go 39 | 40 | # List all the rules for provider.go 41 | list-rules: 42 | grep -E 'func New[^(]+\(\)' rules/* | sed 's/func //' | sed 's/$$/,/' | sort -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/awslabs/serverless-rules/tflint-ruleset-aws-serverless 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/hashicorp/hcl/v2 v2.23.0 7 | github.com/terraform-linters/tflint-plugin-sdk v0.21.0 8 | ) 9 | 10 | require ( 11 | github.com/agext/levenshtein v1.2.1 // indirect 12 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 13 | github.com/fatih/color v1.13.0 // indirect 14 | github.com/golang/protobuf v1.5.4 // indirect 15 | github.com/google/go-cmp v0.6.0 // indirect 16 | github.com/hashicorp/go-hclog v1.6.3 // indirect 17 | github.com/hashicorp/go-plugin v1.6.1 // indirect 18 | github.com/hashicorp/go-version v1.7.0 // indirect 19 | github.com/hashicorp/yamux v0.1.1 // indirect 20 | github.com/mattn/go-colorable v0.1.12 // indirect 21 | github.com/mattn/go-isatty v0.0.14 // indirect 22 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect 23 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect 24 | github.com/oklog/run v1.0.0 // indirect 25 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 26 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 27 | github.com/zclconf/go-cty v1.15.0 // indirect 28 | golang.org/x/mod v0.19.0 // indirect 29 | golang.org/x/net v0.27.0 // indirect 30 | golang.org/x/sync v0.7.0 // indirect 31 | golang.org/x/sys v0.22.0 // indirect 32 | golang.org/x/text v0.16.0 // indirect 33 | golang.org/x/tools v0.23.0 // indirect 34 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect 35 | google.golang.org/grpc v1.65.0 // indirect 36 | google.golang.org/protobuf v1.34.2 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/awslabs/serverless-rules/tflint-ruleset-aws-serverless/rules" 5 | "github.com/terraform-linters/tflint-plugin-sdk/plugin" 6 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 7 | ) 8 | 9 | func main() { 10 | plugin.Serve(&plugin.ServeOpts{ 11 | RuleSet: &tflint.BuiltinRuleSet{ 12 | Name: "aws-serverless", 13 | Version: "0.3.3", 14 | Rules: rules.Rules, 15 | }, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_method_settings_throttling_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsAPIGatewayMethodSettingsThrottlingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing settings block is invalid", 18 | Content: ` 19 | resource "aws_api_gateway_method_settings" "missing" { 20 | method_path = "*/*" 21 | }`, 22 | Expected: helper.Issues{ 23 | { 24 | Rule: NewAwsAPIGatewayMethodSettingsThrottlingRule(), 25 | Message: "\"settings\" block is required for default method settings", 26 | Range: hcl.Range{ 27 | Filename: "resource.tf", 28 | Start: hcl.Pos{Line: 2, Column: 1}, 29 | End: hcl.Pos{Line: 2, Column: 53}, 30 | }, 31 | }, 32 | }, 33 | }, 34 | { 35 | Name: "empty settings block is invalid", 36 | Content: ` 37 | resource "aws_api_gateway_method_settings" "empty" { 38 | method_path = "*/*" 39 | settings {} 40 | }`, 41 | Expected: helper.Issues{ 42 | { 43 | Rule: NewAwsAPIGatewayMethodSettingsThrottlingRule(), 44 | Message: "\"throttling_burst_limit\" is required for default method settings", 45 | Range: hcl.Range{ 46 | Filename: "resource.tf", 47 | Start: hcl.Pos{Line: 4, Column: 2}, 48 | End: hcl.Pos{Line: 4, Column: 10}, 49 | }, 50 | }, 51 | { 52 | Rule: NewAwsAPIGatewayMethodSettingsThrottlingRule(), 53 | Message: "\"throttling_rate_limit\" is required for default method settings", 54 | Range: hcl.Range{ 55 | Filename: "resource.tf", 56 | Start: hcl.Pos{Line: 4, Column: 2}, 57 | End: hcl.Pos{Line: 4, Column: 10}, 58 | }, 59 | }, 60 | }, 61 | }, 62 | { 63 | Name: "missing throttling rate limit is invalid", 64 | Content: ` 65 | resource "aws_api_gateway_method_settings" "missingrate" { 66 | method_path = "*/*" 67 | settings { 68 | throttling_burst_limit = 1000 69 | } 70 | }`, 71 | Expected: helper.Issues{ 72 | { 73 | Rule: NewAwsAPIGatewayMethodSettingsThrottlingRule(), 74 | Message: "\"throttling_rate_limit\" is required for default method settings", 75 | Range: hcl.Range{ 76 | Filename: "resource.tf", 77 | Start: hcl.Pos{Line: 4, Column: 2}, 78 | End: hcl.Pos{Line: 4, Column: 10}, 79 | }, 80 | }, 81 | }, 82 | }, 83 | { 84 | Name: "valid", 85 | Content: ` 86 | resource "aws_api_gateway_method_settings" "valid" { 87 | method_path = "*/*" 88 | settings { 89 | throttling_burst_limit = 1000 90 | throttling_rate_limit = 100 91 | } 92 | }`, 93 | Expected: helper.Issues{}, 94 | }, 95 | } 96 | 97 | rule := NewAwsAPIGatewayMethodSettingsThrottlingRule() 98 | 99 | for _, tc := range cases { 100 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 101 | 102 | if err := rule.Check(runner); err != nil { 103 | t.Fatalf("Unexpected error occurred: %s", err) 104 | } 105 | 106 | helper.AssertIssues(t, tc.Expected, runner.Issues) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_stage_logging.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | // AwsAPIGatewayStageLoggingRule checks whether "aws_api_gateway_stage" has Logging enabled. 11 | type AwsAPIGatewayStageLoggingRule struct { 12 | resourceType string 13 | blockName string 14 | tflint.DefaultRule 15 | } 16 | 17 | // NewAwsAPIGatewayStageLoggingRule returns new rule 18 | func NewAwsAPIGatewayStageLoggingRule() *AwsAPIGatewayStageLoggingRule { 19 | return &AwsAPIGatewayStageLoggingRule{ 20 | resourceType: "aws_api_gateway_stage", 21 | blockName: "access_log_settings", 22 | } 23 | } 24 | 25 | // Name returns the rule name 26 | func (r *AwsAPIGatewayStageLoggingRule) Name() string { 27 | return "aws_apigateway_stage_logging_rule" 28 | } 29 | 30 | // Enabled returns whether the rule is enabled by default 31 | func (r *AwsAPIGatewayStageLoggingRule) Enabled() bool { 32 | return true 33 | } 34 | 35 | // Severity returns the rule severity 36 | func (r *AwsAPIGatewayStageLoggingRule) Severity() tflint.Severity { 37 | return tflint.ERROR 38 | } 39 | 40 | // Link returns the rule reference link 41 | func (r *AwsAPIGatewayStageLoggingRule) Link() string { 42 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/logging/" 43 | } 44 | 45 | // Metadata returns the rule metadata 46 | func (r *AwsAPIGatewayStageLoggingRule) Metadata() interface{} { 47 | return struct { 48 | Name string 49 | Severity tflint.Severity 50 | Link string 51 | }{ 52 | Name: r.Name(), 53 | Severity: r.Severity(), 54 | Link: r.Link(), 55 | } 56 | } 57 | 58 | // Check checks whether "aws_api_gateway_stage" has logging enabled 59 | func (r *AwsAPIGatewayStageLoggingRule) Check(runner tflint.Runner) error { 60 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 61 | Blocks: []hclext.BlockSchema{ 62 | { 63 | Type: r.blockName, 64 | Body: &hclext.BodySchema{}, 65 | }, 66 | }, 67 | }, nil) 68 | if err != nil { 69 | return fmt.Errorf("failed to get resource content: %w", err) 70 | } 71 | 72 | for _, resource := range resources.Blocks { 73 | blocks := resource.Body.Blocks.OfType(r.blockName) 74 | if len(blocks) == 0 { 75 | runner.EmitIssue( 76 | r, 77 | fmt.Sprintf("\"%s\" is not present.", r.blockName), 78 | resource.DefRange, 79 | ) 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_stage_logging_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsAPIGatewayStageLoggingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing access_log_settings is invalid", 18 | Content: ` 19 | resource "aws_api_gateway_stage" "missing" { 20 | }`, 21 | Expected: helper.Issues{ 22 | { 23 | Rule: NewAwsAPIGatewayStageLoggingRule(), 24 | Message: "\"access_log_settings\" is not present.", 25 | Range: hcl.Range{ 26 | Filename: "resource.tf", 27 | Start: hcl.Pos{Line: 2, Column: 1}, 28 | End: hcl.Pos{Line: 2, Column: 43}, 29 | }, 30 | }, 31 | }, 32 | }, 33 | { 34 | Name: "valid", 35 | Content: ` 36 | resource "aws_api_gateway_stage" "valid" { 37 | access_log_settings { 38 | destination_arn = "ARN" 39 | format = "FORMAT" 40 | } 41 | }`, 42 | Expected: helper.Issues{}, 43 | }, 44 | } 45 | 46 | rule := NewAwsAPIGatewayStageLoggingRule() 47 | 48 | for _, tc := range cases { 49 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 50 | 51 | if err := rule.Check(runner); err != nil { 52 | t.Fatalf("Unexpected error occurred: %s", err) 53 | } 54 | 55 | helper.AssertIssues(t, tc.Expected, runner.Issues) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_stage_structured_logging.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | 8 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 9 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 10 | ) 11 | 12 | // AwsApigatewayStageStructuredLogging checks if API Gateway logging format is in JSON 13 | type AwsApigatewayStageStructuredLoggingRule struct { 14 | tflint.DefaultRule 15 | resourceType string 16 | blockName string 17 | attributeName string 18 | } 19 | 20 | // NewAwsApigatewayStageStructuredLoggingRule returns new rule with default attributes 21 | func NewAwsApigatewayStageStructuredLoggingRule() *AwsApigatewayStageStructuredLoggingRule { 22 | return &AwsApigatewayStageStructuredLoggingRule{ 23 | resourceType: "aws_api_gateway_stage", 24 | blockName: "access_log_settings", 25 | attributeName: "format", 26 | } 27 | } 28 | 29 | // Name returns the rule name 30 | func (r *AwsApigatewayStageStructuredLoggingRule) Name() string { 31 | return "aws_api_gateway_stage_structured_logging" 32 | } 33 | 34 | // Enabled returns whether the rule is enabled by default 35 | func (r *AwsApigatewayStageStructuredLoggingRule) Enabled() bool { 36 | return true 37 | } 38 | 39 | // Severity returns the rule severity 40 | func (r *AwsApigatewayStageStructuredLoggingRule) Severity() tflint.Severity { 41 | return tflint.WARNING 42 | } 43 | 44 | // Link returns the rule reference link 45 | func (r *AwsApigatewayStageStructuredLoggingRule) Link() string { 46 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/structured_logging/" 47 | } 48 | 49 | // Check checks if API Gateway logging format is in JSON 50 | func (r *AwsApigatewayStageStructuredLoggingRule) Check(runner tflint.Runner) error { 51 | // Regexp to substitute all $context. variables 52 | re := regexp.MustCompile(`\$context\.[a-zA-Z\.]+`) 53 | 54 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 55 | Blocks: []hclext.BlockSchema{ 56 | { 57 | Type: r.blockName, 58 | Body: &hclext.BodySchema{ 59 | Attributes: []hclext.AttributeSchema{ 60 | {Name: r.attributeName}, 61 | }, 62 | }, 63 | }, 64 | }, 65 | }, nil) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | for _, resource := range resources.Blocks { 71 | blocks := resource.Body.Blocks.OfType(r.blockName) 72 | if len(blocks) == 0 { 73 | continue 74 | } 75 | 76 | attribute, ok := blocks[0].Body.Attributes[r.attributeName] 77 | if !ok { 78 | runner.EmitIssue( 79 | r, 80 | fmt.Sprintf("\"%s\" is not present.", r.attributeName), 81 | blocks[0].DefRange, 82 | ) 83 | continue 84 | } 85 | 86 | var attrValue string 87 | err := runner.EvaluateExpr(attribute.Expr, &attrValue, nil) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | attrValue = re.ReplaceAllLiteralString(attrValue, "4") 93 | 94 | var js map[string]interface{} 95 | if json.Unmarshal([]byte(attrValue), &js) != nil { 96 | runner.EmitIssue( 97 | r, 98 | fmt.Sprintf("\"%s\" is not valid JSON.", r.attributeName), 99 | attribute.Expr.Range(), 100 | ) 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_stage_tracing.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | // AwsAPIGatewayStageTracingRule checks whether "aws_api_gateway_stage" has tracing enabled. 11 | type AwsAPIGatewayStageTracingRule struct { 12 | tflint.DefaultRule 13 | resourceType string 14 | attributeName string 15 | } 16 | 17 | // NewAwsAPIGatewayStageTracingRule returns new rule 18 | func NewAwsAPIGatewayStageTracingRule() *AwsAPIGatewayStageTracingRule { 19 | return &AwsAPIGatewayStageTracingRule{ 20 | resourceType: "aws_api_gateway_stage", 21 | attributeName: "xray_tracing_enabled", 22 | } 23 | } 24 | 25 | // Name returns the rule name 26 | func (r *AwsAPIGatewayStageTracingRule) Name() string { 27 | return "aws_apigateway_stage_tracing_rule" 28 | } 29 | 30 | // Enabled returns whether the rule is enabled by default 31 | func (r *AwsAPIGatewayStageTracingRule) Enabled() bool { 32 | return true 33 | } 34 | 35 | // Severity returns the rule severity 36 | func (r *AwsAPIGatewayStageTracingRule) Severity() tflint.Severity { 37 | return tflint.WARNING 38 | } 39 | 40 | // Link returns the rule reference link 41 | func (r *AwsAPIGatewayStageTracingRule) Link() string { 42 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/tracing/" 43 | } 44 | 45 | // Check checks whether "aws_api_gateway_stage" has tracing enabled 46 | func (r *AwsAPIGatewayStageTracingRule) Check(runner tflint.Runner) error { 47 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 48 | Attributes: []hclext.AttributeSchema{ 49 | {Name: r.attributeName}, 50 | }, 51 | }, nil) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | for _, resource := range resources.Blocks { 57 | attribute, exists := resource.Body.Attributes[r.attributeName] 58 | if !exists { 59 | runner.EmitIssue( 60 | r, 61 | fmt.Sprintf("\"%s\" is not present.", r.attributeName), 62 | resource.DefRange, 63 | ) 64 | continue 65 | } 66 | 67 | var xrayTracingEnabled string 68 | err := runner.EvaluateExpr(attribute.Expr, &xrayTracingEnabled, nil) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | if xrayTracingEnabled != "true" { 74 | runner.EmitIssue( 75 | r, 76 | fmt.Sprintf("\"%s\" should be set to true.", r.attributeName), 77 | attribute.Expr.Range(), 78 | ) 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_api_gateway_stage_tracing_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsAPIGatewayStageTracingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "not present", 18 | Content: ` 19 | resource "aws_api_gateway_stage" "false" {}`, 20 | Expected: helper.Issues{ 21 | { 22 | Rule: NewAwsAPIGatewayStageTracingRule(), 23 | Message: "\"xray_tracing_enabled\" is not present.", 24 | Range: hcl.Range{ 25 | Filename: "resource.tf", 26 | Start: hcl.Pos{Line: 2, Column: 1}, 27 | End: hcl.Pos{Line: 2, Column: 41}, 28 | }, 29 | }, 30 | }, 31 | }, 32 | { 33 | Name: "false is invalid", 34 | Content: ` 35 | resource "aws_api_gateway_stage" "false" { 36 | xray_tracing_enabled = false 37 | }`, 38 | Expected: helper.Issues{ 39 | { 40 | Rule: NewAwsAPIGatewayStageTracingRule(), 41 | Message: "\"xray_tracing_enabled\" should be set to true.", 42 | Range: hcl.Range{ 43 | Filename: "resource.tf", 44 | Start: hcl.Pos{Line: 3, Column: 25}, 45 | End: hcl.Pos{Line: 3, Column: 30}, 46 | }, 47 | }, 48 | }, 49 | }, 50 | { 51 | Name: "true is valid", 52 | Content: ` 53 | resource "aws_api_gateway_stage" "true" { 54 | xray_tracing_enabled = true 55 | }`, 56 | Expected: helper.Issues{}, 57 | }, 58 | } 59 | 60 | rule := NewAwsAPIGatewayStageTracingRule() 61 | 62 | for _, tc := range cases { 63 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 64 | 65 | if err := rule.Check(runner); err != nil { 66 | t.Fatalf("Unexpected error occurred: %s", err) 67 | } 68 | 69 | helper.AssertIssues(t, tc.Expected, runner.Issues) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_apigatewayv2_stage_logging.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | // AwsAPIGatewayStageV2LoggingRule checks whether "aws_api_gateway_stage" has Logging enabled. 11 | type AwsAPIGatewayStageV2LoggingRule struct { 12 | tflint.DefaultRule 13 | resourceType string 14 | blockName string 15 | } 16 | 17 | // NewAwsAPIGatewayStageV2LoggingRule returns new rule 18 | func NewAwsAPIGatewayStageV2LoggingRule() *AwsAPIGatewayStageV2LoggingRule { 19 | return &AwsAPIGatewayStageV2LoggingRule{ 20 | resourceType: "aws_api_gatewayv2_stage", 21 | blockName: "access_log_settings", 22 | } 23 | } 24 | 25 | // Name returns the rule name 26 | func (r *AwsAPIGatewayStageV2LoggingRule) Name() string { 27 | return "aws_apigatewayv2_stage_logging_rule" 28 | } 29 | 30 | // Enabled returns whether the rule is enabled by default 31 | func (r *AwsAPIGatewayStageV2LoggingRule) Enabled() bool { 32 | return true 33 | } 34 | 35 | // Severity returns the rule severity 36 | func (r *AwsAPIGatewayStageV2LoggingRule) Severity() tflint.Severity { 37 | return tflint.ERROR 38 | } 39 | 40 | // Link returns the rule reference link 41 | func (r *AwsAPIGatewayStageV2LoggingRule) Link() string { 42 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/logging/" 43 | } 44 | 45 | // Check checks whether "aws_api_gateway_stage" has logging enabled 46 | func (r *AwsAPIGatewayStageV2LoggingRule) Check(runner tflint.Runner) error { 47 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 48 | Blocks: []hclext.BlockSchema{ 49 | { 50 | Type: r.blockName, 51 | }, 52 | }, 53 | }, nil) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | for _, resource := range resources.Blocks { 59 | blocks := resource.Body.Blocks.OfType(r.blockName) 60 | if len(blocks) == 0 { 61 | runner.EmitIssue( 62 | r, 63 | fmt.Sprintf("\"%s\" is not present.", r.blockName), 64 | resource.DefRange, 65 | ) 66 | } 67 | } 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_apigatewayv2_stage_logging_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsAPIGatewayStageV2LoggingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing access_log_settings is invalid", 18 | Content: ` 19 | resource "aws_api_gatewayv2_stage" "missing" { 20 | }`, 21 | Expected: helper.Issues{ 22 | { 23 | Rule: NewAwsAPIGatewayStageV2LoggingRule(), 24 | Message: "\"access_log_settings\" is not present.", 25 | Range: hcl.Range{ 26 | Filename: "resource.tf", 27 | Start: hcl.Pos{Line: 2, Column: 1}, 28 | End: hcl.Pos{Line: 2, Column: 45}, 29 | }, 30 | }, 31 | }, 32 | }, 33 | { 34 | Name: "valid", 35 | Content: ` 36 | resource "aws_api_gatewayv2_stage" "valid" { 37 | access_log_settings { 38 | destination_arn = "ARN" 39 | format = "FORMAT" 40 | } 41 | }`, 42 | Expected: helper.Issues{}, 43 | }, 44 | } 45 | 46 | rule := NewAwsAPIGatewayStageV2LoggingRule() 47 | 48 | for _, tc := range cases { 49 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 50 | 51 | if err := rule.Check(runner); err != nil { 52 | t.Fatalf("Unexpected error occurred: %s", err) 53 | } 54 | 55 | helper.AssertIssues(t, tc.Expected, runner.Issues) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_apigatewayv2_stage_structured_logging.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | 8 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 9 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 10 | ) 11 | 12 | // AwsApigatewayV2StageStructuredLogging checks if API Gateway logging format is in JSON 13 | type AwsApigatewayV2StageStructuredLoggingRule struct { 14 | tflint.DefaultRule 15 | resourceType string 16 | blockName string 17 | attributeName string 18 | } 19 | 20 | // NewAwsApigatewayV2StageStructuredLoggingRule returns new rule with default attributes 21 | func NewAwsApigatewayV2StageStructuredLoggingRule() *AwsApigatewayV2StageStructuredLoggingRule { 22 | return &AwsApigatewayV2StageStructuredLoggingRule{ 23 | resourceType: "aws_apigatewayv2_stage", 24 | blockName: "access_log_settings", 25 | attributeName: "format", 26 | } 27 | } 28 | 29 | // Name returns the rule name 30 | func (r *AwsApigatewayV2StageStructuredLoggingRule) Name() string { 31 | return "aws_apigatewayv2_stage_structured_logging" 32 | } 33 | 34 | // Enabled returns whether the rule is enabled by default 35 | func (r *AwsApigatewayV2StageStructuredLoggingRule) Enabled() bool { 36 | return true 37 | } 38 | 39 | // Severity returns the rule severity 40 | func (r *AwsApigatewayV2StageStructuredLoggingRule) Severity() tflint.Severity { 41 | return tflint.WARNING 42 | } 43 | 44 | // Link returns the rule reference link 45 | func (r *AwsApigatewayV2StageStructuredLoggingRule) Link() string { 46 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/structured_logging/" 47 | } 48 | 49 | // Check checks if API Gateway logging format is in JSON 50 | func (r *AwsApigatewayV2StageStructuredLoggingRule) Check(runner tflint.Runner) error { 51 | // Regexp to substitute all $context. variables 52 | re := regexp.MustCompile(`\$context\.[a-zA-Z\.]+`) 53 | 54 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 55 | Blocks: []hclext.BlockSchema{ 56 | { 57 | Type: r.blockName, 58 | Body: &hclext.BodySchema{ 59 | Attributes: []hclext.AttributeSchema{ 60 | {Name: r.attributeName}, 61 | }, 62 | }, 63 | }, 64 | }, 65 | }, nil) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | for _, resource := range resources.Blocks { 71 | blocks := resource.Body.Blocks.OfType(r.blockName) 72 | if len(blocks) == 0 { 73 | continue 74 | } 75 | 76 | attribute, ok := blocks[0].Body.Attributes[r.attributeName] 77 | if !ok { 78 | runner.EmitIssue( 79 | r, 80 | fmt.Sprintf("\"%s\" is not present.", r.attributeName), 81 | blocks[0].DefRange, 82 | ) 83 | continue 84 | } 85 | 86 | var attrValue string 87 | err := runner.EvaluateExpr(attribute.Expr, &attrValue, nil) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | attrValue = re.ReplaceAllLiteralString(attrValue, "4") 93 | 94 | var js map[string]interface{} 95 | if json.Unmarshal([]byte(attrValue), &js) != nil { 96 | runner.EmitIssue( 97 | r, 98 | fmt.Sprintf("\"%s\" is not valid JSON.", r.attributeName), 99 | attribute.Expr.Range(), 100 | ) 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_apigatewayv2_stage_throttling.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | // AwsApigatewayV2StageThrottlingRule checks whether "aws_apigatewayv2_stage" has default throttling values. 11 | type AwsApigatewayV2StageThrottlingRule struct { 12 | tflint.DefaultRule 13 | resourceType string 14 | blockName string 15 | burstAttributeName string 16 | rateAttributeName string 17 | } 18 | 19 | // NewAwsApigatewayV2StageThrottlingRule returns new rule 20 | func NewAwsApigatewayV2StageThrottlingRule() *AwsApigatewayV2StageThrottlingRule { 21 | return &AwsApigatewayV2StageThrottlingRule{ 22 | resourceType: "aws_apigatewayv2_stage", 23 | blockName: "default_route_settings", 24 | burstAttributeName: "throttling_burst_limit", 25 | rateAttributeName: "throttling_rate_limit", 26 | } 27 | } 28 | 29 | // Name returns the rule name 30 | func (r *AwsApigatewayV2StageThrottlingRule) Name() string { 31 | return "aws_apigatewayv2_stage_throttling_rule" 32 | } 33 | 34 | // Enabled returns whether the rule is enabled by default 35 | func (r *AwsApigatewayV2StageThrottlingRule) Enabled() bool { 36 | return true 37 | } 38 | 39 | // Severity returns the rule severity 40 | func (r *AwsApigatewayV2StageThrottlingRule) Severity() tflint.Severity { 41 | return tflint.ERROR 42 | } 43 | 44 | // Link returns the rule reference link 45 | func (r *AwsApigatewayV2StageThrottlingRule) Link() string { 46 | return "https://awslabs.github.io/serverless-rules/rules/api_gateway/default_throttling/" 47 | } 48 | 49 | // Check checks whether "aws_apigatewayv2_stage" has has default throttling values 50 | func (r *AwsApigatewayV2StageThrottlingRule) Check(runner tflint.Runner) error { 51 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 52 | Blocks: []hclext.BlockSchema{ 53 | { 54 | Type: r.blockName, 55 | Body: &hclext.BodySchema{ 56 | Attributes: []hclext.AttributeSchema{ 57 | {Name: r.burstAttributeName}, 58 | {Name: r.rateAttributeName}, 59 | }, 60 | }, 61 | }, 62 | }, 63 | }, nil) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | for _, resource := range resources.Blocks { 69 | // Check for block 70 | blocks := resource.Body.Blocks.OfType(r.blockName) 71 | if len(blocks) == 0 { 72 | runner.EmitIssue( 73 | r, 74 | fmt.Sprintf("\"%s\" is not present.", r.blockName), 75 | resource.DefRange, 76 | ) 77 | continue 78 | } 79 | 80 | // Check throttling limits 81 | _, burstOk := blocks[0].Body.Attributes[r.burstAttributeName] 82 | if !burstOk { 83 | runner.EmitIssue( 84 | r, 85 | fmt.Sprintf("\"%s\" is not present.", r.burstAttributeName), 86 | blocks[0].DefRange, 87 | ) 88 | } 89 | 90 | _, rateOk := blocks[0].Body.Attributes[r.rateAttributeName] 91 | if !rateOk { 92 | runner.EmitIssue( 93 | r, 94 | fmt.Sprintf("\"%s\" is not present.", r.rateAttributeName), 95 | blocks[0].DefRange, 96 | ) 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_apigatewayv2_stage_throttling_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsApigatewayV2StageThrottlingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing default_route_settings is invalid", 18 | Content: ` 19 | resource "aws_apigatewayv2_stage" "missing" { 20 | }`, 21 | Expected: helper.Issues{ 22 | { 23 | Rule: NewAwsApigatewayV2StageThrottlingRule(), 24 | Message: "\"default_route_settings\" is not present.", 25 | Range: hcl.Range{ 26 | Filename: "resource.tf", 27 | Start: hcl.Pos{Line: 2, Column: 1}, 28 | End: hcl.Pos{Line: 2, Column: 44}, 29 | }, 30 | }, 31 | }, 32 | }, 33 | { 34 | Name: "empty default_route_settings is invalid", 35 | Content: ` 36 | resource "aws_apigatewayv2_stage" "empty" { 37 | default_route_settings {} 38 | }`, 39 | Expected: helper.Issues{ 40 | { 41 | Rule: NewAwsApigatewayV2StageThrottlingRule(), 42 | Message: "\"throttling_burst_limit\" is not present.", 43 | Range: hcl.Range{ 44 | Filename: "resource.tf", 45 | Start: hcl.Pos{Line: 3, Column: 2}, 46 | End: hcl.Pos{Line: 3, Column: 24}, 47 | }, 48 | }, 49 | { 50 | Rule: NewAwsApigatewayV2StageThrottlingRule(), 51 | Message: "\"throttling_rate_limit\" is not present.", 52 | Range: hcl.Range{ 53 | Filename: "resource.tf", 54 | Start: hcl.Pos{Line: 3, Column: 2}, 55 | End: hcl.Pos{Line: 3, Column: 24}, 56 | }, 57 | }, 58 | }, 59 | }, 60 | { 61 | Name: "missing throttling_rate_limit is invalid", 62 | Content: ` 63 | resource "aws_apigatewayv2_stage" "missingrate" { 64 | default_route_settings { 65 | throttling_burst_limit = 1000 66 | } 67 | }`, 68 | Expected: helper.Issues{ 69 | { 70 | Rule: NewAwsApigatewayV2StageThrottlingRule(), 71 | Message: "\"throttling_rate_limit\" is not present.", 72 | Range: hcl.Range{ 73 | Filename: "resource.tf", 74 | Start: hcl.Pos{Line: 3, Column: 2}, 75 | End: hcl.Pos{Line: 3, Column: 24}, 76 | }, 77 | }, 78 | }, 79 | }, 80 | { 81 | Name: "valid", 82 | Content: ` 83 | resource "aws_apigatewayv2_stage" "valid" { 84 | default_route_settings { 85 | throttling_burst_limit = 1000 86 | throttling_rate_limit = 100 87 | } 88 | }`, 89 | Expected: helper.Issues{}, 90 | }, 91 | } 92 | 93 | rule := NewAwsApigatewayV2StageThrottlingRule() 94 | 95 | for _, tc := range cases { 96 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 97 | 98 | if err := rule.Check(runner); err != nil { 99 | t.Fatalf("Unexpected error occurred: %s", err) 100 | } 101 | 102 | helper.AssertIssues(t, tc.Expected, runner.Issues) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_appsync_graphql_api_tracing.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | type AwsAppsyncGraphqlAPITracingRule struct { 11 | tflint.DefaultRule 12 | resourceType string 13 | attributeName string 14 | } 15 | 16 | func NewAwsAppsyncGraphqlAPITracingRule() *AwsAppsyncGraphqlAPITracingRule { 17 | return &AwsAppsyncGraphqlAPITracingRule{ 18 | resourceType: "aws_appsync_graphql_api", 19 | attributeName: "xray_enabled", 20 | } 21 | } 22 | 23 | // Name returns the rule name 24 | func (r *AwsAppsyncGraphqlAPITracingRule) Name() string { 25 | return "aws_appsync_graphql_api_tracing_rule" 26 | } 27 | 28 | // Enabled returns whether the rule is enabled by default 29 | func (r *AwsAppsyncGraphqlAPITracingRule) Enabled() bool { 30 | return true 31 | } 32 | 33 | // Severity returns the rule severity 34 | func (r *AwsAppsyncGraphqlAPITracingRule) Severity() tflint.Severity { 35 | return tflint.WARNING 36 | } 37 | 38 | // Link returns the rule reference link 39 | func (r *AwsAppsyncGraphqlAPITracingRule) Link() string { 40 | return "https://awslabs.github.io/serverless-rules/rules/appsync/tracing/" 41 | } 42 | 43 | // Check checks whether "aws_appsync_graphql_api" has tracing enabled 44 | func (r *AwsAppsyncGraphqlAPITracingRule) Check(runner tflint.Runner) error { 45 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 46 | Attributes: []hclext.AttributeSchema{ 47 | {Name: r.attributeName}, 48 | }, 49 | }, nil) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | for _, resource := range resources.Blocks { 55 | attribute, exists := resource.Body.Attributes[r.attributeName] 56 | if !exists { 57 | runner.EmitIssue( 58 | r, 59 | fmt.Sprintf("\"%s\" is not present.", r.attributeName), 60 | resource.DefRange, 61 | ) 62 | continue 63 | } 64 | 65 | var xrayTracingEnabled string 66 | err := runner.EvaluateExpr(attribute.Expr, &xrayTracingEnabled, nil) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | if xrayTracingEnabled != "true" { 72 | runner.EmitIssue( 73 | r, 74 | fmt.Sprintf("\"%s\" should be set to true.", r.attributeName), 75 | attribute.Expr.Range(), 76 | ) 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_appsync_graphql_api_tracing_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsAppsyncGraphqlAPITracingRule(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "not present", 18 | Content: ` 19 | resource "aws_appsync_graphql_api" "false" {}`, 20 | Expected: helper.Issues{ 21 | { 22 | Rule: NewAwsAppsyncGraphqlAPITracingRule(), 23 | Message: "\"xray_enabled\" is not present.", 24 | Range: hcl.Range{ 25 | Filename: "resource.tf", 26 | Start: hcl.Pos{Line: 2, Column: 1}, 27 | End: hcl.Pos{Line: 2, Column: 43}, 28 | }, 29 | }, 30 | }, 31 | }, 32 | { 33 | Name: "false is invalid", 34 | Content: ` 35 | resource "aws_appsync_graphql_api" "false" { 36 | xray_enabled = false 37 | }`, 38 | Expected: helper.Issues{ 39 | { 40 | Rule: NewAwsAppsyncGraphqlAPITracingRule(), 41 | Message: "\"xray_enabled\" should be set to true.", 42 | Range: hcl.Range{ 43 | Filename: "resource.tf", 44 | Start: hcl.Pos{Line: 3, Column: 19}, 45 | End: hcl.Pos{Line: 3, Column: 24}, 46 | }, 47 | }, 48 | }, 49 | }, 50 | { 51 | Name: "true is valid", 52 | Content: ` 53 | resource "aws_appsync_graphql_api" "true" { 54 | xray_enabled = true 55 | }`, 56 | Expected: helper.Issues{}, 57 | }, 58 | } 59 | 60 | rule := NewAwsAppsyncGraphqlAPITracingRule() 61 | 62 | for _, tc := range cases { 63 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 64 | 65 | if err := rule.Check(runner); err != nil { 66 | t.Fatalf("Unexpected error occurred: %s", err) 67 | } 68 | 69 | helper.AssertIssues(t, tc.Expected, runner.Issues) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_cloudwatch_event_target_no_dlq.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/terraform-linters/tflint-plugin-sdk/hclext" 7 | "github.com/terraform-linters/tflint-plugin-sdk/tflint" 8 | ) 9 | 10 | // AwsCloudwatchEventTargetNoDlq checks if there is a DLQ configured on EventBridge targets 11 | type AwsCloudwatchEventTargetNoDlqRule struct { 12 | tflint.DefaultRule 13 | resourceType string 14 | blockName string 15 | attributeName string 16 | } 17 | 18 | // NewAwsCloudwatchEventTargetNoDlqRule returns new rule with default attributes 19 | func NewAwsCloudwatchEventTargetNoDlqRule() *AwsCloudwatchEventTargetNoDlqRule { 20 | return &AwsCloudwatchEventTargetNoDlqRule{ 21 | resourceType: "aws_cloudwatch_event_target", 22 | blockName: "dead_letter_config", 23 | attributeName: "arn", 24 | } 25 | } 26 | 27 | // Name returns the rule name 28 | func (r *AwsCloudwatchEventTargetNoDlqRule) Name() string { 29 | return "aws_cloudwatch_event_target_no_dlq" 30 | } 31 | 32 | // Enabled returns whether the rule is enabled by default 33 | func (r *AwsCloudwatchEventTargetNoDlqRule) Enabled() bool { 34 | return true 35 | } 36 | 37 | // Severity returns the rule severity 38 | func (r *AwsCloudwatchEventTargetNoDlqRule) Severity() tflint.Severity { 39 | return tflint.ERROR 40 | } 41 | 42 | // Link returns the rule reference link 43 | func (r *AwsCloudwatchEventTargetNoDlqRule) Link() string { 44 | return "https://awslabs.github.io/serverless-rules/rules/eventbridge/rule_without_dlq/" 45 | } 46 | 47 | // Check checks if there is a DLQ configured on EventBridge targets 48 | func (r *AwsCloudwatchEventTargetNoDlqRule) Check(runner tflint.Runner) error { 49 | resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ 50 | Blocks: []hclext.BlockSchema{ 51 | { 52 | Type: r.blockName, 53 | Body: &hclext.BodySchema{ 54 | Attributes: []hclext.AttributeSchema{ 55 | {Name: r.attributeName}, 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, nil) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | for _, resource := range resources.Blocks { 66 | // Check for block 67 | blocks := resource.Body.Blocks.OfType(r.blockName) 68 | if len(blocks) == 0 { 69 | runner.EmitIssue( 70 | r, 71 | fmt.Sprintf("\"%s\" is not present.", r.blockName), 72 | resource.DefRange, 73 | ) 74 | continue 75 | } 76 | 77 | // Check for attribute 78 | _, exists := blocks[0].Body.Attributes[r.attributeName] 79 | if !exists { 80 | runner.EmitIssue( 81 | r, 82 | fmt.Sprintf("\"%s\" is not present.", r.attributeName), 83 | blocks[0].DefRange, 84 | ) 85 | } 86 | } 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_cloudwatch_event_target_no_dlq_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsCloudwatchEventTargetNoDlq(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing dead_letter_config", 18 | Content: ` 19 | resource "aws_cloudwatch_event_target" "this" { 20 | } 21 | `, 22 | Expected: helper.Issues{ 23 | { 24 | Rule: NewAwsCloudwatchEventTargetNoDlqRule(), 25 | Message: "\"dead_letter_config\" is not present.", 26 | Range: hcl.Range{ 27 | Filename: "resource.tf", 28 | Start: hcl.Pos{Line: 2, Column: 1}, 29 | End: hcl.Pos{Line: 2, Column: 46}, 30 | }, 31 | }, 32 | }, 33 | }, 34 | { 35 | Name: "missing arn", 36 | Content: ` 37 | resource "aws_cloudwatch_event_target" "this" { 38 | dead_letter_config {} 39 | } 40 | `, 41 | Expected: helper.Issues{ 42 | { 43 | Rule: NewAwsCloudwatchEventTargetNoDlqRule(), 44 | Message: "\"arn\" is not present.", 45 | Range: hcl.Range{ 46 | Filename: "resource.tf", 47 | Start: hcl.Pos{Line: 3, Column: 3}, 48 | End: hcl.Pos{Line: 3, Column: 21}, 49 | }, 50 | }, 51 | }, 52 | }, 53 | { 54 | Name: "correct", 55 | Content: ` 56 | resource "aws_cloudwatch_event_target" "this" { 57 | dead_letter_config { 58 | arn = "my-sqs-queue-arn" 59 | } 60 | } 61 | `, 62 | Expected: helper.Issues{}, 63 | }, 64 | } 65 | 66 | rule := NewAwsCloudwatchEventTargetNoDlqRule() 67 | 68 | for _, tc := range cases { 69 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 70 | 71 | if err := rule.Check(runner); err != nil { 72 | t.Fatalf("Unexpected error occurred: %s", err) 73 | } 74 | 75 | helper.AssertIssues(t, tc.Expected, runner.Issues) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_cloudwatch_log_group_lambda_retention_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsCloudwatchLogGroupLambdaRetention(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "missing aws_cloudwatch_log_group", 18 | Content: ` 19 | resource "aws_lambda_function" "this" { 20 | function_name = "my-function-name" 21 | } 22 | `, 23 | Expected: helper.Issues{ 24 | { 25 | Rule: NewAwsCloudwatchLogGroupLambdaRetentionRule(), 26 | Message: `"aws_lambda_function" is missing a log group with retention_in_days.`, 27 | Range: hcl.Range{ 28 | Filename: "resource.tf", 29 | Start: hcl.Pos{Line: 2, Column: 1}, 30 | End: hcl.Pos{Line: 2, Column: 38}, 31 | }, 32 | }, 33 | }, 34 | }, 35 | { 36 | Name: "missing retention_in_days", 37 | Content: ` 38 | resource "aws_lambda_function" "this" { 39 | function_name = "my-function-name" 40 | } 41 | 42 | resource "aws_cloudwatch_log_group" "this" { 43 | name = "/aws/lambda/my-function-name" 44 | } 45 | `, 46 | Expected: helper.Issues{ 47 | { 48 | Rule: NewAwsCloudwatchLogGroupLambdaRetentionRule(), 49 | Message: `"aws_lambda_function" is missing a log group with retention_in_days.`, 50 | Range: hcl.Range{ 51 | Filename: "resource.tf", 52 | Start: hcl.Pos{Line: 2, Column: 1}, 53 | End: hcl.Pos{Line: 2, Column: 38}, 54 | }, 55 | }, 56 | }, 57 | }, 58 | { 59 | Name: "valid", 60 | Content: ` 61 | resource "aws_lambda_function" "this" { 62 | function_name = "my-function-name" 63 | } 64 | 65 | resource "aws_cloudwatch_log_group" "this" { 66 | name = "/aws/lambda/my-function-name" 67 | retention_in_days = 7 68 | } 69 | `, 70 | Expected: helper.Issues{}, 71 | }, 72 | { 73 | Name: "non-lambda", 74 | Content: ` 75 | resource "aws_cloudwatch_log_group" "this" { 76 | name = "not-lambda" 77 | } 78 | `, 79 | Expected: helper.Issues{}, 80 | }, 81 | } 82 | 83 | rule := NewAwsCloudwatchLogGroupLambdaRetentionRule() 84 | 85 | for _, tc := range cases { 86 | runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) 87 | 88 | if err := rule.Check(runner); err != nil { 89 | t.Fatalf("Unexpected error occurred: %s", err) 90 | } 91 | 92 | helper.AssertIssues(t, tc.Expected, runner.Issues) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tflint-ruleset-aws-serverless/rules/aws_iam_role_lambda_no_star_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | hcl "github.com/hashicorp/hcl/v2" 7 | "github.com/terraform-linters/tflint-plugin-sdk/helper" 8 | ) 9 | 10 | func Test_AwsIamRoleLambdaNoStar(t *testing.T) { 11 | cases := []struct { 12 | Name string 13 | Content string 14 | Expected helper.Issues 15 | }{ 16 | { 17 | Name: "inline has star", 18 | Content: ` 19 | resource "aws_iam_role" "this" { 20 | name = "my-function-role" 21 | assume_role_policy = <