├── .editorconfig ├── .github └── workflows │ ├── lock.yml │ ├── pr-title.yml │ ├── pre-commit.yml │ ├── release.yml │ ├── stale-actions.yaml │ └── unit-test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── cloudwatch-alerts-to-slack │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── notify-slack-simple │ ├── README.md │ ├── custom-lambda.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── functions ├── .flake8 ├── .pyproject.toml ├── Pipfile ├── README.md ├── events │ ├── aws_health_event.json │ ├── cloudwatch_alarm.json │ ├── guardduty_finding_high.json │ ├── guardduty_finding_low.json │ └── guardduty_finding_medium.json ├── integration_test.py ├── messages │ ├── backup.json │ ├── cloudwatch_alarm.json │ ├── dms_notification.json │ ├── glue_notification.json │ ├── guardduty_finding.json │ └── text_message.json ├── mylambda.py ├── notify_slack.py ├── notify_slack_test.py └── snapshots │ ├── __init__.py │ └── snap_notify_slack_test.py ├── iam.tf ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | # Uses editorconfig to maintain consistent coding styles 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | max_line_length = 80 15 | trim_trailing_whitespace = true 16 | 17 | [*.{tf,tfvars}] 18 | indent_size = 2 19 | indent_style = space 20 | 21 | [*.md] 22 | max_line_length = 0 23 | trim_trailing_whitespace = false 24 | 25 | [Makefile] 26 | tab_width = 2 27 | indent_style = tab 28 | 29 | [COMMIT_EDITMSG] 30 | max_line_length = 0 31 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '50 1 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/lock-threads@v5 12 | with: 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | issue-comment: > 15 | I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. 16 | If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. 17 | issue-inactive-days: '30' 18 | pr-comment: > 19 | I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. 20 | If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. 21 | pr-inactive-days: '30' 22 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: 'Validate PR title' 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | name: Validate PR title 13 | runs-on: ubuntu-latest 14 | steps: 15 | # Please look up the latest version from 16 | # https://github.com/amannn/action-semantic-pull-request/releases 17 | - uses: amannn/action-semantic-pull-request@v5.5.3 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | # Configure which types are allowed. 22 | # Default: https://github.com/commitizen/conventional-commit-types 23 | types: | 24 | fix 25 | feat 26 | docs 27 | ci 28 | chore 29 | # Configure that a scope must always be provided. 30 | requireScope: false 31 | # Configure additional validation for the subject based on a regex. 32 | # This example ensures the subject starts with an uppercase character. 33 | subjectPattern: ^[A-Z].+$ 34 | # If `subjectPattern` is configured, you can use this property to override 35 | # the default error message that is shown when the pattern doesn't match. 36 | # The variables `subject` and `title` can be used within the message. 37 | subjectPatternError: | 38 | The subject "{subject}" found in the pull request title "{title}" 39 | didn't match the configured pattern. Please ensure that the subject 40 | starts with an uppercase character. 41 | # For work-in-progress PRs you can typically use draft pull requests 42 | # from Github. However, private repositories on the free plan don't have 43 | # this option and therefore this action allows you to opt-in to using the 44 | # special "[WIP]" prefix to indicate this state. This will avoid the 45 | # validation of the PR title and the pull request checks remain pending. 46 | # Note that a second check will be reported if this is enabled. 47 | wip: true 48 | # When using "Squash and merge" on a PR with only one commit, GitHub 49 | # will suggest using that commit message instead of the PR title for the 50 | # merge commit, and it's easy to commit this by mistake. Enable this option 51 | # to also validate the commit message for one commit PRs. 52 | validateSingleCommit: false 53 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-Commit 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - master 8 | 9 | env: 10 | TERRAFORM_DOCS_VERSION: v0.19.0 11 | TFLINT_VERSION: v0.53.0 12 | 13 | jobs: 14 | collectInputs: 15 | name: Collect workflow inputs 16 | runs-on: ubuntu-latest 17 | outputs: 18 | directories: ${{ steps.dirs.outputs.directories }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Get root directories 24 | id: dirs 25 | uses: clowdhaus/terraform-composite-actions/directories@v1.9.0 26 | 27 | preCommitMinVersions: 28 | name: Min TF pre-commit 29 | needs: collectInputs 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | directory: ${{ fromJson(needs.collectInputs.outputs.directories) }} 34 | steps: 35 | # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 36 | - name: Delete huge unnecessary tools folder 37 | run: | 38 | rm -rf /opt/hostedtoolcache/CodeQL 39 | rm -rf /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk 40 | rm -rf /opt/hostedtoolcache/Ruby 41 | rm -rf /opt/hostedtoolcache/go 42 | 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Terraform min/max versions 47 | id: minMax 48 | uses: clowdhaus/terraform-min-max@v1.3.1 49 | with: 50 | directory: ${{ matrix.directory }} 51 | 52 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} 53 | # Run only validate pre-commit check on min version supported 54 | if: ${{ matrix.directory != '.' }} 55 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 56 | with: 57 | terraform-version: ${{ steps.minMax.outputs.minVersion }} 58 | tflint-version: ${{ env.TFLINT_VERSION }} 59 | args: 'terraform_validate --color=always --show-diff-on-failure --files ${{ matrix.directory }}/*' 60 | 61 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} 62 | # Run only validate pre-commit check on min version supported 63 | if: ${{ matrix.directory == '.' }} 64 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 65 | with: 66 | terraform-version: ${{ steps.minMax.outputs.minVersion }} 67 | tflint-version: ${{ env.TFLINT_VERSION }} 68 | args: 'terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)' 69 | 70 | preCommitMaxVersion: 71 | name: Max TF pre-commit 72 | runs-on: ubuntu-latest 73 | needs: collectInputs 74 | steps: 75 | # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 76 | - name: Delete huge unnecessary tools folder 77 | run: | 78 | rm -rf /opt/hostedtoolcache/CodeQL 79 | rm -rf /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk 80 | rm -rf /opt/hostedtoolcache/Ruby 81 | rm -rf /opt/hostedtoolcache/go 82 | 83 | - name: Checkout 84 | uses: actions/checkout@v4 85 | with: 86 | ref: ${{ github.event.pull_request.head.ref }} 87 | repository: ${{github.event.pull_request.head.repo.full_name}} 88 | 89 | - name: Terraform min/max versions 90 | id: minMax 91 | uses: clowdhaus/terraform-min-max@v1.3.1 92 | 93 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} 94 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 95 | with: 96 | terraform-version: ${{ steps.minMax.outputs.maxVersion }} 97 | tflint-version: ${{ env.TFLINT_VERSION }} 98 | terraform-docs-version: ${{ env.TERRAFORM_DOCS_VERSION }} 99 | install-hcledit: true 100 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths: 10 | - '**/*.tpl' 11 | - '**/*.py' 12 | - '**/*.tf' 13 | - '.github/workflows/release.yml' 14 | 15 | jobs: 16 | release: 17 | name: Release 18 | runs-on: ubuntu-latest 19 | # Skip running release workflow on forks 20 | if: github.repository_owner == 'terraform-aws-modules' 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | persist-credentials: false 26 | fetch-depth: 0 27 | 28 | - name: Release 29 | uses: cycjimmy/semantic-release-action@v4 30 | with: 31 | semantic_version: 23.0.2 32 | extra_plugins: | 33 | @semantic-release/changelog@6.0.3 34 | @semantic-release/git@10.0.1 35 | conventional-changelog-conventionalcommits@7.0.2 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/stale-actions.yaml: -------------------------------------------------------------------------------- 1 | name: 'Mark or close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | # Staling issues and PR's 14 | days-before-stale: 30 15 | stale-issue-label: stale 16 | stale-pr-label: stale 17 | stale-issue-message: | 18 | This issue has been automatically marked as stale because it has been open 30 days 19 | with no activity. Remove stale label or comment or this issue will be closed in 10 days 20 | stale-pr-message: | 21 | This PR has been automatically marked as stale because it has been open 30 days 22 | with no activity. Remove stale label or comment or this PR will be closed in 10 days 23 | # Not stale if have this labels or part of milestone 24 | exempt-issue-labels: bug,wip,on-hold 25 | exempt-pr-labels: bug,wip,on-hold 26 | exempt-all-milestones: true 27 | # Close issue operations 28 | # Label will be automatically removed if the issues are no longer closed nor locked. 29 | days-before-close: 10 30 | delete-branch: true 31 | close-issue-message: This issue was automatically closed because of stale in 10 days 32 | close-pr-message: This PR was automatically closed because of stale in 10 days 33 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - 'functions/**' 10 | - '.github/workflows/unit-test.yml' 11 | 12 | defaults: 13 | run: 14 | working-directory: functions 15 | 16 | jobs: 17 | test: 18 | name: Execute unit tests 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up Python 3.11 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: 3.11 29 | 30 | - name: Install pipenv 31 | run: | 32 | python -m pip install --upgrade pip 33 | python -m pip install pipenv 34 | 35 | - name: Install local deps 36 | run: pipenv install --dev 37 | 38 | - name: Lint check 39 | run: pipenv run lint:ci 40 | 41 | - name: Type check 42 | run: pipenv run typecheck 43 | 44 | - name: Unit tests 45 | run: pipenv run test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # Terraform lockfile 5 | .terraform.lock.hcl 6 | 7 | # .tfstate files 8 | *.tfstate 9 | *.tfstate.* 10 | 11 | # Crash log files 12 | crash.log 13 | 14 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 15 | # password, private keys, and other secrets. These should not be part of version 16 | # control as they are data points which are potentially sensitive and subject 17 | # to change depending on the environment. 18 | *.tfvars 19 | 20 | # Ignore override files as they are usually used to override resources locally and so 21 | # are not checked in 22 | override.tf 23 | override.tf.json 24 | *_override.tf 25 | *_override.tf.json 26 | 27 | # Ignore CLI configuration files 28 | .terraformrc 29 | terraform.rc 30 | 31 | # Locals 32 | .swp 33 | .idea 34 | .idea* 35 | .vscode/* 36 | *.DS_Store 37 | *.zip 38 | .env 39 | .envrc 40 | 41 | # Byte-compiled / optimized / DLL files 42 | __pycache__/ 43 | *.py[cod] 44 | *$py.class 45 | 46 | # Distribution / packaging 47 | .Python 48 | env/ 49 | build/ 50 | develop-eggs/ 51 | dist/ 52 | downloads/ 53 | eggs/ 54 | .eggs/ 55 | lib/ 56 | lib64/ 57 | parts/ 58 | sdist/ 59 | var/ 60 | *.egg-info/ 61 | .installed.cfg 62 | *.egg 63 | 64 | # Unit test / coverage reports 65 | .pytest* 66 | htmlcov/ 67 | .tox/ 68 | .coverage 69 | .coverage.* 70 | .cache 71 | nosetests.xml 72 | coverage.xml 73 | *.cover 74 | *.coverage 75 | .hypothesis/ 76 | .mypy_cache/ 77 | 78 | # Lockfile 79 | Pipfile.lock 80 | 81 | # Lambda directories 82 | builds/ 83 | functions/pytest.ini 84 | 85 | # Integration testing file 86 | .int.env 87 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.96.1 4 | hooks: 5 | - id: terraform_fmt 6 | - id: terraform_docs 7 | args: 8 | - '--args=--lockfile=false' 9 | - id: terraform_tflint 10 | args: 11 | - '--args=--only=terraform_deprecated_interpolation' 12 | - '--args=--only=terraform_deprecated_index' 13 | - '--args=--only=terraform_unused_declarations' 14 | - '--args=--only=terraform_comment_syntax' 15 | - '--args=--only=terraform_documented_outputs' 16 | - '--args=--only=terraform_documented_variables' 17 | - '--args=--only=terraform_typed_variables' 18 | - '--args=--only=terraform_module_pinned_source' 19 | - '--args=--only=terraform_naming_convention' 20 | - '--args=--only=terraform_required_version' 21 | - '--args=--only=terraform_required_providers' 22 | - '--args=--only=terraform_standard_module_structure' 23 | - '--args=--only=terraform_workspace_remote' 24 | - id: terraform_validate 25 | - repo: https://github.com/pre-commit/pre-commit-hooks 26 | rev: v5.0.0 27 | hooks: 28 | - id: check-merge-conflict 29 | - id: end-of-file-fixer 30 | - id: trailing-whitespace 31 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main", 4 | "master" 5 | ], 6 | "ci": false, 7 | "plugins": [ 8 | [ 9 | "@semantic-release/commit-analyzer", 10 | { 11 | "preset": "conventionalcommits" 12 | } 13 | ], 14 | [ 15 | "@semantic-release/release-notes-generator", 16 | { 17 | "preset": "conventionalcommits" 18 | } 19 | ], 20 | [ 21 | "@semantic-release/github", 22 | { 23 | "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", 24 | "labels": false, 25 | "releasedLabels": false 26 | } 27 | ], 28 | [ 29 | "@semantic-release/changelog", 30 | { 31 | "changelogFile": "CHANGELOG.md", 32 | "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." 33 | } 34 | ], 35 | [ 36 | "@semantic-release/git", 37 | { 38 | "assets": [ 39 | "CHANGELOG.md" 40 | ], 41 | "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 42 | } 43 | ] 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [6.6.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.5.2...v6.6.0) (2025-03-12) 6 | 7 | 8 | ### Features 9 | 10 | * Support for Security Hub ([#242](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/242)) ([3aef5ba](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/3aef5badc119568f8986eb4fea91b76aef99c4df)) 11 | 12 | ## [6.5.2](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.5.1...v6.5.2) (2025-02-25) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * Modify logging for security inspector issue ([#249](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/249)) ([b3cd40f](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/b3cd40f6e90fa628e0481b5093d60a302e58f155)) 18 | 19 | ## [6.5.1](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.5.0...v6.5.1) (2025-01-06) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * Reverts endpoint variable change from e95cde8 ([#240](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/240)) ([81e4b81](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/81e4b816f39c34fab8a5d78e8b854c43aed7dbd2)) 25 | * Update CI workflow versions to latest ([#239](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/239)) ([50b951a](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/50b951a333ebab734c5afba984f0584fd1b43dd7)) 26 | 27 | ## [6.5.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.4.1...v6.5.0) (2024-09-03) 28 | 29 | 30 | ### Features 31 | 32 | * Add variable to allow disabling the package timestamp trigger ([#233](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/233)) ([b3016e2](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/b3016e2f059ffa4ce12745acd4c131c3744faf44)) 33 | 34 | ## [6.4.1](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.4.0...v6.4.1) (2024-09-03) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * Update `aws_sns_topic_subscription` endpoint to use qualified arn ([#231](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/231)) ([e95cde8](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/e95cde8acdaf221e74595daa2238b75f0682ea06)), closes [#230](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/230) 40 | 41 | ## [6.4.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.3.0...v6.4.0) (2024-04-24) 42 | 43 | 44 | ### Features 45 | 46 | * Improved AWS backup notification readability ([#222](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/222)) ([27d1c46](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/27d1c464f80708740d8d155e7cb11367b41bab6c)) 47 | 48 | ## [6.3.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.2.0...v6.3.0) (2024-04-22) 49 | 50 | 51 | ### Features 52 | 53 | * Update Python lambda runtime from `3.8` to `3.11` ([#225](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/225)) ([b4ef4e4](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/b4ef4e45e9f3dafb774ccf62d9473b338de68f3f)) 54 | 55 | ## [6.2.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.1.2...v6.2.0) (2024-04-22) 56 | 57 | 58 | ### Features 59 | 60 | * Added architecture variable ([#224](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/224)) ([1ae3ab7](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/1ae3ab7e084341e7a1fd3acccb15d2971020fce5)) 61 | 62 | ## [6.1.2](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.1.1...v6.1.2) (2024-03-26) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * Correct assume role permissions for SNS service to assume IAM role ([#220](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/220)) ([dae0c0f](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/dae0c0f49d41cf98c5e31af7912ed406eea81c83)) 68 | 69 | ## [6.1.1](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.1.0...v6.1.1) (2024-03-06) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * Update CI workflow versions to remove deprecated runtime warnings ([#218](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/218)) ([44edd19](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/44edd191bac2812951faea9562c685fbeeeefee8)) 75 | 76 | ## [6.1.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v6.0.0...v6.1.0) (2023-12-11) 77 | 78 | 79 | ### Features 80 | 81 | * Expose `hash_extra` variable from Lambda module ([#211](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/211)) ([ee30bb3](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/ee30bb3f5c0da7c118c8c09fbb195a7c0e607f73)) 82 | 83 | ## [6.0.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.6.0...v6.0.0) (2023-05-18) 84 | 85 | 86 | ### ⚠ BREAKING CHANGES 87 | 88 | * Added variable to filter body of message on SNS level and bumped min Terraform version to 1.0 (#196) 89 | 90 | ### Features 91 | 92 | * Added variable to filter body of message on SNS level and bumped min Terraform version to 1.0 ([#196](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/196)) ([ab660f7](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/ab660f7e86aec7a4f134036460b98eeb92c6c4c8)) 93 | 94 | ## [5.6.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.5.0...v5.6.0) (2023-01-26) 95 | 96 | 97 | ### Features 98 | 99 | * Add account ID to GuardDuty event notification ([#187](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/187)) ([e3452b4](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/e3452b424a0e5ccdaf69935094e9fb7785fb315b)) 100 | 101 | ## [5.5.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.4.1...v5.5.0) (2022-12-07) 102 | 103 | 104 | ### Features 105 | 106 | * Add SNS topic delivery status IAM role ([#178](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/178)) ([2863105](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/2863105fd6e07ea0f16500928242968c4b4873cb)) 107 | 108 | ### [5.4.1](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.4.0...v5.4.1) (2022-11-07) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * Update CI configuration files to use latest version ([#181](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/181)) ([6ca4605](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/6ca4605be57c4dd17c3daf87867b6e98136b0914)) 114 | 115 | ## [5.4.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.3.0...v5.4.0) (2022-10-21) 116 | 117 | 118 | ### Features 119 | 120 | * Add lambda dead-letter queue variables ([#180](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/180)) ([010aa89](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/010aa89147f91eeb95e7d842d90eccc3beac6265)) 121 | 122 | ## [5.3.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.2.0...v5.3.0) (2022-06-17) 123 | 124 | 125 | ### Features 126 | 127 | * Added support for AWS Health events ([#170](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/170)) ([3d38bfa](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/3d38bfa524541a6497ebcc77051ef78253cc4a3e)) 128 | 129 | ## [5.2.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.1.0...v5.2.0) (2022-06-14) 130 | 131 | 132 | ### Features 133 | 134 | * Added support for custom lambda function ([#172](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/172)) ([4a9d0b0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/4a9d0b02a9421ff52b392145aaa2aea0c7317a51)) 135 | 136 | ## [5.1.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v5.0.0...v5.1.0) (2022-05-04) 137 | 138 | 139 | ### Features 140 | 141 | * Added ephemeral_storage_size variable ([#167](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/167)) ([c82299a](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/c82299aaec22f301c62f220d8446675647168ff4)) 142 | 143 | ## [5.0.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.24.0...v5.0.0) (2022-03-31) 144 | 145 | 146 | ### ⚠ BREAKING CHANGES 147 | 148 | * - Update lambda module to 3.1.0 to support AWS provider version 4.8+ 149 | 150 | ### Features 151 | 152 | * Update lambda module to 3.1.0 to support AWS provider version 4.8+ ([#166](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/166)) ([ea822a3](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/ea822a3dbd4ac24803385cabae43538c9a3b10f3)) 153 | 154 | # [4.24.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.23.0...v4.24.0) (2021-12-14) 155 | 156 | 157 | ### Features 158 | 159 | * Revert incorrectly removed output this_slack_topic_arn ([#159](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/159)) ([24ec027](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/24ec027e1b6fe708eb4a6d7788a64d9452ecbfe0)) 160 | 161 | # [4.23.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.22.0...v4.23.0) (2021-12-11) 162 | 163 | 164 | ### Features 165 | 166 | * add support for recreating package locally if not missing/not present ([#158](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/158)) ([912e11d](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/912e11dc38416650ac07e0762a5e469a030032bd)) 167 | 168 | # [4.22.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.21.0...v4.22.0) (2021-12-10) 169 | 170 | 171 | ### Features 172 | 173 | * add lint and unit test workflow checks for pull requests ([#152](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/152)) ([d2675ec](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/d2675eca91f3ca4bc8b7a18912ae84b36b7922f1)) 174 | 175 | # [4.21.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.20.0...v4.21.0) (2021-12-10) 176 | 177 | 178 | ### Features 179 | 180 | * Added policy path variable to lambda module IAM role policy ([#153](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/153)) ([b3179a9](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/b3179a9f025943da60daf39d3ce73e88ed57e9ba)) 181 | 182 | # [4.20.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.19.0...v4.20.0) (2021-12-09) 183 | 184 | 185 | ### Features 186 | 187 | * Update lambda module and bump Terraform/AWS provider versions ([#151](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/151)) ([0a1fae8](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/0a1fae86060248353eea2ededad26f43774e500e)) 188 | 189 | # [4.19.0](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.18.0...v4.19.0) (2021-12-09) 190 | 191 | 192 | ### Bug Fixes 193 | 194 | * update CI/CD process to enable auto-release workflow ([#149](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/149)) ([f7dd0a3](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/f7dd0a35d1c140a3465564740abe3579c9e12b48)) 195 | 196 | 197 | ### Features 198 | 199 | * Added path input variable for lambda module IAM role ([#150](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/150)) ([fc0c120](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/commit/fc0c120bd379be65177745637ad402b46334cda5)) 200 | 201 | 202 | ## [v4.18.0] - 2021-10-01 203 | 204 | - feat: Added support for GuardDuty Findings format ([#143](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/143)) 205 | 206 | 207 | 208 | ## [v4.17.0] - 2021-06-28 209 | 210 | - feat: Allow custom attachement ([#123](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/123)) 211 | 212 | 213 | 214 | ## [v4.16.0] - 2021-06-28 215 | 216 | - feat: add support for nested messages ([#142](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/142)) 217 | 218 | 219 | 220 | ## [v4.15.0] - 2021-05-25 221 | 222 | - chore: Remove check boxes that don't render properly in module doc ([#140](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/140)) 223 | - chore: update CI/CD to use stable `terraform-docs` release artifact and discoverable Apache2.0 license ([#138](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/138)) 224 | 225 | 226 | 227 | ## [v4.14.0] - 2021-04-19 228 | 229 | - feat: Updated code to support Terraform 0.15 ([#136](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/136)) 230 | - chore: update documentation and pin `terraform_docs` version to avoid future changes ([#134](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/134)) 231 | 232 | 233 | 234 | ## [v4.13.0] - 2021-03-12 235 | 236 | - fix: use the current aws partition ([#133](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/133)) 237 | - chore: align ci-cd static checks to use individual minimum Terraform versions ([#131](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/131)) 238 | - fix: Remove data resource for sns topic to avoid race condition ([#81](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/81)) 239 | - chore: add ci-cd workflow for pre-commit checks ([#128](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/128)) 240 | - feat: Improve slack message formatting for generic messages ([#124](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/124)) 241 | - chore: update documentation based on latest `terraform-docs` which includes module and resource sections ([#126](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/126)) 242 | - feat: add support for GovCloud URLs ([#114](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/114)) 243 | - feat: Allow Lambda function to be VPC bound ([#122](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/122)) 244 | - feat: Updated version of terraform-aws-lambda module to 1.28.0 ([#119](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/119)) 245 | - feat: Updated version of Terraform AWS Lambda module to support multiple copies ([#117](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/117)) 246 | - fix: Typo on subscription_filter_policy variable ([#113](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/113)) 247 | - docs: Added a note about using with Terraform Cloud Agents ([#108](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/108)) 248 | - feat: allow reuse of existing lambda_role ([#85](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/85)) 249 | - fix: Fix regression with aws_cloudwatch_log_group resource after upgrade of AWS provider 3.0 ([#106](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/106)) 250 | - docs: Updated version of module to use for Terraform 0.12 users 251 | - fix: Updated version requirements to be Terraform 0.13 only ([#101](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/101)) 252 | - feat: Updated Lambda module to work with Terraform 0.13 ([#99](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/99)) 253 | - fix: Bump version of lambda module that supports Terraform 13 and AWS Provider 3.x ([#96](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/96)) 254 | 255 | 256 | 257 | ## [v3.6.0] - 2021-03-01 258 | 259 | - fix: Fix regression with aws_cloudwatch_log_group resource after upgrade of AWS provider 3.0 ([#106](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/106)) ([#130](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/130)) 260 | - feat: Updated version of Lambda module to allow AWS provider version 3 261 | 262 | 263 | 264 | ## [v4.12.0] - 2021-03-01 265 | 266 | - fix: Remove data resource for sns topic to avoid race condition ([#81](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/81)) 267 | - chore: add ci-cd workflow for pre-commit checks ([#128](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/128)) 268 | 269 | 270 | 271 | ## [v4.11.0] - 2021-02-21 272 | 273 | - feat: Improve slack message formatting for generic messages ([#124](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/124)) 274 | 275 | 276 | 277 | ## [v4.10.0] - 2021-02-20 278 | 279 | - chore: update documentation based on latest `terraform-docs` which includes module and resource sections ([#126](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/126)) 280 | 281 | 282 | 283 | ## [v4.9.0] - 2020-12-18 284 | 285 | - feat: add support for GovCloud URLs ([#114](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/114)) 286 | 287 | 288 | 289 | ## [v4.8.0] - 2020-12-18 290 | 291 | - feat: Allow Lambda function to be VPC bound ([#122](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/122)) 292 | 293 | 294 | 295 | ## [v4.7.0] - 2020-11-17 296 | 297 | - feat: Updated version of terraform-aws-lambda module to 1.28.0 ([#119](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/119)) 298 | 299 | 300 | 301 | ## [v4.6.0] - 2020-11-05 302 | 303 | - feat: Updated version of Terraform AWS Lambda module to support multiple copies ([#117](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/117)) 304 | 305 | 306 | 307 | ## [v4.5.0] - 2020-10-15 308 | 309 | - fix: Typo on subscription_filter_policy variable ([#113](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/113)) 310 | 311 | 312 | 313 | ## [v4.4.0] - 2020-10-08 314 | 315 | - docs: Added a note about using with Terraform Cloud Agents ([#108](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/108)) 316 | 317 | 318 | 319 | ## [v4.3.0] - 2020-09-07 320 | 321 | - feat: allow reuse of existing lambda_role ([#85](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/85)) 322 | 323 | 324 | 325 | ## [v4.2.0] - 2020-09-07 326 | 327 | - fix: Fix regression with aws_cloudwatch_log_group resource after upgrade of AWS provider 3.0 ([#106](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/106)) 328 | - docs: Updated version of module to use for Terraform 0.12 users 329 | - fix: Updated version requirements to be Terraform 0.13 only ([#101](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/101)) 330 | - feat: Updated Lambda module to work with Terraform 0.13 ([#99](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/99)) 331 | - fix: Bump version of lambda module that supports Terraform 13 and AWS Provider 3.x ([#96](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/96)) 332 | 333 | 334 | 335 | ## [v3.5.0] - 2020-08-14 336 | 337 | - feat: Updated version of Lambda module to allow AWS provider version 3 338 | 339 | 340 | 341 | ## [v4.1.0] - 2020-08-14 342 | 343 | - fix: Updated version requirements to be Terraform 0.13 only ([#101](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/101)) 344 | 345 | 346 | 347 | ## [v4.0.0] - 2020-08-13 348 | 349 | - feat: Updated Lambda module to work with Terraform 0.13 ([#99](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/99)) 350 | - fix: Bump version of lambda module that supports Terraform 13 and AWS Provider 3.x ([#96](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/96)) 351 | 352 | 353 | 354 | ## [v3.4.0] - 2020-08-13 355 | 356 | - feat: update required version of aws provider to allow 3.0 ([#95](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/95)) 357 | 358 | 359 | 360 | ## [v3.3.0] - 2020-06-19 361 | 362 | - Updated README 363 | - feat: Add support for SSE on the topic ([#82](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/82)) 364 | 365 | 366 | 367 | ## [v3.2.0] - 2020-06-11 368 | 369 | - feat: Updated version of Lambda module 370 | 371 | 372 | 373 | ## [v3.1.0] - 2020-06-10 374 | 375 | - fix: Upgraded version of Lambda module (fix [#84](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/84)) 376 | 377 | 378 | 379 | ## [v3.0.0] - 2020-06-07 380 | 381 | - Updated pre-commit hooks 382 | - feat: Rewrote module to handle Lambda resources properly with terraform-aws-lambda module ([#83](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/83)) 383 | - chore: Removed stale.yml from .github folder 384 | - fix: Stale bot should process only issues for now ([#79](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/79)) 385 | 386 | 387 | 388 | ## [v2.15.0] - 2020-04-13 389 | 390 | - docs: Updated required versions in README 391 | - Add support fro IAM role boundary policy ([#61](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/61)) 392 | 393 | 394 | 395 | ## [v2.14.0] - 2020-04-13 396 | 397 | - docs: Updated README after [#62](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/62) 398 | - feat: Add support for custom name prefixes for IAM role and policy ([#62](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/62)) 399 | - fix: Move stale.yml to .github ([#78](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/78)) 400 | - feat: Add Stale Bot Config ([#77](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/77)) 401 | 402 | 403 | 404 | ## [v2.13.0] - 2020-03-19 405 | 406 | 407 | 408 | 409 | ## [v2.12.0] - 2020-03-19 410 | 411 | 412 | 413 | 414 | ## [v2.11.0] - 2020-03-19 415 | 416 | - Add subsription filter policy support ([#74](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/74)) 417 | 418 | 419 | 420 | ## [v2.10.0] - 2020-01-21 421 | 422 | - Updated pre-commit-terraform with terraform-docs 0.8.0 support ([#65](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/65)) 423 | 424 | 425 | 426 | ## [v2.9.0] - 2020-01-16 427 | 428 | - Fix empty tuple error ([#64](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/64)) 429 | 430 | 431 | 432 | ## [v2.8.0] - 2019-12-21 433 | 434 | - Added lambda description and improved Lambda IAM policy for KMS ([#56](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/56)) 435 | 436 | 437 | 438 | ## [v2.7.0] - 2019-12-20 439 | 440 | - Added support for multiline messages ([#55](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/55)) 441 | 442 | 443 | 444 | ## [v2.6.0] - 2019-12-20 445 | 446 | - Added pytest and logging (based on [#27](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/27)) ([#54](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/54)) 447 | 448 | 449 | 450 | ## [v2.5.0] - 2019-12-20 451 | 452 | - Updated formatting 453 | - use 0.12 syntax for depends_on ([#51](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/51)) 454 | 455 | 456 | 457 | ## [v2.4.0] - 2019-12-10 458 | 459 | - Use urllib.parse.quote for the alarm name ([#35](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/35)) 460 | - Updated simple example a bit 461 | - Create AWS Cloudwatch log group and give explicit access to it ([#40](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/40)) 462 | - Added support for reserved_concurrent_executions 463 | - Updated docs, python3.7 464 | - Add support for resource tagging ([#45](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/45)) 465 | - Upgraded module to support Terraform 0.12 ([#36](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/36)) 466 | 467 | 468 | 469 | ## [v1.14.0] - 2019-11-08 470 | 471 | - Updated pre-commit hooks 472 | - Reduce scope of IAM Policy for CloudWatch Logs ([#44](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/44)) 473 | 474 | 475 | 476 | ## [v2.3.0] - 2019-11-08 477 | 478 | - Create AWS Cloudwatch log group and give explicit access to it ([#40](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/40)) 479 | 480 | 481 | 482 | ## [v2.2.0] - 2019-11-08 483 | 484 | - Added support for reserved_concurrent_executions 485 | 486 | 487 | 488 | ## [v2.1.0] - 2019-11-08 489 | 490 | - Updated docs, python3.7 491 | - Add support for resource tagging ([#45](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/45)) 492 | 493 | 494 | 495 | ## [v2.0.0] - 2019-06-12 496 | 497 | - Upgraded module to support Terraform 0.12 ([#36](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/36)) 498 | 499 | 500 | 501 | ## [v1.13.0] - 2019-02-22 502 | 503 | - need to convert from json string to dict when extracting message from event ([#30](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/30)) 504 | 505 | 506 | 507 | ## [v1.12.0] - 2019-02-21 508 | 509 | - Pass the subject ot default_notification ([#29](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/29)) 510 | 511 | 512 | 513 | ## [v1.11.0] - 2018-12-28 514 | 515 | - No longer parsing the SNS event as incoming JSON ([#23](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/23)) 516 | 517 | 518 | 519 | ## [v1.10.0] - 2018-08-20 520 | 521 | - Fixed bug which causes apply failure when create = false ([#19](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/19)) 522 | 523 | 524 | 525 | ## [v1.9.0] - 2018-06-21 526 | 527 | - Allow computed KMS key value (fixed [#10](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/10)) ([#18](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/18)) 528 | 529 | 530 | 531 | ## [v1.8.0] - 2018-06-20 532 | 533 | - include short alarm name in slack notification text ([#14](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/14)) 534 | 535 | 536 | 537 | ## [v1.7.0] - 2018-06-20 538 | 539 | - Renamed enable to create, minor fixes after [#15](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/15) 540 | - Add flag to enable/disable creation of resources ([#15](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/15)) 541 | 542 | 543 | 544 | ## [v1.6.0] - 2018-06-19 545 | 546 | - Fixed formatting 547 | - Fix Lambda path in shared state ([#17](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/17)) 548 | - Fixed spelling a bit 549 | - Cirumvent TF's path.module limitation for lambda filenames 550 | - Cirumvent TF's path.module limitation for lambda filenames 551 | - Cirumvent TF's path.module limitation for lambda filenames 552 | 553 | 554 | 555 | ## [v1.5.0] - 2018-06-06 556 | 557 | - Fixed formatting (ran 'pre-commit run -a') 558 | - Add in slack emoji support ([#11](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/11)) 559 | - Update comments in examples/ about aws_kms_ciphertext ([#12](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/12)) 560 | 561 | 562 | 563 | ## [v1.4.0] - 2018-06-05 564 | 565 | - Ignore `last_modified` timestamp deciding whether to do an update ([#9](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/9)) 566 | - Updated formatting in examples 567 | 568 | 569 | 570 | ## [v1.3.0] - 2018-05-29 571 | 572 | - Ignore changes in filename (fixed [#6](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/6)) 573 | 574 | 575 | 576 | ## [v1.2.0] - 2018-05-16 577 | 578 | - Added pre-commit hook to autogenerate terraform-docs ([#7](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/7)) 579 | 580 | 581 | 582 | ## [v1.1.0] - 2018-03-22 583 | 584 | - Feature/lambda function name variable ([#5](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/5)) 585 | 586 | 587 | 588 | ## [v1.0.1] - 2018-02-22 589 | 590 | - Fix mismatch in alarm state labels and values ([#4](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/4)) 591 | 592 | 593 | 594 | ## [v1.0.0] - 2018-02-15 595 | 596 | - Added better code, examples, docs ([#2](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/issues/2)) 597 | 598 | 599 | 600 | ## v0.0.1 - 2018-02-12 601 | 602 | - Add encrypted webhook URL example 603 | - Fix decryption of webhook URL 604 | - Update readme 605 | - Add basic example 606 | - Make KMS optional 607 | - Add README description 608 | - Add preliminary cloudwatch event handling lambda 609 | - Initial commit 610 | 611 | 612 | [Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.18.0...HEAD 613 | [v4.18.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.17.0...v4.18.0 614 | [v4.17.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.16.0...v4.17.0 615 | [v4.16.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.15.0...v4.16.0 616 | [v4.15.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.14.0...v4.15.0 617 | [v4.14.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.13.0...v4.14.0 618 | [v4.13.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.6.0...v4.13.0 619 | [v3.6.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.12.0...v3.6.0 620 | [v4.12.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.11.0...v4.12.0 621 | [v4.11.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.10.0...v4.11.0 622 | [v4.10.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.9.0...v4.10.0 623 | [v4.9.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.8.0...v4.9.0 624 | [v4.8.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.7.0...v4.8.0 625 | [v4.7.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.6.0...v4.7.0 626 | [v4.6.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.5.0...v4.6.0 627 | [v4.5.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.4.0...v4.5.0 628 | [v4.4.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.3.0...v4.4.0 629 | [v4.3.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.2.0...v4.3.0 630 | [v4.2.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.5.0...v4.2.0 631 | [v3.5.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.1.0...v3.5.0 632 | [v4.1.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v4.0.0...v4.1.0 633 | [v4.0.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.4.0...v4.0.0 634 | [v3.4.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.3.0...v3.4.0 635 | [v3.3.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.2.0...v3.3.0 636 | [v3.2.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.1.0...v3.2.0 637 | [v3.1.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v3.0.0...v3.1.0 638 | [v3.0.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.15.0...v3.0.0 639 | [v2.15.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.14.0...v2.15.0 640 | [v2.14.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.13.0...v2.14.0 641 | [v2.13.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.12.0...v2.13.0 642 | [v2.12.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.11.0...v2.12.0 643 | [v2.11.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.10.0...v2.11.0 644 | [v2.10.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.9.0...v2.10.0 645 | [v2.9.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.8.0...v2.9.0 646 | [v2.8.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.7.0...v2.8.0 647 | [v2.7.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.6.0...v2.7.0 648 | [v2.6.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.5.0...v2.6.0 649 | [v2.5.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.4.0...v2.5.0 650 | [v2.4.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.14.0...v2.4.0 651 | [v1.14.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.3.0...v1.14.0 652 | [v2.3.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.2.0...v2.3.0 653 | [v2.2.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.1.0...v2.2.0 654 | [v2.1.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v2.0.0...v2.1.0 655 | [v2.0.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.13.0...v2.0.0 656 | [v1.13.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.12.0...v1.13.0 657 | [v1.12.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.11.0...v1.12.0 658 | [v1.11.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.10.0...v1.11.0 659 | [v1.10.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.9.0...v1.10.0 660 | [v1.9.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.8.0...v1.9.0 661 | [v1.8.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.7.0...v1.8.0 662 | [v1.7.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.6.0...v1.7.0 663 | [v1.6.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.5.0...v1.6.0 664 | [v1.5.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.4.0...v1.5.0 665 | [v1.4.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.3.0...v1.4.0 666 | [v1.3.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.2.0...v1.3.0 667 | [v1.2.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.1.0...v1.2.0 668 | [v1.1.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.0.1...v1.1.0 669 | [v1.0.1]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v1.0.0...v1.0.1 670 | [v1.0.0]: https://github.com/terraform-aws-modules/terraform-aws-notify-slack/compare/v0.0.1...v1.0.0 671 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Notify Slack Terraform module 2 | 3 | This module creates an SNS topic (or uses an existing one) and an AWS Lambda function that sends notifications to Slack using the [incoming webhooks API](https://api.slack.com/incoming-webhooks). 4 | 5 | Start by setting up an [incoming webhook integration](https://my.slack.com/services/new/incoming-webhook/) in your Slack workspace. 6 | 7 | Doing serverless with Terraform? Check out [serverless.tf framework](https://serverless.tf), which aims to simplify all operations when working with the serverless in Terraform. 8 | 9 | ## Supported Features 10 | 11 | - AWS Lambda runtime Python 3.11 12 | - Create new SNS topic or use existing one 13 | - Support plaintext and encrypted version of Slack webhook URL 14 | - Most of Slack message options are customizable 15 | - Custom Lambda function 16 | - Various event types are supported, even generic messages: 17 | - AWS CloudWatch Alarms 18 | - AWS CloudWatch LogMetrics Alarms 19 | - AWS GuardDuty Findings 20 | 21 | ## Usage 22 | 23 | ```hcl 24 | module "notify_slack" { 25 | source = "terraform-aws-modules/notify-slack/aws" 26 | version = "~> 5.0" 27 | 28 | sns_topic_name = "slack-topic" 29 | 30 | slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC" 31 | slack_channel = "aws-notification" 32 | slack_username = "reporter" 33 | } 34 | ``` 35 | 36 | ## Using with Terraform Cloud Agents 37 | 38 | [Terraform Cloud Agents](https://www.terraform.io/docs/cloud/workspaces/agent.html) are a paid feature, available as part of the Terraform Cloud for Business upgrade package. 39 | 40 | This module requires Python 3.11. You can customize [tfc-agent](https://hub.docker.com/r/hashicorp/tfc-agent) to include Python using this sample `Dockerfile`: 41 | 42 | ```Dockerfile 43 | FROM hashicorp/tfc-agent:latest 44 | RUN apt-get -y update && apt-get -y install python3.11 python3-pip 45 | ENTRYPOINT ["/bin/tfc-agent"] 46 | ``` 47 | 48 | ## Use existing SNS topic or create new 49 | 50 | If you want to subscribe the AWS Lambda Function created by this module to an existing SNS topic you should specify `create_sns_topic = false` as an argument and specify the name of existing SNS topic name in `sns_topic_name`. 51 | 52 | ## Examples 53 | 54 | - [notify-slack-simple](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/examples/notify-slack-simple) - Creates SNS topic which sends messages to Slack channel. 55 | - [cloudwatch-alerts-to-slack](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/examples/cloudwatch-alerts-to-slack) - End to end example which shows how to send AWS Cloudwatch alerts to Slack channel and use KMS to encrypt webhook URL. 56 | 57 | ## Local Development and Testing 58 | 59 | See the [functions](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/functions) for further details. 60 | 61 | 62 | ## Requirements 63 | 64 | | Name | Version | 65 | |------|---------| 66 | | [terraform](#requirement\_terraform) | >= 1.0 | 67 | | [aws](#requirement\_aws) | >= 4.8 | 68 | 69 | ## Providers 70 | 71 | | Name | Version | 72 | |------|---------| 73 | | [aws](#provider\_aws) | >= 4.8 | 74 | 75 | ## Modules 76 | 77 | | Name | Source | Version | 78 | |------|--------|---------| 79 | | [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | 6.8.0 | 80 | 81 | ## Resources 82 | 83 | | Name | Type | 84 | |------|------| 85 | | [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | 86 | | [aws_iam_role.sns_feedback_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 87 | | [aws_sns_topic.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | 88 | | [aws_sns_topic_subscription.sns_notify_slack](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | 89 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 90 | | [aws_iam_policy_document.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 91 | | [aws_iam_policy_document.sns_feedback](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 92 | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | 93 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 94 | 95 | ## Inputs 96 | 97 | | Name | Description | Type | Default | Required | 98 | |------|-------------|------|---------|:--------:| 99 | | [architectures](#input\_architectures) | Instruction set architecture for your Lambda function. Valid values are ["x86\_64"] and ["arm64"]. | `list(string)` | `null` | no | 100 | | [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data for Lambda | `string` | `null` | no | 101 | | [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Specifies the number of days you want to retain log events in log group for Lambda. | `number` | `0` | no | 102 | | [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | Additional tags for the Cloudwatch log group | `map(string)` | `{}` | no | 103 | | [create](#input\_create) | Whether to create all resources | `bool` | `true` | no | 104 | | [create\_sns\_topic](#input\_create\_sns\_topic) | Whether to create new SNS topic | `bool` | `true` | no | 105 | | [enable\_sns\_topic\_delivery\_status\_logs](#input\_enable\_sns\_topic\_delivery\_status\_logs) | Whether to enable SNS topic delivery status logs | `bool` | `false` | no | 106 | | [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | 107 | | [iam\_policy\_path](#input\_iam\_policy\_path) | Path of policies to that should be added to IAM role for Lambda Function | `string` | `null` | no | 108 | | [iam\_role\_boundary\_policy\_arn](#input\_iam\_role\_boundary\_policy\_arn) | The ARN of the policy that is used to set the permissions boundary for the role | `string` | `null` | no | 109 | | [iam\_role\_name\_prefix](#input\_iam\_role\_name\_prefix) | A unique role name beginning with the specified prefix | `string` | `"lambda"` | no | 110 | | [iam\_role\_path](#input\_iam\_role\_path) | Path of IAM role to use for Lambda Function | `string` | `null` | no | 111 | | [iam\_role\_tags](#input\_iam\_role\_tags) | Additional tags for the IAM role | `map(string)` | `{}` | no | 112 | | [kms\_key\_arn](#input\_kms\_key\_arn) | ARN of the KMS key used for decrypting slack webhook url | `string` | `""` | no | 113 | | [lambda\_attach\_dead\_letter\_policy](#input\_lambda\_attach\_dead\_letter\_policy) | Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function | `bool` | `false` | no | 114 | | [lambda\_dead\_letter\_target\_arn](#input\_lambda\_dead\_letter\_target\_arn) | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | 115 | | [lambda\_description](#input\_lambda\_description) | The description of the Lambda function | `string` | `null` | no | 116 | | [lambda\_function\_ephemeral\_storage\_size](#input\_lambda\_function\_ephemeral\_storage\_size) | Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no | 117 | | [lambda\_function\_name](#input\_lambda\_function\_name) | The name of the Lambda function to create | `string` | `"notify_slack"` | no | 118 | | [lambda\_function\_s3\_bucket](#input\_lambda\_function\_s3\_bucket) | S3 bucket to store artifacts | `string` | `null` | no | 119 | | [lambda\_function\_store\_on\_s3](#input\_lambda\_function\_store\_on\_s3) | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | 120 | | [lambda\_function\_tags](#input\_lambda\_function\_tags) | Additional tags for the Lambda function | `map(string)` | `{}` | no | 121 | | [lambda\_function\_vpc\_security\_group\_ids](#input\_lambda\_function\_vpc\_security\_group\_ids) | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | 122 | | [lambda\_function\_vpc\_subnet\_ids](#input\_lambda\_function\_vpc\_subnet\_ids) | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | 123 | | [lambda\_role](#input\_lambda\_role) | IAM role attached to the Lambda Function. If this is set then a role will not be created for you. | `string` | `""` | no | 124 | | [lambda\_source\_path](#input\_lambda\_source\_path) | The source path of the custom Lambda function | `string` | `null` | no | 125 | | [log\_events](#input\_log\_events) | Boolean flag to enabled/disable logging of incoming events | `bool` | `false` | no | 126 | | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | 127 | | [recreate\_missing\_package](#input\_recreate\_missing\_package) | Whether to recreate missing Lambda package if it is missing locally or not | `bool` | `true` | no | 128 | | [reserved\_concurrent\_executions](#input\_reserved\_concurrent\_executions) | The amount of reserved concurrent executions for this lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations | `number` | `-1` | no | 129 | | [slack\_channel](#input\_slack\_channel) | The name of the channel in Slack for notifications | `string` | n/a | yes | 130 | | [slack\_emoji](#input\_slack\_emoji) | A custom emoji that will appear on Slack messages | `string` | `":aws:"` | no | 131 | | [slack\_username](#input\_slack\_username) | The username that will appear on Slack messages | `string` | n/a | yes | 132 | | [slack\_webhook\_url](#input\_slack\_webhook\_url) | The URL of Slack webhook | `string` | n/a | yes | 133 | | [sns\_topic\_feedback\_role\_description](#input\_sns\_topic\_feedback\_role\_description) | Description of IAM role to use for SNS topic delivery status logging | `string` | `null` | no | 134 | | [sns\_topic\_feedback\_role\_force\_detach\_policies](#input\_sns\_topic\_feedback\_role\_force\_detach\_policies) | Specifies to force detaching any policies the IAM role has before destroying it. | `bool` | `true` | no | 135 | | [sns\_topic\_feedback\_role\_name](#input\_sns\_topic\_feedback\_role\_name) | Name of the IAM role to use for SNS topic delivery status logging | `string` | `null` | no | 136 | | [sns\_topic\_feedback\_role\_path](#input\_sns\_topic\_feedback\_role\_path) | Path of IAM role to use for SNS topic delivery status logging | `string` | `null` | no | 137 | | [sns\_topic\_feedback\_role\_permissions\_boundary](#input\_sns\_topic\_feedback\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by SNS topic delivery status logging | `string` | `null` | no | 138 | | [sns\_topic\_feedback\_role\_tags](#input\_sns\_topic\_feedback\_role\_tags) | A map of tags to assign to IAM the SNS topic feedback role | `map(string)` | `{}` | no | 139 | | [sns\_topic\_kms\_key\_id](#input\_sns\_topic\_kms\_key\_id) | ARN of the KMS key used for enabling SSE on the topic | `string` | `""` | no | 140 | | [sns\_topic\_lambda\_feedback\_role\_arn](#input\_sns\_topic\_lambda\_feedback\_role\_arn) | IAM role for SNS topic delivery status logs. If this is set then a role will not be created for you. | `string` | `""` | no | 141 | | [sns\_topic\_lambda\_feedback\_sample\_rate](#input\_sns\_topic\_lambda\_feedback\_sample\_rate) | The percentage of successful deliveries to log | `number` | `100` | no | 142 | | [sns\_topic\_name](#input\_sns\_topic\_name) | The name of the SNS topic to create | `string` | n/a | yes | 143 | | [sns\_topic\_tags](#input\_sns\_topic\_tags) | Additional tags for the SNS topic | `map(string)` | `{}` | no | 144 | | [subscription\_filter\_policy](#input\_subscription\_filter\_policy) | (Optional) A valid filter policy that will be used in the subscription to filter messages seen by the target resource. | `string` | `null` | no | 145 | | [subscription\_filter\_policy\_scope](#input\_subscription\_filter\_policy\_scope) | (Optional) A valid filter policy scope MessageAttributes\|MessageBody | `string` | `null` | no | 146 | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | 147 | | [trigger\_on\_package\_timestamp](#input\_trigger\_on\_package\_timestamp) | (Optional) Whether or not to ignore the file timestamp when deciding to create the archive | `bool` | `false` | no | 148 | 149 | ## Outputs 150 | 151 | | Name | Description | 152 | |------|-------------| 153 | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The Amazon Resource Name (ARN) specifying the log group | 154 | | [lambda\_iam\_role\_arn](#output\_lambda\_iam\_role\_arn) | The ARN of the IAM role used by Lambda function | 155 | | [lambda\_iam\_role\_name](#output\_lambda\_iam\_role\_name) | The name of the IAM role used by Lambda function | 156 | | [notify\_slack\_lambda\_function\_arn](#output\_notify\_slack\_lambda\_function\_arn) | The ARN of the Lambda function | 157 | | [notify\_slack\_lambda\_function\_invoke\_arn](#output\_notify\_slack\_lambda\_function\_invoke\_arn) | The ARN to be used for invoking Lambda function from API Gateway | 158 | | [notify\_slack\_lambda\_function\_last\_modified](#output\_notify\_slack\_lambda\_function\_last\_modified) | The date Lambda function was last modified | 159 | | [notify\_slack\_lambda\_function\_name](#output\_notify\_slack\_lambda\_function\_name) | The name of the Lambda function | 160 | | [notify\_slack\_lambda\_function\_version](#output\_notify\_slack\_lambda\_function\_version) | Latest published version of your Lambda function | 161 | | [slack\_topic\_arn](#output\_slack\_topic\_arn) | The ARN of the SNS topic from which messages will be sent to Slack | 162 | | [sns\_topic\_feedback\_role\_arn](#output\_sns\_topic\_feedback\_role\_arn) | The Amazon Resource Name (ARN) of the IAM role used for SNS delivery status logging | 163 | | [this\_slack\_topic\_arn](#output\_this\_slack\_topic\_arn) | The ARN of the SNS topic from which messages will be sent to Slack (backward compatibility for version 4.x) | 164 | 165 | 166 | ## Authors 167 | 168 | Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/graphs/contributors). 169 | 170 | ## License 171 | 172 | Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/LICENSE) for full details. 173 | -------------------------------------------------------------------------------- /examples/cloudwatch-alerts-to-slack/README.md: -------------------------------------------------------------------------------- 1 | # CloudWatch alerts to Slack 2 | 3 | Configuration in this directory creates a VPC, an SNS topic that sends messages to a Slack channel with Slack webhook URL encrypted using KMS and a CloudWatch Alarm that monitors the duration of lambda execution. 4 | 5 | ## KMS keys 6 | 7 | There are 3 ways to define KMS key which should be used by Lambda function: 8 | 9 | 1. Create [aws_kms_key resource](https://www.terraform.io/docs/providers/aws/r/kms_key.html) and put ARN of it as `kms_key_arn` argument to this module 10 | 1. Use [aws_kms_alias data-source](https://www.terraform.io/docs/providers/aws/d/kms_alias.html) to get an existing KMS key alias and put ARN of it as `kms_key_arn` argument to this module 11 | 1. Hard-code the ARN of KMS key 12 | 13 | ### Option 1: 14 | 15 | ```hcl 16 | resource "aws_kms_key" "this" { 17 | description = "KMS key for notify-slack test" 18 | } 19 | 20 | resource "aws_kms_alias" "this" { 21 | name = "alias/kms-test-key" 22 | target_key_id = aws_kms_key.this.id 23 | } 24 | 25 | // kms_key_arn = aws_kms_key.this.arn 26 | ``` 27 | 28 | ### Option 2: 29 | 30 | ``` 31 | data "aws_kms_alias" "this" { 32 | name = "alias/kms-test-key" 33 | } 34 | 35 | // kms_key_arn = data.aws_kms_alias.this.target_key_arn 36 | ``` 37 | 38 | ### Option 3: 39 | 40 | ``` 41 | // kms_key_arn = "arn:aws:kms:eu-west-1:835367859851:key/054b4846-95fe-4537-94f2-1dfd255238cf" 42 | ``` 43 | 44 | ## Usage 45 | 46 | To run this example you need to execute: 47 | 48 | ```bash 49 | $ terraform init 50 | $ terraform plan 51 | $ terraform apply 52 | ``` 53 | 54 | Note that in practice, encryption of the Slack webhook URL should happen differently (outside of this module). 55 | 56 | Note that this example may create resources which can cost money. Run `terraform destroy` when you don't need these resources. 57 | 58 | 59 | ## Requirements 60 | 61 | | Name | Version | 62 | |------|---------| 63 | | [terraform](#requirement\_terraform) | >= 1.0 | 64 | | [aws](#requirement\_aws) | >= 4.8 | 65 | | [random](#requirement\_random) | >= 2.0 | 66 | 67 | ## Providers 68 | 69 | | Name | Version | 70 | |------|---------| 71 | | [aws](#provider\_aws) | >= 4.8 | 72 | | [random](#provider\_random) | >= 2.0 | 73 | 74 | ## Modules 75 | 76 | | Name | Source | Version | 77 | |------|--------|---------| 78 | | [notify\_slack](#module\_notify\_slack) | ../../ | n/a | 79 | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | 80 | 81 | ## Resources 82 | 83 | | Name | Type | 84 | |------|------| 85 | | [aws_cloudwatch_metric_alarm.lambda_duration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | 86 | | [aws_kms_ciphertext.slack_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_ciphertext) | resource | 87 | | [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 88 | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | 89 | 90 | ## Inputs 91 | 92 | No inputs. 93 | 94 | ## Outputs 95 | 96 | | Name | Description | 97 | |------|-------------| 98 | | [lambda\_iam\_role\_arn](#output\_lambda\_iam\_role\_arn) | The ARN of the IAM role used by Lambda function | 99 | | [lambda\_iam\_role\_name](#output\_lambda\_iam\_role\_name) | The name of the IAM role used by Lambda function | 100 | | [notify\_slack\_lambda\_function\_arn](#output\_notify\_slack\_lambda\_function\_arn) | The ARN of the Lambda function | 101 | | [notify\_slack\_lambda\_function\_invoke\_arn](#output\_notify\_slack\_lambda\_function\_invoke\_arn) | The ARN to be used for invoking Lambda function from API Gateway | 102 | | [notify\_slack\_lambda\_function\_last\_modified](#output\_notify\_slack\_lambda\_function\_last\_modified) | The date Lambda function was last modified | 103 | | [notify\_slack\_lambda\_function\_name](#output\_notify\_slack\_lambda\_function\_name) | The name of the Lambda function | 104 | | [notify\_slack\_lambda\_function\_version](#output\_notify\_slack\_lambda\_function\_version) | Latest published version of your Lambda function | 105 | | [sns\_topic\_arn](#output\_sns\_topic\_arn) | The ARN of the SNS topic from which messages will be sent to Slack | 106 | 107 | -------------------------------------------------------------------------------- /examples/cloudwatch-alerts-to-slack/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | resource "aws_kms_key" "this" { 6 | description = "KMS key for notify-slack test" 7 | } 8 | 9 | # Encrypt the URL, storing encryption here will show it in logs and in tfstate 10 | # https://www.terraform.io/docs/state/sensitive-data.html 11 | resource "aws_kms_ciphertext" "slack_url" { 12 | plaintext = "https://hooks.slack.com/services/AAA/BBB/CCC" 13 | key_id = aws_kms_key.this.arn 14 | } 15 | 16 | module "notify_slack" { 17 | source = "../../" 18 | 19 | for_each = toset([ 20 | "develop", 21 | "release", 22 | "test", 23 | ]) 24 | 25 | sns_topic_name = "slack-topic" 26 | enable_sns_topic_delivery_status_logs = true 27 | 28 | # Specify the ARN of the pre-defined feedback role or leave blank to have the module create it 29 | #sns_topic_lambda_feedback_role_arn = "arn:aws:iam::111122223333:role/sns-delivery-status" 30 | 31 | lambda_function_name = "notify_slack_${each.value}" 32 | 33 | slack_webhook_url = aws_kms_ciphertext.slack_url.ciphertext_blob 34 | slack_channel = "aws-notification" 35 | slack_username = "reporter" 36 | 37 | kms_key_arn = aws_kms_key.this.arn 38 | 39 | lambda_description = "Lambda function which sends notifications to Slack" 40 | log_events = true 41 | 42 | # VPC 43 | # lambda_function_vpc_subnet_ids = module.vpc.intra_subnets 44 | # lambda_function_vpc_security_group_ids = [module.vpc.default_security_group_id] 45 | 46 | tags = { 47 | Name = "cloudwatch-alerts-to-slack" 48 | } 49 | } 50 | 51 | resource "aws_cloudwatch_metric_alarm" "lambda_duration" { 52 | alarm_name = "NotifySlackDuration" 53 | comparison_operator = "GreaterThanOrEqualToThreshold" 54 | evaluation_periods = "1" 55 | metric_name = "Duration" 56 | namespace = "AWS/Lambda" 57 | period = "60" 58 | statistic = "Average" 59 | threshold = "5000" 60 | alarm_description = "Duration of notifying slack exceeds threshold" 61 | 62 | alarm_actions = [module.notify_slack["develop"].slack_topic_arn] 63 | 64 | dimensions = { 65 | FunctionName = module.notify_slack["develop"].notify_slack_lambda_function_name 66 | } 67 | } 68 | 69 | ###### 70 | # VPC 71 | ###### 72 | resource "random_pet" "this" { 73 | length = 2 74 | } 75 | 76 | module "vpc" { 77 | source = "terraform-aws-modules/vpc/aws" 78 | 79 | name = random_pet.this.id 80 | cidr = "10.10.0.0/16" 81 | 82 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 83 | intra_subnets = ["10.10.101.0/24", "10.10.102.0/24", "10.10.103.0/24"] 84 | } 85 | -------------------------------------------------------------------------------- /examples/cloudwatch-alerts-to-slack/outputs.tf: -------------------------------------------------------------------------------- 1 | output "sns_topic_arn" { 2 | description = "The ARN of the SNS topic from which messages will be sent to Slack" 3 | value = module.notify_slack["develop"].slack_topic_arn 4 | } 5 | 6 | output "lambda_iam_role_arn" { 7 | description = "The ARN of the IAM role used by Lambda function" 8 | value = module.notify_slack["develop"].lambda_iam_role_arn 9 | } 10 | 11 | output "lambda_iam_role_name" { 12 | description = "The name of the IAM role used by Lambda function" 13 | value = module.notify_slack["develop"].lambda_iam_role_name 14 | } 15 | 16 | output "notify_slack_lambda_function_arn" { 17 | description = "The ARN of the Lambda function" 18 | value = module.notify_slack["develop"].notify_slack_lambda_function_arn 19 | } 20 | 21 | output "notify_slack_lambda_function_name" { 22 | description = "The name of the Lambda function" 23 | value = module.notify_slack["develop"].notify_slack_lambda_function_name 24 | } 25 | 26 | output "notify_slack_lambda_function_invoke_arn" { 27 | description = "The ARN to be used for invoking Lambda function from API Gateway" 28 | value = module.notify_slack["develop"].notify_slack_lambda_function_invoke_arn 29 | } 30 | 31 | output "notify_slack_lambda_function_last_modified" { 32 | description = "The date Lambda function was last modified" 33 | value = module.notify_slack["develop"].notify_slack_lambda_function_last_modified 34 | } 35 | 36 | output "notify_slack_lambda_function_version" { 37 | description = "Latest published version of your Lambda function" 38 | value = module.notify_slack["develop"].notify_slack_lambda_function_version 39 | } 40 | -------------------------------------------------------------------------------- /examples/cloudwatch-alerts-to-slack/variables.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-notify-slack/0a6a45ee485f3fae6773b2ce9a0bbf24cbbb42a6/examples/cloudwatch-alerts-to-slack/variables.tf -------------------------------------------------------------------------------- /examples/cloudwatch-alerts-to-slack/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.8" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = ">= 2.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/notify-slack-simple/README.md: -------------------------------------------------------------------------------- 1 | Basic Slack notification 2 | ======================== 3 | 4 | Configuration in this directory creates an SNS topic that sends messages to a Slack channel. 5 | 6 | Note, this example does not use KMS key. 7 | 8 | Usage 9 | ===== 10 | 11 | To run this example you need to execute: 12 | 13 | ```bash 14 | $ terraform init 15 | $ terraform plan 16 | $ terraform apply 17 | ``` 18 | 19 | Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources. 20 | 21 | 22 | ## Requirements 23 | 24 | | Name | Version | 25 | |------|---------| 26 | | [terraform](#requirement\_terraform) | >= 1.0 | 27 | | [aws](#requirement\_aws) | >= 4.8 | 28 | | [local](#requirement\_local) | >= 2.0 | 29 | 30 | ## Providers 31 | 32 | | Name | Version | 33 | |------|---------| 34 | | [aws](#provider\_aws) | >= 4.8 | 35 | | [local](#provider\_local) | >= 2.0 | 36 | 37 | ## Modules 38 | 39 | | Name | Source | Version | 40 | |------|--------|---------| 41 | | [custom\_lambda](#module\_custom\_lambda) | ../../ | n/a | 42 | | [notify\_slack](#module\_notify\_slack) | ../../ | n/a | 43 | 44 | ## Resources 45 | 46 | | Name | Type | 47 | |------|------| 48 | | [aws_sns_topic.custom_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | 49 | | [aws_sns_topic.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | 50 | | [local_file.integration_testing](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | 51 | 52 | ## Inputs 53 | 54 | No inputs. 55 | 56 | ## Outputs 57 | 58 | | Name | Description | 59 | |------|-------------| 60 | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The Amazon Resource Name (ARN) specifying the log group | 61 | | [lambda\_iam\_role\_arn](#output\_lambda\_iam\_role\_arn) | The ARN of the IAM role used by Lambda function | 62 | | [lambda\_iam\_role\_name](#output\_lambda\_iam\_role\_name) | The name of the IAM role used by Lambda function | 63 | | [notify\_slack\_lambda\_function\_arn](#output\_notify\_slack\_lambda\_function\_arn) | The ARN of the Lambda function | 64 | | [notify\_slack\_lambda\_function\_invoke\_arn](#output\_notify\_slack\_lambda\_function\_invoke\_arn) | The ARN to be used for invoking Lambda function from API Gateway | 65 | | [notify\_slack\_lambda\_function\_last\_modified](#output\_notify\_slack\_lambda\_function\_last\_modified) | The date Lambda function was last modified | 66 | | [notify\_slack\_lambda\_function\_name](#output\_notify\_slack\_lambda\_function\_name) | The name of the Lambda function | 67 | | [notify\_slack\_lambda\_function\_version](#output\_notify\_slack\_lambda\_function\_version) | Latest published version of your Lambda function | 68 | | [sns\_topic\_arn](#output\_sns\_topic\_arn) | The ARN of the SNS topic from which messages will be sent to Slack | 69 | 70 | -------------------------------------------------------------------------------- /examples/notify-slack-simple/custom-lambda.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | custom = { 3 | name = "ex-${replace(basename(path.cwd), "_", "-")}-custom" 4 | tags = merge({ "Type" = "custom" }, local.tags) 5 | } 6 | } 7 | 8 | ################################################################################ 9 | # Supporting Resources 10 | ################################################################################ 11 | 12 | resource "aws_sns_topic" "custom_lambda" { 13 | name = local.custom.name 14 | tags = local.custom.tags 15 | } 16 | 17 | ################################################################################ 18 | # Slack Notify Module 19 | ################################################################################ 20 | 21 | module "custom_lambda" { 22 | source = "../../" 23 | 24 | lambda_function_name = "custom_lambda" 25 | lambda_source_path = "../../functions/mylambda.py" 26 | 27 | iam_role_name_prefix = "custom" 28 | 29 | sns_topic_name = aws_sns_topic.custom_lambda.name 30 | 31 | slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC" 32 | slack_channel = "aws-notification" 33 | slack_username = "reporter" 34 | 35 | tags = local.custom.tags 36 | } 37 | -------------------------------------------------------------------------------- /examples/notify-slack-simple/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = local.region 3 | } 4 | 5 | locals { 6 | name = "ex-${replace(basename(path.cwd), "_", "-")}" 7 | region = "eu-west-1" 8 | tags = { 9 | Owner = "user" 10 | Environment = "dev" 11 | } 12 | } 13 | 14 | ################################################################################ 15 | # Supporting Resources 16 | ################################################################################ 17 | 18 | resource "aws_sns_topic" "example" { 19 | name = local.name 20 | tags = local.tags 21 | } 22 | 23 | ################################################################################ 24 | # Slack Notify Module 25 | ################################################################################ 26 | 27 | module "notify_slack" { 28 | source = "../../" 29 | 30 | sns_topic_name = aws_sns_topic.example.name 31 | create_sns_topic = false 32 | 33 | slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC" 34 | slack_channel = "aws-notification" 35 | slack_username = "reporter" 36 | 37 | tags = local.tags 38 | } 39 | 40 | ################################################################################ 41 | # Integration Testing Support 42 | # This populates a file that is gitignored to aid in executing the integration tests locally 43 | ################################################################################ 44 | 45 | resource "local_file" "integration_testing" { 46 | filename = "${path.module}/../../functions/.int.env" 47 | content = <<-EOT 48 | REGION=${local.region} 49 | LAMBDA_FUNCTION_NAME=${module.notify_slack.notify_slack_lambda_function_name} 50 | SNS_TOPIC_ARN=${aws_sns_topic.example.arn} 51 | EOT 52 | } 53 | -------------------------------------------------------------------------------- /examples/notify-slack-simple/outputs.tf: -------------------------------------------------------------------------------- 1 | output "sns_topic_arn" { 2 | description = "The ARN of the SNS topic from which messages will be sent to Slack" 3 | value = module.notify_slack.slack_topic_arn 4 | } 5 | 6 | output "lambda_iam_role_arn" { 7 | description = "The ARN of the IAM role used by Lambda function" 8 | value = module.notify_slack.lambda_iam_role_arn 9 | } 10 | 11 | output "lambda_iam_role_name" { 12 | description = "The name of the IAM role used by Lambda function" 13 | value = module.notify_slack.lambda_iam_role_name 14 | } 15 | 16 | output "notify_slack_lambda_function_arn" { 17 | description = "The ARN of the Lambda function" 18 | value = module.notify_slack.notify_slack_lambda_function_arn 19 | } 20 | 21 | output "notify_slack_lambda_function_name" { 22 | description = "The name of the Lambda function" 23 | value = module.notify_slack.notify_slack_lambda_function_name 24 | } 25 | 26 | output "notify_slack_lambda_function_invoke_arn" { 27 | description = "The ARN to be used for invoking Lambda function from API Gateway" 28 | value = module.notify_slack.notify_slack_lambda_function_invoke_arn 29 | } 30 | 31 | output "notify_slack_lambda_function_last_modified" { 32 | description = "The date Lambda function was last modified" 33 | value = module.notify_slack.notify_slack_lambda_function_last_modified 34 | } 35 | 36 | output "notify_slack_lambda_function_version" { 37 | description = "Latest published version of your Lambda function" 38 | value = module.notify_slack.notify_slack_lambda_function_version 39 | } 40 | 41 | output "lambda_cloudwatch_log_group_arn" { 42 | description = "The Amazon Resource Name (ARN) specifying the log group" 43 | value = module.notify_slack.lambda_cloudwatch_log_group_arn 44 | } 45 | -------------------------------------------------------------------------------- /examples/notify-slack-simple/variables.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-notify-slack/0a6a45ee485f3fae6773b2ce9a0bbf24cbbb42a6/examples/notify-slack-simple/variables.tf -------------------------------------------------------------------------------- /examples/notify-slack-simple/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.8" 8 | } 9 | local = { 10 | source = "hashicorp/local" 11 | version = ">= 2.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-complexity = 10 3 | max-line-length = 120 4 | exclude = 5 | .pytest_cache 6 | __pycache__/ 7 | *tests/ 8 | events/ 9 | messages/ 10 | snapshots/ 11 | -------------------------------------------------------------------------------- /functions/.pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | target-version = ['py311'] 4 | include = '\.pyi?$' 5 | verbose = true 6 | exclude = ''' 7 | /( 8 | | \.git 9 | | \.mypy_cache 10 | | dist 11 | | \.pants\.d 12 | | virtualenvs 13 | | \.venv 14 | | _build 15 | | build 16 | | dist 17 | | snapshots 18 | )/ 19 | ''' 20 | 21 | [tool.isort] 22 | line_length = 120 23 | skip = '.terraform' 24 | sections = 'FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER' 25 | known_third_party = 'aws_lambda_powertools,boto3,botocore,pytest,snapshottest' 26 | known_first_party = 'events,notify_slack' 27 | indent = ' ' 28 | 29 | [tool.mypy] 30 | namespace_packages = true 31 | explicit_package_bases = true 32 | 33 | no_implicit_optional = true 34 | implicit_reexport = false 35 | strict_equality = true 36 | 37 | warn_unused_configs = true 38 | warn_unused_ignores = true 39 | warn_return_any = true 40 | warn_redundant_casts = true 41 | warn_unreachable = true 42 | 43 | pretty = true 44 | show_column_numbers = true 45 | show_error_context = true 46 | show_error_codes = true 47 | show_traceback = true 48 | 49 | [tool.coverage.run] 50 | branch = true 51 | omit = ["*_test.py", "tests/*", "events/*", "messages/*", "snapshots/*", "venv/*", ".mypy_cache/*", ".pytest_cache/*"] 52 | 53 | [tool.coverage.report] 54 | show_missing = true 55 | skip_covered = true 56 | skip_empty = true 57 | sort = "-Miss" 58 | fail_under = 75 59 | -------------------------------------------------------------------------------- /functions/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | boto3 = "~=1.34" 10 | botocore = "~=1.34" 11 | black = "*" 12 | flake8 = "*" 13 | isort = "*" 14 | mypy = "*" 15 | pytest = "*" 16 | pytest-cov = "*" 17 | radon = "*" 18 | snapshottest = "~=0.6" 19 | 20 | [requires] 21 | python_version = "3.11" 22 | 23 | [scripts] 24 | test = "python3 -m pytest --cov --cov-report=term" 25 | 'test:updatesnapshots' = "python3 -m pytest --snapshot-update" 26 | cover = "python3 -m coverage html" 27 | complexity = "python3 -m radon cc notify_slack.py -a" 28 | halstead = "python3 -m radon hal notify_slack.py" 29 | typecheck = "python3 -m mypy . --ignore-missing-imports" 30 | lint = "python3 -m flake8 . --count --statistics --benchmark --exit-zero --config=.flake8" 31 | 'lint:ci' = "python3 -m flake8 . --config=.flake8" 32 | imports = "python3 -m isort . --profile black" 33 | format = "python3 -m black ." 34 | 35 | [pipenv] 36 | allow_prereleases = true 37 | -------------------------------------------------------------------------------- /functions/README.md: -------------------------------------------------------------------------------- 1 | # Slack Notify Lambda Functions 2 | 3 | ## Conventions 4 | 5 | The following tools and conventions are used within this project: 6 | 7 | - [pipenv](https://github.com/pypa/pipenv) for managing Python dependencies and development virtualenv 8 | - [flake8](https://github.com/PyCQA/flake8) & [radon](https://github.com/rubik/radon) for linting and static code analysis 9 | - [isort](https://github.com/timothycrosley/isort) for import statement formatting 10 | - [black](https://github.com/ambv/black) for code formatting 11 | - [mypy](https://github.com/python/mypy) for static type checking 12 | - [pytest](https://github.com/pytest-dev/pytest) and [snapshottest](https://github.com/syrusakbary/snapshottest) for unit testing and snapshot testing 13 | 14 | ## Getting Started 15 | 16 | The following instructions will help you get setup for local development and testing purposes. 17 | 18 | ### Prerequisites 19 | 20 | #### [Pipenv](https://github.com/pypa/pipenv) 21 | 22 | Pipenv is used to help manage the python dependencies and local virtualenv for local testing and development. To install `pipenv` please refer to the project [installation documentation](https://github.com/pypa/pipenv#installation). 23 | 24 | Install the projects Python dependencies (with development dependencies) locally by running the following command. 25 | 26 | ```bash 27 | $ pipenv install --dev 28 | ``` 29 | 30 | If you add/change/modify any of the Pipfile dependencies, you can update your local virtualenv using: 31 | 32 | ```bash 33 | $ pipenv update 34 | ``` 35 | 36 | ### Testing 37 | 38 | #### Sample Payloads 39 | 40 | In the `functions/` directory there are two folders that contain sample message payloads used for testing and validation: 41 | 42 | 1. `functions/events/` contains raw events as provided by AWS. You can see a more in-depth list of example events in the (AWS documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html) 43 | 2. `functions/messages/` contains SNS message payloads in the form that they are delivered to the Slack notify lambda function. The `Message` attribute field is where the payload is stored that will be parsed and sent to Slack; this can be events like those described above in #1, or any string/stringified-JSON 44 | 45 | #### Unit Tests 46 | 47 | There are a number of pipenv scripts that are provided to aid in testing and ensuring the codebase is formatted properly. 48 | 49 | - `pipenv run test`: execute unit tests defined using pytest and show test coverage 50 | - `pipenv run lint`: show linting errors and static analysis of codebase 51 | - `pipenv run format`: auto-format codebase according to configurations provided 52 | - `pipenv run imports`: auto-format import statements according to configurations provided 53 | - `pipenv run typecheck`: show typecheck analysis of codebase 54 | 55 | See the `[scripts]` section of the `Pipfile` for the complete list of script commands. 56 | 57 | #### Snapshot Testing 58 | 59 | Snapshot testing is used to compare a set of given inputs to generated output snapshots to aid in unit testing. The tests are run in conjunction with the standard unit tests and the output will be shown in the cumulative output from `pipenv run test`. In theory, however, the snapshots themselves should not change unless: 60 | 61 | 1. The expected output of the message payload has changed 62 | 2. Event/message payloads have been added to or removed from the project 63 | 64 | When a change is required to update the snapshots, please do the following: 65 | 66 | 1. Update the snapshots by running: 67 | 68 | ```bash 69 | $ pipenv run test:updatesnapshots 70 | $ pipenv run format # this is necessary since the generated code follows its own style 71 | ``` 72 | 73 | 2. Provide a clear reasoning within your pull request as to why the snapshots have changed 74 | 75 | #### Integration Tests 76 | 77 | Integration tests require setting up a live Slack webhook 78 | 79 | To run the unit tests: 80 | 81 | 1. Set up a dedicated slack channel as a test sandbox with it's own webhook. See [Slack Incoming Webhooks docs](https://api.slack.com/messaging/webhooks) for details. 82 | 2. From within the `examples/notify-slack-simple/` directory, update the `slack_*` variables to use your values: 83 | 84 | ```hcl 85 | slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC" 86 | slack_channel = "aws-notification" 87 | slack_username = "reporter" 88 | ``` 89 | 90 | 3. Deploy the resources in the `examples/notify-slack-simple/` project using Terraform 91 | 92 | ```bash 93 | $ terraform init && terraform apply -y 94 | ``` 95 | 96 | 4. From within the `functions/` directory, execute the integration tests locally: 97 | 98 | ```bash 99 | $ pipenv run python integration_test.py 100 | ``` 101 | 102 | Within the Slack channel that is associated to the webhook URL provided, you should see all of the messages arriving. You can compared the messages to the payloads in the `functions/events/` and `functions/messages` directories; there should be one Slack message per event payload/file. 103 | 104 | 5. Do not forget to clean up your provisioned resources by returning to the `example/notify-slack-simple/` directory and destroying using Terraform: 105 | 106 | ```bash 107 | $ terraform destroy -y 108 | ``` 109 | 110 | ## Supporting Additional Events 111 | 112 | To add new events with custom message formatting, the general workflow will consist of (ignoring git actions for brevity): 113 | 114 | 1. Add a new example event paylod to the `functions/events/` directory; please name the file, using snake casing, in the form `_.json` such as `guardduty_finding.json` or `cloudwatch_alarm.json` 115 | 2. In the `functions/notify_slack.py` file, add the new formatting function, following a similar naming pattern like in step #1 where the function name is `format__()` such as `format_guardduty_finding()` or `format_cloudwatch_alarm()` 116 | 3. (Optional) Ff there are different "severity" type levels that are to be mapped to Slack message color bars, create an enum that maps the possible serverity values to the appropriate colors. See the `CloudWatchAlarmState` and `GuardDutyFindingSeverity` for examples. The enum name should follow pascal case, Python standard, in the form of `` 117 | 4. Update the snapshots to include your new event payload and expected output. Note - the other snapshots should not be affected by your change, the snapshot diff should only show your new event: 118 | 119 | ```bash 120 | $ pipenv run test:updatesnapshots 121 | $ pipenv run format # this is necessary since the generated code follows its own style 122 | ``` 123 | -------------------------------------------------------------------------------- /functions/events/aws_health_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "121345678-1234-1234-1234-123456789012", 4 | "detail-type": "AWS Health Event", 5 | "source": "aws.health", 6 | "account": "123456789012", 7 | "time": "2016-06-05T06:27:57Z", 8 | "region": "us-west-2", 9 | "resources": [ 10 | "i-abcd1111" 11 | ], 12 | "detail": { 13 | "eventArn": "arn:aws:health:us-west-2::event/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED_90353408594353980", 14 | "service": "EC2", 15 | "eventTypeCode": "AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED", 16 | "eventTypeCategory": "issue", 17 | "startTime": "Sat, 05 Jun 2016 15:10:09 GMT", 18 | "eventDescription": [ 19 | { 20 | "language": "en_US", 21 | "latestDescription": "A description of the event will be provided here" 22 | } 23 | ], 24 | "affectedEntities": [ 25 | { 26 | "entityValue": "i-abcd1111", 27 | "tags": { 28 | "stage": "prod", 29 | "app": "my-app" 30 | } 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /functions/events/cloudwatch_alarm.json: -------------------------------------------------------------------------------- 1 | { 2 | "AlarmName": "Example", 3 | "AlarmDescription": "Example alarm description.", 4 | "AWSAccountId": "000000000000", 5 | "NewStateValue": "ALARM", 6 | "NewStateReason": "Threshold Crossed", 7 | "StateChangeTime": "2017-01-12T16:30:42.236+0000", 8 | "Region": "EU - Ireland", 9 | "OldStateValue": "OK" 10 | } 11 | -------------------------------------------------------------------------------- /functions/events/guardduty_finding_high.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail-type": "GuardDuty Finding", 3 | "region": "us-east-1", 4 | "detail": { 5 | "id": "sample-id-2", 6 | "title": "SAMPLE Unprotected port on EC2 instance i-123123123 is being probed", 7 | "severity": 9, 8 | "accountId": "123456789", 9 | "description": "EC2 instance has an unprotected port which is being probed by a known malicious host.", 10 | "type": "Recon:EC2 PortProbeUnprotectedPort", 11 | "service": { 12 | "eventFirstSeen": "2020-01-02T01:02:03Z", 13 | "eventLastSeen": "2020-01-03T01:02:03Z", 14 | "count": 1234 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /functions/events/guardduty_finding_low.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail-type": "GuardDuty Finding", 3 | "region": "us-east-1", 4 | "detail": { 5 | "id": "sample-id-2", 6 | "title": "SAMPLE Unprotected port on EC2 instance i-123123123 is being probed", 7 | "severity": 2, 8 | "accountId": "123456789", 9 | "description": "EC2 instance has an unprotected port which is being probed by a known malicious host.", 10 | "type": "Recon:EC2 PortProbeUnprotectedPort", 11 | "service": { 12 | "eventFirstSeen": "2020-01-02T01:02:03Z", 13 | "eventLastSeen": "2020-01-03T01:02:03Z", 14 | "count": 1234 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /functions/events/guardduty_finding_medium.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail-type": "GuardDuty Finding", 3 | "region": "us-east-1", 4 | "detail": { 5 | "id": "sample-id-2", 6 | "title": "SAMPLE Unprotected port on EC2 instance i-123123123 is being probed", 7 | "severity": 5, 8 | "accountId": "123456789", 9 | "description": "EC2 instance has an unprotected port which is being probed by a known malicious host.", 10 | "type": "Recon:EC2 PortProbeUnprotectedPort", 11 | "service": { 12 | "eventFirstSeen": "2020-01-02T01:02:03Z", 13 | "eventLastSeen": "2020-01-03T01:02:03Z", 14 | "count": 1234 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /functions/integration_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Integration Test 4 | ---------------- 5 | 6 | Executes tests against live Slack webhook 7 | 8 | """ 9 | 10 | import os 11 | from pprint import pprint 12 | from typing import List 13 | 14 | import boto3 15 | import pytest 16 | 17 | 18 | @pytest.mark.skip(reason="Execute with`pytest run python integration_test.py`") 19 | def _get_files(directory: str) -> List[str]: 20 | """ 21 | Helper function to get list of files under `directory` 22 | 23 | :params directory: directory to pull list of files from 24 | :returns: list of files names under directory specified 25 | """ 26 | return [ 27 | os.path.join(directory, f) 28 | for f in os.listdir(directory) 29 | if os.path.isfile(os.path.join(directory, f)) 30 | ] 31 | 32 | 33 | @pytest.mark.skip(reason="Execute with`pytest run python integration_test.py`") 34 | def invoke_lambda_handler(): 35 | """ 36 | Invoke lambda handler with sample SNS messages 37 | 38 | Messages should arrive at the live webhook specified 39 | """ 40 | lambda_client = boto3.client("lambda", region_name=REGION) 41 | 42 | # These are SNS messages that invoke the lambda handler; 43 | # the event payload is in the `message` field 44 | messages = _get_files(directory="./messages") 45 | 46 | for message in messages: 47 | with open(message, "r") as mfile: 48 | msg = mfile.read() 49 | response = lambda_client.invoke( 50 | FunctionName=LAMBDA_FUNCTION_NAME, 51 | InvocationType="Event", 52 | Payload=msg, 53 | ) 54 | pprint(response) 55 | 56 | 57 | @pytest.mark.skip(reason="Execute with`pytest run python integration_test.py`") 58 | def publish_event_to_sns_topic(): 59 | """ 60 | Publish sample events to SNS topic created 61 | 62 | Messages should arrive at the live webhook specified 63 | """ 64 | sns_client = boto3.client("sns", region_name=REGION) 65 | 66 | # These are event payloads that will get published 67 | events = _get_files(directory="./events") 68 | 69 | for event in events: 70 | with open(event, "r") as efile: 71 | msg = efile.read() 72 | response = sns_client.publish( 73 | TopicArn=SNS_TOPIC_ARN, 74 | Message=msg, 75 | Subject=event, 76 | ) 77 | pprint(response) 78 | 79 | 80 | if __name__ == "__main__": 81 | # Sourcing env vars set by `notify-slack-simple` example 82 | with open(".int.env", "r") as envvarfile: 83 | for line in envvarfile.readlines(): 84 | (_var, _val) = line.strip().split("=") 85 | os.environ[_var] = _val 86 | 87 | # Not using .get() so it fails loudly if not set (`KeyError`) 88 | REGION = os.environ["REGION"] 89 | LAMBDA_FUNCTION_NAME = os.environ["LAMBDA_FUNCTION_NAME"] 90 | SNS_TOPIC_ARN = os.environ["SNS_TOPIC_ARN"] 91 | 92 | invoke_lambda_handler() 93 | publish_event_to_sns_topic() 94 | -------------------------------------------------------------------------------- /functions/messages/backup.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "EventSource": "aws:sns", 5 | "EventVersion": "1.0", 6 | "EventSubscriptionArn": "arn:aws:sns:...-a3802aa1ed45", 7 | "Sns": { 8 | "Type": "Notification", 9 | "MessageId": "12345678-abcd-123a-def0-abcd1a234567", 10 | "TopicArn": "arn:aws:sns:us-west-1:123456789012:backup-2sqs-sns-topic", 11 | "Subject": "Notification from AWS Backup", 12 | "Message": "An AWS Backup job was completed successfully. Recovery point ARN: arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012d. Resource ARN : arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e. BackupJob ID : 1b2345b2-f22c-4dab-5eb6-bbc7890ed123", 13 | "Timestamp": "2019-08-02T18:46:02.788Z", 14 | "MessageAttributes": { 15 | "EventType": { 16 | "Type": "String", 17 | "Value": "BACKUP_JOB" 18 | }, 19 | "State": { 20 | "Type": "String", 21 | "Value": "COMPLETED" 22 | }, 23 | "AccountId": { 24 | "Type": "String", 25 | "Value": "123456789012" 26 | }, 27 | "Id": { 28 | "Type": "String", 29 | "Value": "1b2345b2-f22c-4dab-5eb6-bbc7890ed123" 30 | }, 31 | "StartTime": { 32 | "Type": "String", 33 | "Value": "2019-09-02T13:48:52.226Z" 34 | } 35 | } 36 | } 37 | }, 38 | { 39 | "EventSource": "aws:sns", 40 | "EventVersion": "1.0", 41 | "EventSubscriptionArn": "arn:aws:sns:...-a3802aa1ed45", 42 | "Sns": { 43 | "Type": "Notification", 44 | "MessageId": "12345678-abcd-123a-def0-abcd1a234567", 45 | "TopicArn": "arn:aws:sns:us-west-1:123456789012:backup-2sqs-sns-topic", 46 | "Subject": "Notification from AWS Backup", 47 | "Message": "An AWS Backup job failed. Resource ARN : arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e. BackupJob ID : 1b2345b2-f22c-4dab-5eb6-bbc7890ed123", 48 | "Timestamp": "2019-08-02T18:46:02.788Z", 49 | "MessageAttributes": { 50 | "EventType": { 51 | "Type": "String", 52 | "Value": "BACKUP_JOB" 53 | }, 54 | "State": { 55 | "Type": "String", 56 | "Value": "FAILED" 57 | }, 58 | "AccountId": { 59 | "Type": "String", 60 | "Value": "123456789012" 61 | }, 62 | "Id": { 63 | "Type": "String", 64 | "Value": "1b2345b2-f22c-4dab-5eb6-bbc7890ed123" 65 | }, 66 | "StartTime": { 67 | "Type": "String", 68 | "Value": "2019-09-02T13:48:52.226Z" 69 | } 70 | } 71 | } 72 | }, 73 | { 74 | "EventSource": "aws:sns", 75 | "EventVersion": "1.0", 76 | "EventSubscriptionArn": "arn:aws:sns:...-a3802aa1ed45", 77 | "Sns": { 78 | "Type": "Notification", 79 | "MessageId": "12345678-abcd-123a-def0-abcd1a234567", 80 | "TopicArn": "arn:aws:sns:us-west-1:123456789012:backup-2sqs-sns-topic", 81 | "Subject": "Notification from AWS Backup", 82 | "Message": "An AWS Backup job failed to complete in time. Resource ARN : arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e. BackupJob ID : 1b2345b2-f22c-4dab-5eb6-bbc7890ed123", 83 | "Timestamp": "2019-08-02T18:46:02.788Z", 84 | "MessageAttributes": { 85 | "EventType": { 86 | "Type": "String", 87 | "Value": "BACKUP_JOB" 88 | }, 89 | "State": { 90 | "Type": "String", 91 | "Value": "EXPIRED" 92 | }, 93 | "AccountId": { 94 | "Type": "String", 95 | "Value": "123456789012" 96 | }, 97 | "Id": { 98 | "Type": "String", 99 | "Value": "1b2345b2-f22c-4dab-5eb6-bbc7890ed123" 100 | }, 101 | "StartTime": { 102 | "Type": "String", 103 | "Value": "2019-09-02T13:48:52.226Z" 104 | } 105 | } 106 | } 107 | } 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /functions/messages/cloudwatch_alarm.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [{ 3 | "EventSource": "aws:sns", 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:us-east-1::ExampleTopic", 6 | "Sns": { 7 | "Type": "Notification", 8 | "MessageId": "f86e3c5b-cd17-1ab8-80e9-c0776d4f1e7a", 9 | "TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic", 10 | "Subject": "'OK: \"DBMigrationRequired\" in EU (London)", 11 | "Message": "{\"AlarmName\": \"DBMigrationRequired\",\"AlarmDescription\": \"App is reporting \\\"A JPA error occurred(Unable to build EntityManagerFactory)\\\"\",\"AWSAccountId\": \"735598076380\",\"NewStateValue\": \"OK\",\"NewStateReason\": \"Threshold Crossed: 1 datapoint [1.0 (12\/02\/19 15:44:00)] was not less than the threshold (1.0).\",\"StateChangeTime\": \"2019-02-12T15:45:24.006+0000\",\"Region\": \"US (Virginia)\",\"OldStateValue\": \"ALARM\",\"Trigger\": {\"MetricName\": \"DBMigrationRequired\",\"Namespace\": \"LogMetrics\",\"StatisticType\": \"Statistic\",\"Statistic\": \"SUM\",\"Unit\": null,\"Dimensions\": [],\"Period\": 60,\"EvaluationPeriods\": 1,\"ComparisonOperator\": \"LessThanThreshold\",\"Threshold\": 1.0,\"TreatMissingData\": \"- TreatMissingData:NonBreaching\",\"EvaluateLowSampleCountPercentile\": \"\"}}", 12 | "Timestamp": "2019-02-12T15:45:24.091Z", 13 | "SignatureVersion": "1", 14 | "Signature": "EXAMPLE", 15 | "SigningCertUrl": "EXAMPLE", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "MessageAttributes": {} 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /functions/messages/dms_notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [{ 3 | "EventSource": "aws:sns", 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:eu-west-1::ExampleTopic", 6 | "Sns": { 7 | "Type": "Notification", 8 | "MessageId": "f86e3c5b-cd17-1ab8-80e9-c0776d4f1e7a", 9 | "TopicArn": "arn:aws:sns:eu-west-1:123456789012:ExampleTopic", 10 | "Subject": "DMS Notification Message", 11 | "Message": "{\"Event Source\": \"replication-task\",\"Event Time\": \"2019-02-12 15:45:24.091\",\"Identifier Link\": \"https:\/\/console.aws.amazon.com\/dms\/home?region=us-east-1#tasks:ids=hello-world\",\"SourceId\": \"hello-world\",\"Event ID\": \"http:\/\/docs.aws.amazon.com\/dms\/latest\/userguide\/CHAP_Events.html#DMS-EVENT-0079 \",\"Event Message\": \"Replication task has stopped.\"}", 12 | "Timestamp": "2019-02-12T15:45:24.091Z", 13 | "SignatureVersion": "1", 14 | "Signature": "EXAMPLE", 15 | "SigningCertUrl": "EXAMPLE", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "MessageAttributes": {} 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /functions/messages/glue_notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [{ 3 | "EventSource": "aws:sns", 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:us-east-2::ExampleTopic", 6 | "Sns": { 7 | "Type": "Notification", 8 | "MessageId": "00337b3f-0982-5cb1-9138-22799c885da9", 9 | "TopicArn": "arn:aws:sns:us-east-2:123456789012:ExampleTopic", 10 | "Subject": "", 11 | "Message": "{\"version\": \"0\",\"id\": \"ad3c3da1-148c-d5da-9a6a-79f1bc9a8a2e\",\"detail-type\": \"Glue Job State Change\",\"source\": \"aws.glue\",\"account\": \"000000000000\",\"time\": \"2021-06-18T12:34:06Z\",\"region\": \"us-east-2\",\"resources\": [],\"detail\": {\"jobName\": \"test_job\",\"severity\": \"ERROR\",\"state\": \"FAILED\",\"jobRunId\": \"jr_ca2144d747b45ad412d3c66a1b6934b6b27aa252be9a21a95c54dfaa224a1925\",\"message\": \"SystemExit: 1\"}}", 12 | "Timestamp": "2021-06-18T12:34:09.509Z", 13 | "SignatureVersion": "1", 14 | "Signature": "EXAMPLE", 15 | "SigningCertUrl": "EXAMPLE", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "MessageAttributes": {} 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /functions/messages/guardduty_finding.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [{ 3 | "EventSource": "aws:sns", 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:us-gov-east-1::ExampleTopic", 6 | "Sns": { 7 | "Type": "Notification", 8 | "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", 9 | "TopicArn": "arn:aws:sns:us-gov-east-1:123456789012:ExampleTopic", 10 | "Subject": "GuardDuty Finding", 11 | "Message": "{\"detail-type\": \"GuardDuty Finding\",\"region\": \"us-gov-east-1\",\"detail\": {\"id\": \"sample-id-2\",\"title\": \"SAMPLE Unprotected port on EC2 instance i-123123123 is being probed\",\"severity\": 9,\"accountId\":\"123456789\",\"description\": \"EC2 instance has an unprotected port which is being probed by a known malicious host.\",\"type\": \"Recon:EC2 PortProbeUnprotectedPort\",\"service\": {\"eventFirstSeen\": \"2020-01-02T01:02:03Z\",\"eventLastSeen\": \"2020-01-03T01:02:03Z\",\"count\": 1234}}}", 12 | "Timestamp": "1970-01-01T00:00:00.000Z", 13 | "SignatureVersion": "1", 14 | "Signature": "EXAMPLE", 15 | "SigningCertUrl": "EXAMPLE", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "MessageAttributes": {} 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /functions/messages/text_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [{ 3 | "EventSource": "aws:sns", 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:us-gov-west-1::ExampleTopic", 6 | "Sns": { 7 | "Type": "Notification", 8 | "MessageId": "f86e3c5b-cd17-1ab8-80e9-c0776d4f1e7a", 9 | "TopicArn": "arn:aws:sns:us-gov-west-1:123456789012:ExampleTopic", 10 | "Subject": "All Fine", 11 | "Message": "This\nis\na typical multi-line\nmessage from SNS!\n\nHave a ~good~ amazing day! :)", 12 | "Timestamp": "2019-02-12T15:45:24.091Z", 13 | "SignatureVersion": "1", 14 | "Signature": "EXAMPLE", 15 | "SigningCertUrl": "EXAMPLE", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "MessageAttributes": {} 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /functions/mylambda.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.6 2 | # CUSTOM LAMBDA FUNCTION 3 | 4 | import json 5 | import os 6 | 7 | import urllib3 8 | 9 | http = urllib3.PoolManager() 10 | 11 | 12 | def lambda_handler(event, context): 13 | url = os.environ["SLACK_WEBHOOK_URL"] 14 | msg = { 15 | "channel": "#channel-name", 16 | "username": "Prometheus", 17 | "text": event["Records"][0]["Sns"]["Message"], 18 | "icon_emoji": "", 19 | } 20 | 21 | encoded_msg = json.dumps(msg).encode("utf-8") 22 | resp = http.request("POST", url, body=encoded_msg) 23 | print( 24 | { 25 | "message": event["Records"][0]["Sns"]["Message"], 26 | "status_code": resp.status, 27 | "response": resp.data, 28 | } 29 | ) 30 | -------------------------------------------------------------------------------- /functions/notify_slack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Notify Slack 4 | ------------ 5 | 6 | Receives event payloads that are parsed and sent to Slack 7 | 8 | """ 9 | 10 | import base64 11 | import json 12 | import logging 13 | import os 14 | import re 15 | import urllib.parse 16 | import urllib.request 17 | from enum import Enum 18 | from typing import Any, Dict, Optional, Union, cast 19 | from urllib.error import HTTPError 20 | 21 | import boto3 22 | 23 | # Set default region if not provided 24 | REGION = os.environ.get("AWS_REGION", "us-east-1") 25 | 26 | # Create client so its cached/frozen between invocations 27 | KMS_CLIENT = boto3.client("kms", region_name=REGION) 28 | 29 | SECURITY_HUB_CLIENT = boto3.client('securityhub', region_name=REGION) 30 | 31 | 32 | class AwsService(Enum): 33 | """AWS service supported by function""" 34 | 35 | cloudwatch = "cloudwatch" 36 | guardduty = "guardduty" 37 | securityhub = "securityhub" 38 | 39 | 40 | def decrypt_url(encrypted_url: str) -> str: 41 | """Decrypt encrypted URL with KMS 42 | 43 | :param encrypted_url: URL to decrypt with KMS 44 | :returns: plaintext URL 45 | """ 46 | try: 47 | decrypted_payload = KMS_CLIENT.decrypt( 48 | CiphertextBlob=base64.b64decode(encrypted_url) 49 | ) 50 | return decrypted_payload["Plaintext"].decode() 51 | except Exception: 52 | logging.exception("Failed to decrypt URL with KMS") 53 | return "" 54 | 55 | 56 | def get_service_url(region: str, service: str) -> str: 57 | """Get the appropriate service URL for the region 58 | 59 | :param region: name of the AWS region 60 | :param service: name of the AWS service 61 | :returns: AWS console url formatted for the region and service provided 62 | """ 63 | try: 64 | service_name = AwsService[service].value 65 | if region.startswith("us-gov-"): 66 | return f"https://console.amazonaws-us-gov.com/{service_name}/home?region={region}" 67 | else: 68 | return f"https://console.aws.amazon.com/{service_name}/home?region={region}" 69 | 70 | except KeyError: 71 | print(f"Service {service} is currently not supported") 72 | raise 73 | 74 | 75 | class CloudWatchAlarmState(Enum): 76 | """Maps CloudWatch notification state to Slack message format color""" 77 | 78 | OK = "good" 79 | INSUFFICIENT_DATA = "warning" 80 | ALARM = "danger" 81 | 82 | 83 | def format_cloudwatch_alarm(message: Dict[str, Any], region: str) -> Dict[str, Any]: 84 | """Format CloudWatch alarm event into Slack message format 85 | 86 | :params message: SNS message body containing CloudWatch alarm event 87 | :region: AWS region where the event originated from 88 | :returns: formatted Slack message payload 89 | """ 90 | 91 | cloudwatch_url = get_service_url(region=region, service="cloudwatch") 92 | alarm_name = message["AlarmName"] 93 | 94 | return { 95 | "color": CloudWatchAlarmState[message["NewStateValue"]].value, 96 | "fallback": f"Alarm {alarm_name} triggered", 97 | "fields": [ 98 | {"title": "Alarm Name", "value": f"`{alarm_name}`", "short": True}, 99 | { 100 | "title": "Alarm Description", 101 | "value": f"`{message['AlarmDescription']}`", 102 | "short": False, 103 | }, 104 | { 105 | "title": "Alarm reason", 106 | "value": f"`{message['NewStateReason']}`", 107 | "short": False, 108 | }, 109 | { 110 | "title": "Old State", 111 | "value": f"`{message['OldStateValue']}`", 112 | "short": True, 113 | }, 114 | { 115 | "title": "Current State", 116 | "value": f"`{message['NewStateValue']}`", 117 | "short": True, 118 | }, 119 | { 120 | "title": "Link to Alarm", 121 | "value": f"{cloudwatch_url}#alarm:alarmFilter=ANY;name={urllib.parse.quote(alarm_name)}", 122 | "short": False, 123 | }, 124 | ], 125 | "text": f"AWS CloudWatch notification - {message['AlarmName']}", 126 | } 127 | 128 | 129 | def format_aws_security_hub(message: Dict[str, Any], region: str) -> Dict[str, Any]: 130 | """ 131 | Format AWS Security Hub finding event into Slack message format 132 | 133 | :params message: SNS message body containing SecurityHub finding event 134 | :params region: AWS region where the event originated from 135 | :returns: formatted Slack message payload 136 | """ 137 | service_url = get_service_url(region=region, service="securityhub") 138 | finding = message["detail"]["findings"][0] 139 | 140 | # Switch Status From New To Notified To Prevent Repeated Messages 141 | try: 142 | compliance_status = finding["Compliance"].get("Status", "UNKNOWN") 143 | workflow_status = finding["Workflow"].get("Status", "UNKNOWN") 144 | if compliance_status == "FAILED" and workflow_status == "NEW": 145 | notified = SECURITY_HUB_CLIENT.batch_update_findings( 146 | FindingIdentifiers=[{ 147 | 'Id': finding.get('Id'), 148 | 'ProductArn': finding.get("ProductArn") 149 | }], 150 | Workflow={"Status": "NOTIFIED"} 151 | ) 152 | logging.warning(f"Successfully updated finding status to NOTIFIED: {json.dumps(notified)}") 153 | except Exception as e: 154 | logging.error(f"Failed to update finding status: {str(e)}") 155 | pass 156 | 157 | if finding.get("ProductName") == "Inspector": 158 | severity = finding["Severity"].get("Label", "INFORMATIONAL") 159 | compliance_status = finding["Compliance"].get("Status", "UNKNOWN") 160 | 161 | Id = finding.get("Id", "No ID Provided") 162 | title = finding.get("Title", "No Title Provided") 163 | description = finding.get("Description", "No Description Provided") 164 | control_id = finding['ProductFields'].get('ControlId', 'N/A') 165 | control_url = service_url + f"#/controls/{control_id}" 166 | aws_account_id = finding.get('AwsAccountId', 'Unknown Account') 167 | first_observed = finding.get('FirstObservedAt', 'Unknown Date') 168 | last_updated = finding.get('UpdatedAt', 'Unknown Date') 169 | affected_resource = finding['Resources'][0].get('Id', 'Unknown Resource') 170 | remediation_url = finding.get("Remediation", {}).get("Recommendation", {}).get("Url", "#") 171 | 172 | finding_base_path = "#/findings?search=Id%3D%255Coperator%255C%253AEQUALS%255C%253A" 173 | double_encoded_id = urllib.parse.quote(urllib.parse.quote(Id, safe=''), safe='') 174 | finding_url = f"{service_url}{finding_base_path}{double_encoded_id}" 175 | generator_id = finding.get("GeneratorId", "Unknown Generator") 176 | 177 | color = SecurityHubSeverity.get(severity.upper(), SecurityHubSeverity.INFORMATIONAL).value 178 | if compliance_status == "PASSED": 179 | color = "#4BB543" 180 | 181 | slack_message = { 182 | "color": color, 183 | "fallback": f"Inspector Finding: {title}", 184 | "fields": [ 185 | {"title": "Title", "value": f"`{title}`", "short": False}, 186 | {"title": "Description", "value": f"`{description}`", "short": False}, 187 | {"title": "Compliance Status", "value": f"`{compliance_status}`", "short": True}, 188 | {"title": "Severity", "value": f"`{severity}`", "short": True}, 189 | {"title": "Control ID", "value": f"`{control_id}`", "short": True}, 190 | {"title": "Account ID", "value": f"`{aws_account_id}`", "short": True}, 191 | {"title": "First Observed", "value": f"`{first_observed}`", "short": True}, 192 | {"title": "Last Updated", "value": f"`{last_updated}`", "short": True}, 193 | {"title": "Affected Resource", "value": f"`{affected_resource}`", "short": False}, 194 | {"title": "Generator", "value": f"`{generator_id}`", "short": False}, 195 | {"title": "Control Url", "value": f"`{control_url}`", "short": False}, 196 | {"title": "Finding Url", "value": f"`{finding_url}`", "short": False}, 197 | {"title": "Remediation", "value": f"`{remediation_url}`", "short": False}, 198 | ], 199 | "text": f"AWS Inspector Finding - {title}", 200 | } 201 | 202 | return slack_message 203 | 204 | if finding.get("ProductName") == "Security Hub": 205 | severity = finding["Severity"].get("Label", "INFORMATIONAL") 206 | compliance_status = finding["Compliance"].get("Status", "UNKNOWN") 207 | 208 | Id = finding.get("Id", "No ID Provided") 209 | title = finding.get("Title", "No Title Provided") 210 | description = finding.get("Description", "No Description Provided") 211 | control_id = finding['ProductFields'].get('ControlId', 'N/A') 212 | control_url = service_url + f"#/controls/{control_id}" 213 | aws_account_id = finding.get('AwsAccountId', 'Unknown Account') 214 | first_observed = finding.get('FirstObservedAt', 'Unknown Date') 215 | last_updated = finding.get('UpdatedAt', 'Unknown Date') 216 | affected_resource = finding['Resources'][0].get('Id', 'Unknown Resource') 217 | remediation_url = finding.get("Remediation", {}).get("Recommendation", {}).get("Url", "#") 218 | generator_id = finding.get("GeneratorId", "Unknown Generator") 219 | 220 | finding_base_path = "#/findings?search=Id%3D%255Coperator%255C%253AEQUALS%255C%253A" 221 | double_encoded_id = urllib.parse.quote(urllib.parse.quote(Id, safe=''), safe='') 222 | finding_url = f"{service_url}{finding_base_path}{double_encoded_id}" 223 | 224 | color = SecurityHubSeverity.get(severity.upper(), SecurityHubSeverity.INFORMATIONAL).value 225 | if compliance_status == "PASSED": 226 | color = "#4BB543" 227 | 228 | slack_message = { 229 | "color": color, 230 | "fallback": f"Security Hub Finding: {title}", 231 | "fields": [ 232 | {"title": "Title", "value": f"`{title}`", "short": False}, 233 | {"title": "Description", "value": f"`{description}`", "short": False}, 234 | {"title": "Compliance Status", "value": f"`{compliance_status}`", "short": True}, 235 | {"title": "Severity", "value": f"`{severity}`", "short": True}, 236 | {"title": "Control ID", "value": f"`{control_id}`", "short": True}, 237 | {"title": "Account ID", "value": f"`{aws_account_id}`", "short": True}, 238 | {"title": "First Observed", "value": f"`{first_observed}`", "short": True}, 239 | {"title": "Last Updated", "value": f"`{last_updated}`", "short": True}, 240 | {"title": "Affected Resource", "value": f"`{affected_resource}`", "short": False}, 241 | {"title": "Generator", "value": f"`{generator_id}`", "short": False}, 242 | {"title": "Control Url", "value": f"`{control_url}`", "short": False}, 243 | {"title": "Finding Url", "value": f"`{finding_url}`", "short": False}, 244 | {"title": "Remediation", "value": f"`{remediation_url}`", "short": False}, 245 | ], 246 | "text": f"AWS Security Hub Finding - {title}", 247 | } 248 | 249 | return slack_message 250 | 251 | return format_default(message=message) 252 | 253 | 254 | class SecurityHubSeverity(Enum): 255 | """Maps Security Hub finding severity to Slack message format color""" 256 | 257 | CRITICAL = "danger" 258 | HIGH = "danger" 259 | MEDIUM = "warning" 260 | LOW = "#777777" 261 | INFORMATIONAL = "#439FE0" 262 | 263 | @staticmethod 264 | def get(name, default): 265 | try: 266 | return SecurityHubSeverity[name] 267 | except KeyError: 268 | return default 269 | 270 | 271 | class GuardDutyFindingSeverity(Enum): 272 | """Maps GuardDuty finding severity to Slack message format color""" 273 | 274 | Low = "#777777" 275 | Medium = "warning" 276 | High = "danger" 277 | 278 | 279 | def format_guardduty_finding(message: Dict[str, Any], region: str) -> Dict[str, Any]: 280 | """ 281 | Format GuardDuty finding event into Slack message format 282 | 283 | :params message: SNS message body containing GuardDuty finding event 284 | :params region: AWS region where the event originated from 285 | :returns: formatted Slack message payload 286 | """ 287 | 288 | guardduty_url = get_service_url(region=region, service="guardduty") 289 | detail = message["detail"] 290 | service = detail.get("service", {}) 291 | severity_score = detail.get("severity") 292 | 293 | if severity_score < 4.0: 294 | severity = "Low" 295 | elif severity_score < 7.0: 296 | severity = "Medium" 297 | else: 298 | severity = "High" 299 | 300 | return { 301 | "color": GuardDutyFindingSeverity[severity].value, 302 | "fallback": f"GuardDuty Finding: {detail.get('title')}", 303 | "fields": [ 304 | { 305 | "title": "Description", 306 | "value": f"`{detail['description']}`", 307 | "short": False, 308 | }, 309 | { 310 | "title": "Finding Type", 311 | "value": f"`{detail['type']}`", 312 | "short": False, 313 | }, 314 | { 315 | "title": "First Seen", 316 | "value": f"`{service['eventFirstSeen']}`", 317 | "short": True, 318 | }, 319 | { 320 | "title": "Last Seen", 321 | "value": f"`{service['eventLastSeen']}`", 322 | "short": True, 323 | }, 324 | {"title": "Severity", "value": f"`{severity}`", "short": True}, 325 | {"title": "Account ID", "value": f"`{detail['accountId']}`", "short": True}, 326 | { 327 | "title": "Count", 328 | "value": f"`{service['count']}`", 329 | "short": True, 330 | }, 331 | { 332 | "title": "Link to Finding", 333 | "value": f"{guardduty_url}#/findings?search=id%3D{detail['id']}", 334 | "short": False, 335 | }, 336 | ], 337 | "text": f"AWS GuardDuty Finding - {detail.get('title')}", 338 | } 339 | 340 | 341 | class AwsHealthCategory(Enum): 342 | """Maps AWS Health eventTypeCategory to Slack message format color 343 | 344 | eventTypeCategory 345 | The category code of the event. The possible values are issue, 346 | accountNotification, and scheduledChange. 347 | """ 348 | 349 | accountNotification = "#777777" 350 | scheduledChange = "warning" 351 | issue = "danger" 352 | 353 | 354 | def format_aws_health(message: Dict[str, Any], region: str) -> Dict[str, Any]: 355 | """ 356 | Format AWS Health event into Slack message format 357 | 358 | :params message: SNS message body containing AWS Health event 359 | :params region: AWS region where the event originated from 360 | :returns: formatted Slack message payload 361 | """ 362 | 363 | aws_health_url = ( 364 | f"https://phd.aws.amazon.com/phd/home?region={region}#/dashboard/open-issues" 365 | ) 366 | detail = message["detail"] 367 | resources = message.get("resources", "") 368 | service = detail.get("service", "") 369 | 370 | return { 371 | "color": AwsHealthCategory[detail["eventTypeCategory"]].value, 372 | "text": f"New AWS Health Event for {service}", 373 | "fallback": f"New AWS Health Event for {service}", 374 | "fields": [ 375 | {"title": "Affected Service", "value": f"`{service}`", "short": True}, 376 | { 377 | "title": "Affected Region", 378 | "value": f"`{message.get('region')}`", 379 | "short": True, 380 | }, 381 | { 382 | "title": "Code", 383 | "value": f"`{detail.get('eventTypeCode')}`", 384 | "short": False, 385 | }, 386 | { 387 | "title": "Event Description", 388 | "value": f"`{detail['eventDescription'][0]['latestDescription']}`", 389 | "short": False, 390 | }, 391 | { 392 | "title": "Affected Resources", 393 | "value": f"`{', '.join(resources)}`", 394 | "short": False, 395 | }, 396 | { 397 | "title": "Start Time", 398 | "value": f"`{detail.get('startTime', '')}`", 399 | "short": True, 400 | }, 401 | { 402 | "title": "End Time", 403 | "value": f"`{detail.get('endTime', '')}`", 404 | "short": True, 405 | }, 406 | { 407 | "title": "Link to Event", 408 | "value": f"{aws_health_url}", 409 | "short": False, 410 | }, 411 | ], 412 | } 413 | 414 | 415 | def aws_backup_field_parser(message: str) -> Dict[str, str]: 416 | """ 417 | Parser for AWS Backup event message. It extracts the fields from the message and returns a dictionary. 418 | 419 | :params message: message containing AWS Backup event 420 | :returns: dictionary containing the fields extracted from the message 421 | """ 422 | # Order is somewhat important, working in reverse order of the message payload 423 | # to reduce right most matched values 424 | field_names = { 425 | "BackupJob ID": r"(BackupJob ID : ).*", 426 | "Resource ARN": r"(Resource ARN : ).*[.]", 427 | "Recovery point ARN": r"(Recovery point ARN: ).*[.]", 428 | } 429 | fields = {} 430 | 431 | for fname, freg in field_names.items(): 432 | match = re.search(freg, message) 433 | if match: 434 | value = match.group(0).split(" ")[-1] 435 | fields[fname] = value.removesuffix(".") 436 | 437 | # Remove the matched field from the message 438 | message = message.replace(match.group(0), "") 439 | 440 | return fields 441 | 442 | 443 | def format_aws_backup(message: str) -> Dict[str, Any]: 444 | """ 445 | Format AWS Backup event into Slack message format 446 | 447 | :params message: SNS message body containing AWS Backup event 448 | :returns: formatted Slack message payload 449 | """ 450 | 451 | fields: list[Dict[str, Any]] = [] 452 | attachments = {} 453 | 454 | title = message.split(".")[0] 455 | 456 | if "failed" in title: 457 | title = f"⚠️ {title}" 458 | 459 | if "completed" in title: 460 | title = f"✅ {title}" 461 | 462 | fields.append({"title": title}) 463 | 464 | backup_fields = aws_backup_field_parser(message) 465 | 466 | for k, v in backup_fields.items(): 467 | fields.append({"value": k, "short": False}) 468 | fields.append({"value": f"`{v}`", "short": False}) 469 | 470 | attachments["fields"] = fields # type: ignore 471 | 472 | return attachments 473 | 474 | 475 | def format_default( 476 | message: Union[str, Dict], subject: Optional[str] = None 477 | ) -> Dict[str, Any]: 478 | """ 479 | Default formatter, converting event into Slack message format 480 | 481 | :params message: SNS message body containing message/event 482 | :returns: formatted Slack message payload 483 | """ 484 | 485 | attachments = { 486 | "fallback": "A new message", 487 | "text": "AWS notification", 488 | "title": subject if subject else "Message", 489 | "mrkdwn_in": ["value"], 490 | } 491 | fields = [] 492 | 493 | if type(message) is dict: 494 | for k, v in message.items(): 495 | value = f"{json.dumps(v)}" if isinstance(v, (dict, list)) else str(v) 496 | fields.append({"title": k, "value": f"`{value}`", "short": len(value) < 25}) 497 | else: 498 | fields.append({"value": message, "short": False}) 499 | 500 | if fields: 501 | attachments["fields"] = fields # type: ignore 502 | 503 | return attachments 504 | 505 | 506 | def parse_notification(message: Dict[str, Any], subject: Optional[str], region: str) -> Optional[Dict]: 507 | """ 508 | Parse notification message and format into Slack message payload 509 | 510 | :params message: SNS message body notification payload 511 | :params subject: Optional subject line for Slack notification 512 | :params region: AWS region where the event originated from 513 | :returns: Slack message payload 514 | """ 515 | if "AlarmName" in message: 516 | return format_cloudwatch_alarm(message=message, region=region) 517 | if isinstance(message, Dict) and message.get("detail-type") == "GuardDuty Finding": 518 | return format_guardduty_finding(message=message, region=message["region"]) 519 | if isinstance(message, Dict) and message.get("detail-type") == "Security Hub Findings - Imported": 520 | return format_aws_security_hub(message=message, region=message["region"]) 521 | if isinstance(message, Dict) and message.get("detail-type") == "AWS Health Event": 522 | return format_aws_health(message=message, region=message["region"]) 523 | if subject == "Notification from AWS Backup": 524 | return format_aws_backup(message=str(message)) 525 | return format_default(message=message, subject=subject) 526 | 527 | 528 | def get_slack_message_payload( 529 | message: Union[str, Dict], region: str, subject: Optional[str] = None 530 | ) -> Dict: 531 | """ 532 | Parse notification message and format into Slack message payload 533 | 534 | :params message: SNS message body notification payload 535 | :params region: AWS region where the event originated from 536 | :params subject: Optional subject line for Slack notification 537 | :returns: Slack message payload 538 | """ 539 | 540 | slack_channel = os.environ["SLACK_CHANNEL"] 541 | slack_username = os.environ["SLACK_USERNAME"] 542 | slack_emoji = os.environ["SLACK_EMOJI"] 543 | 544 | payload = { 545 | "channel": slack_channel, 546 | "username": slack_username, 547 | "icon_emoji": slack_emoji, 548 | } 549 | attachment = None 550 | 551 | if isinstance(message, str): 552 | try: 553 | message = json.loads(message) 554 | except json.JSONDecodeError: 555 | logging.info("Not a structured payload, just a string message") 556 | 557 | message = cast(Dict[str, Any], message) 558 | 559 | if "attachments" in message or "text" in message: 560 | payload = {**payload, **message} 561 | else: 562 | attachment = parse_notification(message, subject, region) 563 | 564 | if attachment: 565 | payload["attachments"] = [attachment] # type: ignore 566 | 567 | return payload 568 | 569 | 570 | def send_slack_notification(payload: Dict[str, Any]) -> str: 571 | """ 572 | Send notification payload to Slack 573 | 574 | :params payload: formatted Slack message payload 575 | :returns: response details from sending notification 576 | """ 577 | 578 | slack_url = os.environ["SLACK_WEBHOOK_URL"] 579 | if not slack_url.startswith("http"): 580 | slack_url = decrypt_url(slack_url) 581 | 582 | data = urllib.parse.urlencode({"payload": json.dumps(payload)}).encode("utf-8") 583 | req = urllib.request.Request(slack_url) 584 | 585 | try: 586 | result = urllib.request.urlopen(req, data) 587 | return json.dumps({"code": result.getcode(), "info": result.info().as_string()}) 588 | 589 | except HTTPError as e: 590 | logging.error(f"{e}: result") 591 | return json.dumps({"code": e.getcode(), "info": e.info().as_string()}) 592 | 593 | 594 | def lambda_handler(event: Dict[str, Any], context: Dict[str, Any]) -> str: 595 | """ 596 | Lambda function to parse notification events and forward to Slack 597 | 598 | :param event: lambda expected event object 599 | :param context: lambda expected context object 600 | :returns: none 601 | """ 602 | 603 | if os.environ.get("LOG_EVENTS", "False") == "True": 604 | logging.info("Event logging enabled: %s", json.dumps(event)) 605 | 606 | for record in event["Records"]: 607 | sns = record["Sns"] 608 | subject = sns["Subject"] 609 | message = sns["Message"] 610 | region = sns["TopicArn"].split(":")[3] 611 | 612 | payload = get_slack_message_payload( 613 | message=message, region=region, subject=subject 614 | ) 615 | response = send_slack_notification(payload=payload) 616 | 617 | if json.loads(response)["code"] != 200: 618 | response_info = json.loads(response)["info"] 619 | logging.error( 620 | f"Error: received status `{response_info}` using event `{event}` and context `{context}`" 621 | ) 622 | 623 | return response 624 | -------------------------------------------------------------------------------- /functions/notify_slack_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Slack Notification Test 4 | ----------------------- 5 | 6 | Unit tests for `notify_slack.py` 7 | 8 | """ 9 | 10 | import ast 11 | import os 12 | 13 | import notify_slack 14 | import pytest 15 | 16 | 17 | def test_sns_get_slack_message_payload_snapshots(snapshot, monkeypatch): 18 | """ 19 | Compare outputs of get_slack_message_payload() with snapshots stored 20 | 21 | Run `pipenv run test:updatesnapshots` to update snapshot images 22 | """ 23 | 24 | monkeypatch.setenv("SLACK_CHANNEL", "slack_testing_sandbox") 25 | monkeypatch.setenv("SLACK_USERNAME", "notify_slack_test") 26 | monkeypatch.setenv("SLACK_EMOJI", ":aws:") 27 | 28 | # These are SNS messages that invoke the lambda handler; the event payload is in the 29 | # `message` field 30 | _dir = "./messages" 31 | messages = [f for f in os.listdir(_dir) if os.path.isfile(os.path.join(_dir, f))] 32 | 33 | for file in messages: 34 | with open(os.path.join(_dir, file), "r") as ofile: 35 | event = ast.literal_eval(ofile.read()) 36 | 37 | attachments = [] 38 | # These are as delivered wrapped in an SNS message payload so we unpack 39 | for record in event["Records"]: 40 | sns = record["Sns"] 41 | subject = sns["Subject"] 42 | message = sns["Message"] 43 | region = sns["TopicArn"].split(":")[3] 44 | 45 | attachment = notify_slack.get_slack_message_payload( 46 | message=message, region=region, subject=subject 47 | ) 48 | attachments.append(attachment) 49 | 50 | filename = os.path.basename(file) 51 | snapshot.assert_match(attachments, f"message_{filename}") 52 | 53 | 54 | def test_event_get_slack_message_payload_snapshots(snapshot, monkeypatch): 55 | """ 56 | Compare outputs of get_slack_message_payload() with snapshots stored 57 | 58 | Run `pipenv run test:updatesnapshots` to update snapshot images 59 | """ 60 | 61 | monkeypatch.setenv("SLACK_CHANNEL", "slack_testing_sandbox") 62 | monkeypatch.setenv("SLACK_USERNAME", "notify_slack_test") 63 | monkeypatch.setenv("SLACK_EMOJI", ":aws:") 64 | 65 | # These are just the raw events that will be converted to JSON string and 66 | # sent via SNS message 67 | _dir = "./events" 68 | events = [f for f in os.listdir(_dir) if os.path.isfile(os.path.join(_dir, f))] 69 | 70 | for file in events: 71 | with open(os.path.join(_dir, file), "r") as ofile: 72 | event = ast.literal_eval(ofile.read()) 73 | 74 | attachment = notify_slack.get_slack_message_payload( 75 | message=event, region="us-east-1", subject="bar" 76 | ) 77 | attachments = [attachment] 78 | 79 | filename = os.path.basename(file) 80 | snapshot.assert_match(attachments, f"event_{filename}") 81 | 82 | 83 | def test_environment_variables_set(monkeypatch): 84 | """ 85 | Should pass since environment variables are provided 86 | """ 87 | 88 | monkeypatch.setenv("SLACK_CHANNEL", "slack_testing_sandbox") 89 | monkeypatch.setenv("SLACK_USERNAME", "notify_slack_test") 90 | monkeypatch.setenv("SLACK_EMOJI", ":aws:") 91 | monkeypatch.setenv( 92 | "SLACK_WEBHOOK_URL", "https://hooks.slack.com/services/YOUR/WEBOOK/URL" 93 | ) 94 | 95 | with open(os.path.join("./messages/text_message.json"), "r") as efile: 96 | event = ast.literal_eval(efile.read()) 97 | 98 | for record in event["Records"]: 99 | sns = record["Sns"] 100 | subject = sns["Subject"] 101 | message = sns["Message"] 102 | region = sns["TopicArn"].split(":")[3] 103 | 104 | notify_slack.get_slack_message_payload( 105 | message=message, region=region, subject=subject 106 | ) 107 | 108 | 109 | def test_environment_variables_missing(): 110 | """ 111 | Should pass since environment variables are NOT provided and 112 | will raise a `KeyError` 113 | """ 114 | with pytest.raises(KeyError): 115 | # will raise before parsing/validation 116 | notify_slack.get_slack_message_payload(message={}, region="foo", subject="bar") 117 | 118 | 119 | @pytest.mark.parametrize( 120 | "region,service,expected", 121 | [ 122 | ( 123 | "us-east-1", 124 | "cloudwatch", 125 | "https://console.aws.amazon.com/cloudwatch/home?region=us-east-1", 126 | ), 127 | ( 128 | "us-gov-east-1", 129 | "cloudwatch", 130 | "https://console.amazonaws-us-gov.com/cloudwatch/home?region=us-gov-east-1", 131 | ), 132 | ( 133 | "us-east-1", 134 | "guardduty", 135 | "https://console.aws.amazon.com/guardduty/home?region=us-east-1", 136 | ), 137 | ( 138 | "us-gov-east-1", 139 | "guardduty", 140 | "https://console.amazonaws-us-gov.com/guardduty/home?region=us-gov-east-1", 141 | ), 142 | ], 143 | ) 144 | def test_get_service_url(region, service, expected): 145 | assert notify_slack.get_service_url(region=region, service=service) == expected 146 | 147 | 148 | def test_get_service_url_exception(): 149 | """ 150 | Should raise error since service is not defined in enum 151 | """ 152 | with pytest.raises(KeyError): 153 | notify_slack.get_service_url(region="us-east-1", service="athena") 154 | -------------------------------------------------------------------------------- /functions/snapshots/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-notify-slack/0a6a45ee485f3fae6773b2ce9a0bbf24cbbb42a6/functions/snapshots/__init__.py -------------------------------------------------------------------------------- /functions/snapshots/snap_notify_slack_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # snapshottest: v1 - https://goo.gl/zC4yUc 3 | from __future__ import unicode_literals 4 | 5 | from snapshottest import Snapshot 6 | 7 | 8 | snapshots = Snapshot() 9 | 10 | snapshots['test_event_get_slack_message_payload_snapshots event_aws_health_event.json'] = [ 11 | { 12 | 'attachments': [ 13 | { 14 | 'color': 'danger', 15 | 'fallback': 'New AWS Health Event for EC2', 16 | 'fields': [ 17 | { 18 | 'short': True, 19 | 'title': 'Affected Service', 20 | 'value': '`EC2`' 21 | }, 22 | { 23 | 'short': True, 24 | 'title': 'Affected Region', 25 | 'value': '`us-west-2`' 26 | }, 27 | { 28 | 'short': False, 29 | 'title': 'Code', 30 | 'value': '`AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED`' 31 | }, 32 | { 33 | 'short': False, 34 | 'title': 'Event Description', 35 | 'value': '`A description of the event will be provided here`' 36 | }, 37 | { 38 | 'short': False, 39 | 'title': 'Affected Resources', 40 | 'value': '`i-abcd1111`' 41 | }, 42 | { 43 | 'short': True, 44 | 'title': 'Start Time', 45 | 'value': '`Sat, 05 Jun 2016 15:10:09 GMT`' 46 | }, 47 | { 48 | 'short': True, 49 | 'title': 'End Time', 50 | 'value': '``' 51 | }, 52 | { 53 | 'short': False, 54 | 'title': 'Link to Event', 55 | 'value': 'https://phd.aws.amazon.com/phd/home?region=us-west-2#/dashboard/open-issues' 56 | } 57 | ], 58 | 'text': 'New AWS Health Event for EC2' 59 | } 60 | ], 61 | 'channel': 'slack_testing_sandbox', 62 | 'icon_emoji': ':aws:', 63 | 'username': 'notify_slack_test' 64 | } 65 | ] 66 | 67 | snapshots['test_event_get_slack_message_payload_snapshots event_cloudwatch_alarm.json'] = [ 68 | { 69 | 'attachments': [ 70 | { 71 | 'color': 'danger', 72 | 'fallback': 'Alarm Example triggered', 73 | 'fields': [ 74 | { 75 | 'short': True, 76 | 'title': 'Alarm Name', 77 | 'value': '`Example`' 78 | }, 79 | { 80 | 'short': False, 81 | 'title': 'Alarm Description', 82 | 'value': '`Example alarm description.`' 83 | }, 84 | { 85 | 'short': False, 86 | 'title': 'Alarm reason', 87 | 'value': '`Threshold Crossed`' 88 | }, 89 | { 90 | 'short': True, 91 | 'title': 'Old State', 92 | 'value': '`OK`' 93 | }, 94 | { 95 | 'short': True, 96 | 'title': 'Current State', 97 | 'value': '`ALARM`' 98 | }, 99 | { 100 | 'short': False, 101 | 'title': 'Link to Alarm', 102 | 'value': 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#alarm:alarmFilter=ANY;name=Example' 103 | } 104 | ], 105 | 'text': 'AWS CloudWatch notification - Example' 106 | } 107 | ], 108 | 'channel': 'slack_testing_sandbox', 109 | 'icon_emoji': ':aws:', 110 | 'username': 'notify_slack_test' 111 | } 112 | ] 113 | 114 | snapshots['test_event_get_slack_message_payload_snapshots event_guardduty_finding_high.json'] = [ 115 | { 116 | 'attachments': [ 117 | { 118 | 'color': 'danger', 119 | 'fallback': 'GuardDuty Finding: SAMPLE Unprotected port on EC2 instance i-123123123 is being probed', 120 | 'fields': [ 121 | { 122 | 'short': False, 123 | 'title': 'Description', 124 | 'value': '`EC2 instance has an unprotected port which is being probed by a known malicious host.`' 125 | }, 126 | { 127 | 'short': False, 128 | 'title': 'Finding Type', 129 | 'value': '`Recon:EC2 PortProbeUnprotectedPort`' 130 | }, 131 | { 132 | 'short': True, 133 | 'title': 'First Seen', 134 | 'value': '`2020-01-02T01:02:03Z`' 135 | }, 136 | { 137 | 'short': True, 138 | 'title': 'Last Seen', 139 | 'value': '`2020-01-03T01:02:03Z`' 140 | }, 141 | { 142 | 'short': True, 143 | 'title': 'Severity', 144 | 'value': '`High`' 145 | }, 146 | { 147 | 'short': True, 148 | 'title': 'Account ID', 149 | 'value': '`123456789`' 150 | }, 151 | { 152 | 'short': True, 153 | 'title': 'Count', 154 | 'value': '`1234`' 155 | }, 156 | { 157 | 'short': False, 158 | 'title': 'Link to Finding', 159 | 'value': 'https://console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?search=id%3Dsample-id-2' 160 | } 161 | ], 162 | 'text': 'AWS GuardDuty Finding - SAMPLE Unprotected port on EC2 instance i-123123123 is being probed' 163 | } 164 | ], 165 | 'channel': 'slack_testing_sandbox', 166 | 'icon_emoji': ':aws:', 167 | 'username': 'notify_slack_test' 168 | } 169 | ] 170 | 171 | snapshots['test_event_get_slack_message_payload_snapshots event_guardduty_finding_low.json'] = [ 172 | { 173 | 'attachments': [ 174 | { 175 | 'color': '#777777', 176 | 'fallback': 'GuardDuty Finding: SAMPLE Unprotected port on EC2 instance i-123123123 is being probed', 177 | 'fields': [ 178 | { 179 | 'short': False, 180 | 'title': 'Description', 181 | 'value': '`EC2 instance has an unprotected port which is being probed by a known malicious host.`' 182 | }, 183 | { 184 | 'short': False, 185 | 'title': 'Finding Type', 186 | 'value': '`Recon:EC2 PortProbeUnprotectedPort`' 187 | }, 188 | { 189 | 'short': True, 190 | 'title': 'First Seen', 191 | 'value': '`2020-01-02T01:02:03Z`' 192 | }, 193 | { 194 | 'short': True, 195 | 'title': 'Last Seen', 196 | 'value': '`2020-01-03T01:02:03Z`' 197 | }, 198 | { 199 | 'short': True, 200 | 'title': 'Severity', 201 | 'value': '`Low`' 202 | }, 203 | { 204 | 'short': True, 205 | 'title': 'Account ID', 206 | 'value': '`123456789`' 207 | }, 208 | { 209 | 'short': True, 210 | 'title': 'Count', 211 | 'value': '`1234`' 212 | }, 213 | { 214 | 'short': False, 215 | 'title': 'Link to Finding', 216 | 'value': 'https://console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?search=id%3Dsample-id-2' 217 | } 218 | ], 219 | 'text': 'AWS GuardDuty Finding - SAMPLE Unprotected port on EC2 instance i-123123123 is being probed' 220 | } 221 | ], 222 | 'channel': 'slack_testing_sandbox', 223 | 'icon_emoji': ':aws:', 224 | 'username': 'notify_slack_test' 225 | } 226 | ] 227 | 228 | snapshots['test_event_get_slack_message_payload_snapshots event_guardduty_finding_medium.json'] = [ 229 | { 230 | 'attachments': [ 231 | { 232 | 'color': 'warning', 233 | 'fallback': 'GuardDuty Finding: SAMPLE Unprotected port on EC2 instance i-123123123 is being probed', 234 | 'fields': [ 235 | { 236 | 'short': False, 237 | 'title': 'Description', 238 | 'value': '`EC2 instance has an unprotected port which is being probed by a known malicious host.`' 239 | }, 240 | { 241 | 'short': False, 242 | 'title': 'Finding Type', 243 | 'value': '`Recon:EC2 PortProbeUnprotectedPort`' 244 | }, 245 | { 246 | 'short': True, 247 | 'title': 'First Seen', 248 | 'value': '`2020-01-02T01:02:03Z`' 249 | }, 250 | { 251 | 'short': True, 252 | 'title': 'Last Seen', 253 | 'value': '`2020-01-03T01:02:03Z`' 254 | }, 255 | { 256 | 'short': True, 257 | 'title': 'Severity', 258 | 'value': '`Medium`' 259 | }, 260 | { 261 | 'short': True, 262 | 'title': 'Account ID', 263 | 'value': '`123456789`' 264 | }, 265 | { 266 | 'short': True, 267 | 'title': 'Count', 268 | 'value': '`1234`' 269 | }, 270 | { 271 | 'short': False, 272 | 'title': 'Link to Finding', 273 | 'value': 'https://console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?search=id%3Dsample-id-2' 274 | } 275 | ], 276 | 'text': 'AWS GuardDuty Finding - SAMPLE Unprotected port on EC2 instance i-123123123 is being probed' 277 | } 278 | ], 279 | 'channel': 'slack_testing_sandbox', 280 | 'icon_emoji': ':aws:', 281 | 'username': 'notify_slack_test' 282 | } 283 | ] 284 | 285 | snapshots['test_sns_get_slack_message_payload_snapshots message_backup.json'] = [ 286 | { 287 | 'attachments': [ 288 | { 289 | 'fields': [ 290 | { 291 | 'title': '✅ An AWS Backup job was completed successfully' 292 | }, 293 | { 294 | 'short': False, 295 | 'value': 'BackupJob ID' 296 | }, 297 | { 298 | 'short': False, 299 | 'value': '`1b2345b2-f22c-4dab-5eb6-bbc7890ed123`' 300 | }, 301 | { 302 | 'short': False, 303 | 'value': 'Resource ARN' 304 | }, 305 | { 306 | 'short': False, 307 | 'value': '`arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e`' 308 | }, 309 | { 310 | 'short': False, 311 | 'value': 'Recovery point ARN' 312 | }, 313 | { 314 | 'short': False, 315 | 'value': '`arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012d`' 316 | } 317 | ] 318 | } 319 | ], 320 | 'channel': 'slack_testing_sandbox', 321 | 'icon_emoji': ':aws:', 322 | 'username': 'notify_slack_test' 323 | }, 324 | { 325 | 'attachments': [ 326 | { 327 | 'fields': [ 328 | { 329 | 'title': '⚠️ An AWS Backup job failed' 330 | }, 331 | { 332 | 'short': False, 333 | 'value': 'BackupJob ID' 334 | }, 335 | { 336 | 'short': False, 337 | 'value': '`1b2345b2-f22c-4dab-5eb6-bbc7890ed123`' 338 | }, 339 | { 340 | 'short': False, 341 | 'value': 'Resource ARN' 342 | }, 343 | { 344 | 'short': False, 345 | 'value': '`arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e`' 346 | } 347 | ] 348 | } 349 | ], 350 | 'channel': 'slack_testing_sandbox', 351 | 'icon_emoji': ':aws:', 352 | 'username': 'notify_slack_test' 353 | }, 354 | { 355 | 'attachments': [ 356 | { 357 | 'fields': [ 358 | { 359 | 'title': '⚠️ An AWS Backup job failed to complete in time' 360 | }, 361 | { 362 | 'short': False, 363 | 'value': 'BackupJob ID' 364 | }, 365 | { 366 | 'short': False, 367 | 'value': '`1b2345b2-f22c-4dab-5eb6-bbc7890ed123`' 368 | }, 369 | { 370 | 'short': False, 371 | 'value': 'Resource ARN' 372 | }, 373 | { 374 | 'short': False, 375 | 'value': '`arn:aws:ec2:us-west-1:123456789012:volume/vol-012f345df6789012e`' 376 | } 377 | ] 378 | } 379 | ], 380 | 'channel': 'slack_testing_sandbox', 381 | 'icon_emoji': ':aws:', 382 | 'username': 'notify_slack_test' 383 | } 384 | ] 385 | 386 | snapshots['test_sns_get_slack_message_payload_snapshots message_cloudwatch_alarm.json'] = [ 387 | { 388 | 'attachments': [ 389 | { 390 | 'color': 'good', 391 | 'fallback': 'Alarm DBMigrationRequired triggered', 392 | 'fields': [ 393 | { 394 | 'short': True, 395 | 'title': 'Alarm Name', 396 | 'value': '`DBMigrationRequired`' 397 | }, 398 | { 399 | 'short': False, 400 | 'title': 'Alarm Description', 401 | 'value': '`App is reporting "A JPA error occurred(Unable to build EntityManagerFactory)"`' 402 | }, 403 | { 404 | 'short': False, 405 | 'title': 'Alarm reason', 406 | 'value': '`Threshold Crossed: 1 datapoint [1.0 (12/02/19 15:44:00)] was not less than the threshold (1.0).`' 407 | }, 408 | { 409 | 'short': True, 410 | 'title': 'Old State', 411 | 'value': '`ALARM`' 412 | }, 413 | { 414 | 'short': True, 415 | 'title': 'Current State', 416 | 'value': '`OK`' 417 | }, 418 | { 419 | 'short': False, 420 | 'title': 'Link to Alarm', 421 | 'value': 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#alarm:alarmFilter=ANY;name=DBMigrationRequired' 422 | } 423 | ], 424 | 'text': 'AWS CloudWatch notification - DBMigrationRequired' 425 | } 426 | ], 427 | 'channel': 'slack_testing_sandbox', 428 | 'icon_emoji': ':aws:', 429 | 'username': 'notify_slack_test' 430 | } 431 | ] 432 | 433 | snapshots['test_sns_get_slack_message_payload_snapshots message_dms_notification.json'] = [ 434 | { 435 | 'attachments': [ 436 | { 437 | 'fallback': 'A new message', 438 | 'fields': [ 439 | { 440 | 'short': True, 441 | 'title': 'Event Source', 442 | 'value': '`replication-task`' 443 | }, 444 | { 445 | 'short': True, 446 | 'title': 'Event Time', 447 | 'value': '`2019-02-12 15:45:24.091`' 448 | }, 449 | { 450 | 'short': False, 451 | 'title': 'Identifier Link', 452 | 'value': '`https://console.aws.amazon.com/dms/home?region=us-east-1#tasks:ids=hello-world`' 453 | }, 454 | { 455 | 'short': True, 456 | 'title': 'SourceId', 457 | 'value': '`hello-world`' 458 | }, 459 | { 460 | 'short': False, 461 | 'title': 'Event ID', 462 | 'value': '`http://docs.aws.amazon.com/dms/latest/userguide/CHAP_Events.html#DMS-EVENT-0079 `' 463 | }, 464 | { 465 | 'short': False, 466 | 'title': 'Event Message', 467 | 'value': '`Replication task has stopped.`' 468 | } 469 | ], 470 | 'mrkdwn_in': [ 471 | 'value' 472 | ], 473 | 'text': 'AWS notification', 474 | 'title': 'DMS Notification Message' 475 | } 476 | ], 477 | 'channel': 'slack_testing_sandbox', 478 | 'icon_emoji': ':aws:', 479 | 'username': 'notify_slack_test' 480 | } 481 | ] 482 | 483 | snapshots['test_sns_get_slack_message_payload_snapshots message_glue_notification.json'] = [ 484 | { 485 | 'attachments': [ 486 | { 487 | 'fallback': 'A new message', 488 | 'fields': [ 489 | { 490 | 'short': True, 491 | 'title': 'version', 492 | 'value': '`0`' 493 | }, 494 | { 495 | 'short': False, 496 | 'title': 'id', 497 | 'value': '`ad3c3da1-148c-d5da-9a6a-79f1bc9a8a2e`' 498 | }, 499 | { 500 | 'short': True, 501 | 'title': 'detail-type', 502 | 'value': '`Glue Job State Change`' 503 | }, 504 | { 505 | 'short': True, 506 | 'title': 'source', 507 | 'value': '`aws.glue`' 508 | }, 509 | { 510 | 'short': True, 511 | 'title': 'account', 512 | 'value': '`000000000000`' 513 | }, 514 | { 515 | 'short': True, 516 | 'title': 'time', 517 | 'value': '`2021-06-18T12:34:06Z`' 518 | }, 519 | { 520 | 'short': True, 521 | 'title': 'region', 522 | 'value': '`us-east-2`' 523 | }, 524 | { 525 | 'short': True, 526 | 'title': 'resources', 527 | 'value': '`[]`' 528 | }, 529 | { 530 | 'short': False, 531 | 'title': 'detail', 532 | 'value': '`{"jobName": "test_job", "severity": "ERROR", "state": "FAILED", "jobRunId": "jr_ca2144d747b45ad412d3c66a1b6934b6b27aa252be9a21a95c54dfaa224a1925", "message": "SystemExit: 1"}`' 533 | } 534 | ], 535 | 'mrkdwn_in': [ 536 | 'value' 537 | ], 538 | 'text': 'AWS notification', 539 | 'title': 'Message' 540 | } 541 | ], 542 | 'channel': 'slack_testing_sandbox', 543 | 'icon_emoji': ':aws:', 544 | 'username': 'notify_slack_test' 545 | } 546 | ] 547 | 548 | snapshots['test_sns_get_slack_message_payload_snapshots message_guardduty_finding.json'] = [ 549 | { 550 | 'attachments': [ 551 | { 552 | 'color': 'danger', 553 | 'fallback': 'GuardDuty Finding: SAMPLE Unprotected port on EC2 instance i-123123123 is being probed', 554 | 'fields': [ 555 | { 556 | 'short': False, 557 | 'title': 'Description', 558 | 'value': '`EC2 instance has an unprotected port which is being probed by a known malicious host.`' 559 | }, 560 | { 561 | 'short': False, 562 | 'title': 'Finding Type', 563 | 'value': '`Recon:EC2 PortProbeUnprotectedPort`' 564 | }, 565 | { 566 | 'short': True, 567 | 'title': 'First Seen', 568 | 'value': '`2020-01-02T01:02:03Z`' 569 | }, 570 | { 571 | 'short': True, 572 | 'title': 'Last Seen', 573 | 'value': '`2020-01-03T01:02:03Z`' 574 | }, 575 | { 576 | 'short': True, 577 | 'title': 'Severity', 578 | 'value': '`High`' 579 | }, 580 | { 581 | 'short': True, 582 | 'title': 'Account ID', 583 | 'value': '`123456789`' 584 | }, 585 | { 586 | 'short': True, 587 | 'title': 'Count', 588 | 'value': '`1234`' 589 | }, 590 | { 591 | 'short': False, 592 | 'title': 'Link to Finding', 593 | 'value': 'https://console.amazonaws-us-gov.com/guardduty/home?region=us-gov-east-1#/findings?search=id%3Dsample-id-2' 594 | } 595 | ], 596 | 'text': 'AWS GuardDuty Finding - SAMPLE Unprotected port on EC2 instance i-123123123 is being probed' 597 | } 598 | ], 599 | 'channel': 'slack_testing_sandbox', 600 | 'icon_emoji': ':aws:', 601 | 'username': 'notify_slack_test' 602 | } 603 | ] 604 | 605 | snapshots['test_sns_get_slack_message_payload_snapshots message_text_message.json'] = [ 606 | { 607 | 'attachments': [ 608 | { 609 | 'fallback': 'A new message', 610 | 'fields': [ 611 | { 612 | 'short': False, 613 | 'value': '''This 614 | is 615 | a typical multi-line 616 | message from SNS! 617 | 618 | Have a ~good~ amazing day! :)''' 619 | } 620 | ], 621 | 'mrkdwn_in': [ 622 | 'value' 623 | ], 624 | 'text': 'AWS notification', 625 | 'title': 'All Fine' 626 | } 627 | ], 628 | 'channel': 'slack_testing_sandbox', 629 | 'icon_emoji': ':aws:', 630 | 'username': 'notify_slack_test' 631 | } 632 | ] 633 | -------------------------------------------------------------------------------- /iam.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | create_sns_feedback_role = local.create && var.create_sns_topic && var.enable_sns_topic_delivery_status_logs && var.sns_topic_lambda_feedback_role_arn == "" 3 | } 4 | 5 | data "aws_iam_policy_document" "sns_feedback" { 6 | count = local.create_sns_feedback_role ? 1 : 0 7 | 8 | statement { 9 | sid = "SnsAssume" 10 | effect = "Allow" 11 | 12 | actions = [ 13 | "sts:AssumeRole", 14 | "sts:TagSession", 15 | ] 16 | 17 | principals { 18 | type = "Service" 19 | identifiers = ["sns.amazonaws.com"] 20 | } 21 | } 22 | } 23 | 24 | resource "aws_iam_role" "sns_feedback_role" { 25 | count = local.create_sns_feedback_role ? 1 : 0 26 | 27 | name = var.sns_topic_feedback_role_name 28 | description = var.sns_topic_feedback_role_description 29 | path = var.sns_topic_feedback_role_path 30 | force_detach_policies = var.sns_topic_feedback_role_force_detach_policies 31 | permissions_boundary = var.sns_topic_feedback_role_permissions_boundary 32 | assume_role_policy = data.aws_iam_policy_document.sns_feedback[0].json 33 | 34 | tags = merge( 35 | var.tags, 36 | var.sns_topic_feedback_role_tags, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | data "aws_partition" "current" {} 3 | data "aws_region" "current" {} 4 | 5 | locals { 6 | create = var.create && var.putin_khuylo 7 | 8 | sns_topic_arn = try( 9 | aws_sns_topic.this[0].arn, 10 | "arn:${data.aws_partition.current.id}:sns:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${var.sns_topic_name}", 11 | "" 12 | ) 13 | 14 | sns_feedback_role = local.create_sns_feedback_role ? aws_iam_role.sns_feedback_role[0].arn : var.sns_topic_lambda_feedback_role_arn 15 | lambda_policy_document = { 16 | sid = "AllowWriteToCloudwatchLogs" 17 | effect = "Allow" 18 | actions = ["logs:CreateLogStream", "logs:PutLogEvents"] 19 | resources = [replace("${try(aws_cloudwatch_log_group.lambda[0].arn, "")}:*", ":*:*", ":*")] 20 | } 21 | 22 | lambda_policy_document_kms = { 23 | sid = "AllowKMSDecrypt" 24 | effect = "Allow" 25 | actions = ["kms:Decrypt"] 26 | resources = [var.kms_key_arn] 27 | } 28 | 29 | lambda_policy_document_securityhub = { 30 | sid = "AllowSecurityHub" 31 | effect = "Allow" 32 | actions = ["securityhub:BatchUpdateFindings"] 33 | resources = ["*"] 34 | } 35 | 36 | lambda_handler = try(split(".", basename(var.lambda_source_path))[0], "notify_slack") 37 | } 38 | 39 | data "aws_iam_policy_document" "lambda" { 40 | count = var.create ? 1 : 0 41 | 42 | dynamic "statement" { 43 | for_each = concat([local.lambda_policy_document, 44 | local.lambda_policy_document_securityhub], var.kms_key_arn != "" ? [local.lambda_policy_document_kms] : []) 45 | content { 46 | sid = statement.value.sid 47 | effect = statement.value.effect 48 | actions = statement.value.actions 49 | resources = statement.value.resources 50 | } 51 | } 52 | } 53 | 54 | resource "aws_cloudwatch_log_group" "lambda" { 55 | count = var.create ? 1 : 0 56 | 57 | name = "/aws/lambda/${var.lambda_function_name}" 58 | retention_in_days = var.cloudwatch_log_group_retention_in_days 59 | kms_key_id = var.cloudwatch_log_group_kms_key_id 60 | 61 | tags = merge(var.tags, var.cloudwatch_log_group_tags) 62 | } 63 | 64 | resource "aws_sns_topic" "this" { 65 | count = var.create_sns_topic && var.create ? 1 : 0 66 | 67 | name = var.sns_topic_name 68 | 69 | kms_master_key_id = var.sns_topic_kms_key_id 70 | 71 | lambda_failure_feedback_role_arn = var.enable_sns_topic_delivery_status_logs ? local.sns_feedback_role : null 72 | lambda_success_feedback_role_arn = var.enable_sns_topic_delivery_status_logs ? local.sns_feedback_role : null 73 | lambda_success_feedback_sample_rate = var.enable_sns_topic_delivery_status_logs ? var.sns_topic_lambda_feedback_sample_rate : null 74 | 75 | tags = merge(var.tags, var.sns_topic_tags) 76 | } 77 | 78 | 79 | resource "aws_sns_topic_subscription" "sns_notify_slack" { 80 | count = var.create ? 1 : 0 81 | 82 | topic_arn = local.sns_topic_arn 83 | protocol = "lambda" 84 | endpoint = module.lambda.lambda_function_arn 85 | filter_policy = var.subscription_filter_policy 86 | filter_policy_scope = var.subscription_filter_policy_scope 87 | } 88 | 89 | module "lambda" { 90 | source = "terraform-aws-modules/lambda/aws" 91 | version = "6.8.0" 92 | 93 | create = var.create 94 | 95 | function_name = var.lambda_function_name 96 | description = var.lambda_description 97 | 98 | hash_extra = var.hash_extra 99 | handler = "${local.lambda_handler}.lambda_handler" 100 | source_path = var.lambda_source_path != null ? "${path.root}/${var.lambda_source_path}" : "${path.module}/functions/notify_slack.py" 101 | recreate_missing_package = var.recreate_missing_package 102 | runtime = "python3.11" 103 | architectures = var.architectures 104 | timeout = 30 105 | kms_key_arn = var.kms_key_arn 106 | reserved_concurrent_executions = var.reserved_concurrent_executions 107 | ephemeral_storage_size = var.lambda_function_ephemeral_storage_size 108 | trigger_on_package_timestamp = var.trigger_on_package_timestamp 109 | 110 | # If publish is disabled, there will be "Error adding new Lambda Permission for notify_slack: 111 | # InvalidParameterValueException: We currently do not support adding policies for $LATEST." 112 | publish = true 113 | 114 | environment_variables = { 115 | SLACK_WEBHOOK_URL = var.slack_webhook_url 116 | SLACK_CHANNEL = var.slack_channel 117 | SLACK_USERNAME = var.slack_username 118 | SLACK_EMOJI = var.slack_emoji 119 | LOG_EVENTS = var.log_events ? "True" : "False" 120 | } 121 | 122 | create_role = var.lambda_role == "" 123 | lambda_role = var.lambda_role 124 | role_name = "${var.iam_role_name_prefix}-${var.lambda_function_name}" 125 | role_permissions_boundary = var.iam_role_boundary_policy_arn 126 | role_tags = var.iam_role_tags 127 | role_path = var.iam_role_path 128 | policy_path = var.iam_policy_path 129 | 130 | # Do not use Lambda's policy for cloudwatch logs, because we have to add a policy 131 | # for KMS conditionally. This way attach_policy_json is always true independenty of 132 | # the value of presense of KMS. Famous "computed values in count" bug... 133 | attach_cloudwatch_logs_policy = false 134 | attach_policy_json = true 135 | policy_json = try(data.aws_iam_policy_document.lambda[0].json, "") 136 | 137 | use_existing_cloudwatch_log_group = true 138 | attach_network_policy = var.lambda_function_vpc_subnet_ids != null 139 | 140 | dead_letter_target_arn = var.lambda_dead_letter_target_arn 141 | attach_dead_letter_policy = var.lambda_attach_dead_letter_policy 142 | 143 | allowed_triggers = { 144 | AllowExecutionFromSNS = { 145 | principal = "sns.amazonaws.com" 146 | source_arn = local.sns_topic_arn 147 | } 148 | } 149 | 150 | store_on_s3 = var.lambda_function_store_on_s3 151 | s3_bucket = var.lambda_function_s3_bucket 152 | 153 | vpc_subnet_ids = var.lambda_function_vpc_subnet_ids 154 | vpc_security_group_ids = var.lambda_function_vpc_security_group_ids 155 | 156 | tags = merge(var.tags, var.lambda_function_tags) 157 | 158 | depends_on = [aws_cloudwatch_log_group.lambda] 159 | } 160 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "slack_topic_arn" { 2 | description = "The ARN of the SNS topic from which messages will be sent to Slack" 3 | value = local.sns_topic_arn 4 | } 5 | 6 | # todo: Remove `this_slack_topic_arn` output during next major release 5.x 7 | output "this_slack_topic_arn" { 8 | description = "The ARN of the SNS topic from which messages will be sent to Slack (backward compatibility for version 4.x)" 9 | value = local.sns_topic_arn 10 | } 11 | 12 | output "lambda_iam_role_arn" { 13 | description = "The ARN of the IAM role used by Lambda function" 14 | value = module.lambda.lambda_role_arn 15 | } 16 | 17 | output "lambda_iam_role_name" { 18 | description = "The name of the IAM role used by Lambda function" 19 | value = module.lambda.lambda_role_name 20 | } 21 | 22 | output "notify_slack_lambda_function_arn" { 23 | description = "The ARN of the Lambda function" 24 | value = module.lambda.lambda_function_arn 25 | } 26 | 27 | output "notify_slack_lambda_function_name" { 28 | description = "The name of the Lambda function" 29 | value = module.lambda.lambda_function_name 30 | } 31 | 32 | output "notify_slack_lambda_function_invoke_arn" { 33 | description = "The ARN to be used for invoking Lambda function from API Gateway" 34 | value = module.lambda.lambda_function_invoke_arn 35 | } 36 | 37 | output "notify_slack_lambda_function_last_modified" { 38 | description = "The date Lambda function was last modified" 39 | value = module.lambda.lambda_function_last_modified 40 | } 41 | 42 | output "notify_slack_lambda_function_version" { 43 | description = "Latest published version of your Lambda function" 44 | value = module.lambda.lambda_function_version 45 | } 46 | 47 | output "lambda_cloudwatch_log_group_arn" { 48 | description = "The Amazon Resource Name (ARN) specifying the log group" 49 | value = try(aws_cloudwatch_log_group.lambda[0].arn, "") 50 | } 51 | 52 | output "sns_topic_feedback_role_arn" { 53 | description = "The Amazon Resource Name (ARN) of the IAM role used for SNS delivery status logging" 54 | value = local.sns_feedback_role 55 | } 56 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "putin_khuylo" { 2 | description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" 3 | type = bool 4 | default = true 5 | } 6 | 7 | variable "architectures" { 8 | description = "Instruction set architecture for your Lambda function. Valid values are [\"x86_64\"] and [\"arm64\"]." 9 | type = list(string) 10 | default = null 11 | } 12 | 13 | variable "create" { 14 | description = "Whether to create all resources" 15 | type = bool 16 | default = true 17 | } 18 | 19 | variable "create_sns_topic" { 20 | description = "Whether to create new SNS topic" 21 | type = bool 22 | default = true 23 | } 24 | 25 | variable "hash_extra" { 26 | description = "The string to add into hashing function. Useful when building same source path for different functions." 27 | type = string 28 | default = "" 29 | } 30 | 31 | variable "lambda_role" { 32 | description = "IAM role attached to the Lambda Function. If this is set then a role will not be created for you." 33 | type = string 34 | default = "" 35 | } 36 | 37 | variable "lambda_function_name" { 38 | description = "The name of the Lambda function to create" 39 | type = string 40 | default = "notify_slack" 41 | } 42 | 43 | variable "lambda_description" { 44 | description = "The description of the Lambda function" 45 | type = string 46 | default = null 47 | } 48 | 49 | variable "lambda_source_path" { 50 | description = "The source path of the custom Lambda function" 51 | type = string 52 | default = null 53 | } 54 | 55 | variable "lambda_dead_letter_target_arn" { 56 | description = "The ARN of an SNS topic or SQS queue to notify when an invocation fails." 57 | type = string 58 | default = null 59 | } 60 | 61 | variable "lambda_attach_dead_letter_policy" { 62 | description = "Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function" 63 | type = bool 64 | default = false 65 | } 66 | 67 | variable "sns_topic_name" { 68 | description = "The name of the SNS topic to create" 69 | type = string 70 | } 71 | 72 | variable "sns_topic_kms_key_id" { 73 | description = "ARN of the KMS key used for enabling SSE on the topic" 74 | type = string 75 | default = "" 76 | } 77 | 78 | variable "enable_sns_topic_delivery_status_logs" { 79 | description = "Whether to enable SNS topic delivery status logs" 80 | type = bool 81 | default = false 82 | } 83 | 84 | variable "sns_topic_lambda_feedback_role_arn" { 85 | description = "IAM role for SNS topic delivery status logs. If this is set then a role will not be created for you." 86 | type = string 87 | default = "" 88 | } 89 | 90 | variable "sns_topic_feedback_role_name" { 91 | description = "Name of the IAM role to use for SNS topic delivery status logging" 92 | type = string 93 | default = null 94 | } 95 | 96 | variable "sns_topic_feedback_role_description" { 97 | description = "Description of IAM role to use for SNS topic delivery status logging" 98 | type = string 99 | default = null 100 | } 101 | 102 | variable "sns_topic_feedback_role_path" { 103 | description = "Path of IAM role to use for SNS topic delivery status logging" 104 | type = string 105 | default = null 106 | } 107 | 108 | variable "sns_topic_feedback_role_force_detach_policies" { 109 | description = "Specifies to force detaching any policies the IAM role has before destroying it." 110 | type = bool 111 | default = true 112 | } 113 | 114 | variable "sns_topic_feedback_role_permissions_boundary" { 115 | description = "The ARN of the policy that is used to set the permissions boundary for the IAM role used by SNS topic delivery status logging" 116 | type = string 117 | default = null 118 | } 119 | 120 | variable "sns_topic_feedback_role_tags" { 121 | description = "A map of tags to assign to IAM the SNS topic feedback role" 122 | type = map(string) 123 | default = {} 124 | } 125 | 126 | variable "sns_topic_lambda_feedback_sample_rate" { 127 | description = "The percentage of successful deliveries to log" 128 | type = number 129 | default = 100 130 | } 131 | 132 | variable "slack_webhook_url" { 133 | description = "The URL of Slack webhook" 134 | type = string 135 | } 136 | 137 | variable "slack_channel" { 138 | description = "The name of the channel in Slack for notifications" 139 | type = string 140 | } 141 | 142 | variable "slack_username" { 143 | description = "The username that will appear on Slack messages" 144 | type = string 145 | } 146 | 147 | variable "slack_emoji" { 148 | description = "A custom emoji that will appear on Slack messages" 149 | type = string 150 | default = ":aws:" 151 | } 152 | 153 | variable "kms_key_arn" { 154 | description = "ARN of the KMS key used for decrypting slack webhook url" 155 | type = string 156 | default = "" 157 | } 158 | 159 | variable "recreate_missing_package" { 160 | description = "Whether to recreate missing Lambda package if it is missing locally or not" 161 | type = bool 162 | default = true 163 | } 164 | 165 | variable "log_events" { 166 | description = "Boolean flag to enabled/disable logging of incoming events" 167 | type = bool 168 | default = false 169 | } 170 | 171 | variable "reserved_concurrent_executions" { 172 | description = "The amount of reserved concurrent executions for this lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations" 173 | type = number 174 | default = -1 175 | } 176 | 177 | variable "cloudwatch_log_group_retention_in_days" { 178 | description = "Specifies the number of days you want to retain log events in log group for Lambda." 179 | type = number 180 | default = 0 181 | } 182 | 183 | variable "cloudwatch_log_group_kms_key_id" { 184 | description = "The ARN of the KMS Key to use when encrypting log data for Lambda" 185 | type = string 186 | default = null 187 | } 188 | 189 | variable "tags" { 190 | description = "A map of tags to add to all resources" 191 | type = map(string) 192 | default = {} 193 | } 194 | 195 | variable "iam_role_tags" { 196 | description = "Additional tags for the IAM role" 197 | type = map(string) 198 | default = {} 199 | } 200 | 201 | variable "iam_role_boundary_policy_arn" { 202 | description = "The ARN of the policy that is used to set the permissions boundary for the role" 203 | type = string 204 | default = null 205 | } 206 | 207 | variable "iam_role_name_prefix" { 208 | description = "A unique role name beginning with the specified prefix" 209 | type = string 210 | default = "lambda" 211 | } 212 | 213 | variable "iam_role_path" { 214 | description = "Path of IAM role to use for Lambda Function" 215 | type = string 216 | default = null 217 | } 218 | 219 | variable "iam_policy_path" { 220 | description = "Path of policies to that should be added to IAM role for Lambda Function" 221 | type = string 222 | default = null 223 | } 224 | 225 | variable "lambda_function_tags" { 226 | description = "Additional tags for the Lambda function" 227 | type = map(string) 228 | default = {} 229 | } 230 | 231 | variable "lambda_function_vpc_subnet_ids" { 232 | description = "List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets." 233 | type = list(string) 234 | default = null 235 | } 236 | 237 | variable "lambda_function_vpc_security_group_ids" { 238 | description = "List of security group ids when Lambda Function should run in the VPC." 239 | type = list(string) 240 | default = null 241 | } 242 | 243 | variable "lambda_function_store_on_s3" { 244 | description = "Whether to store produced artifacts on S3 or locally." 245 | type = bool 246 | default = false 247 | } 248 | 249 | variable "lambda_function_s3_bucket" { 250 | description = "S3 bucket to store artifacts" 251 | type = string 252 | default = null 253 | } 254 | 255 | variable "lambda_function_ephemeral_storage_size" { 256 | description = "Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB)." 257 | type = number 258 | default = 512 259 | } 260 | 261 | variable "sns_topic_tags" { 262 | description = "Additional tags for the SNS topic" 263 | type = map(string) 264 | default = {} 265 | } 266 | 267 | variable "cloudwatch_log_group_tags" { 268 | description = "Additional tags for the Cloudwatch log group" 269 | type = map(string) 270 | default = {} 271 | } 272 | 273 | variable "subscription_filter_policy" { 274 | description = "(Optional) A valid filter policy that will be used in the subscription to filter messages seen by the target resource." 275 | type = string 276 | default = null 277 | } 278 | 279 | variable "subscription_filter_policy_scope" { 280 | description = "(Optional) A valid filter policy scope MessageAttributes|MessageBody" 281 | type = string 282 | default = null 283 | } 284 | 285 | variable "trigger_on_package_timestamp" { 286 | description = "(Optional) Whether or not to ignore the file timestamp when deciding to create the archive" 287 | type = bool 288 | default = false 289 | } 290 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.8" 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------