├── .github ├── CODEOWNERS └── workflows │ ├── commit.yaml │ └── pullRequest.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .terraform-version ├── .tflint.hcl ├── .yamllint.yml ├── LICENSE ├── README.md ├── bin ├── install-macos.sh ├── install-ubuntu.sh └── install.sh ├── examples └── basic │ ├── README.md │ └── main.tf ├── getsha.sh ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rhythmictech/engineering 2 | -------------------------------------------------------------------------------- /.github/workflows/commit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: commit-check 3 | on: 4 | push: 5 | 6 | jobs: 7 | pre-commit-check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | - name: Install prerequisites 14 | run: ./bin/install-ubuntu.sh 15 | - name: Setup Terraform 16 | uses: hashicorp/setup-terraform@v1 17 | with: 18 | terraform_version: ~0.12.29 19 | - name: initialize Terraform 20 | run: terraform init --backend=false 21 | - name: pre-commit 22 | uses: pre-commit/action@v2.0.0 23 | env: 24 | AWS_DEFAULT_REGION: us-east-1 25 | # many of these are covered by better reviewdog linters below 26 | SKIP: >- 27 | terraform_tflint_deep, 28 | no-commit-to-branch, 29 | terraform_tflint_nocreds, 30 | terraform_tfsec 31 | - uses: stefanzweifel/git-auto-commit-action@v4 32 | if: ${{ failure() }} 33 | with: 34 | commit_message: Apply automatic changes 35 | commit_options: "--no-verify" 36 | # Optional commit user and author settings 37 | commit_user_name: Linter Bot 38 | commit_user_email: noreply@rhythmictech.com 39 | commit_author: Linter Bot 40 | tflint: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - name: Setup Terraform 45 | uses: hashicorp/setup-terraform@v1 46 | with: 47 | terraform_version: ~0.12.29 48 | - name: Terraform init 49 | run: terraform init --backend=false 50 | - name: tflint 51 | uses: reviewdog/action-tflint@master 52 | with: 53 | github_token: ${{ secrets.GITHUB_TOKEN }} 54 | reporter: github-check 55 | filter_mode: added 56 | flags: --module 57 | level: error 58 | tfsec: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - name: Setup Terraform 63 | uses: hashicorp/setup-terraform@v1 64 | with: 65 | terraform_version: ~0.12.29 66 | - name: Terraform init 67 | run: terraform init --backend=false 68 | - name: tfsec 69 | uses: reviewdog/action-tfsec@master 70 | with: 71 | github_token: ${{ secrets.GITHUB_TOKEN }} 72 | reporter: github-check 73 | filter_mode: added 74 | level: warning 75 | misspell: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v2 79 | - name: misspell 80 | uses: reviewdog/action-misspell@v1 81 | with: 82 | github_token: ${{ secrets.GITHUB_TOKEN }} 83 | locale: "US" 84 | reporter: github-check 85 | filter_mode: added 86 | level: error 87 | yamllint: 88 | runs-on: ubuntu-latest 89 | steps: 90 | - uses: actions/checkout@v2 91 | - name: yamllint 92 | uses: reviewdog/action-yamllint@v1 93 | with: 94 | github_token: ${{ secrets.GITHUB_TOKEN }} 95 | reporter: github-check 96 | filter_mode: added 97 | level: error 98 | -------------------------------------------------------------------------------- /.github/workflows/pullRequest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pull request 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | pre-commit: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | - name: Install prerequisites 14 | run: ./bin/install-ubuntu.sh 15 | - name: Setup Terraform 16 | uses: hashicorp/setup-terraform@v1 17 | with: 18 | terraform_version: ~0.12.29 19 | - name: initialize Terraform 20 | run: terraform init --backend=false 21 | - name: pre-commit 22 | uses: pre-commit/action@v2.0.0 23 | env: 24 | AWS_DEFAULT_REGION: us-east-1 25 | # many of these are covered by better reviewdog linters below 26 | SKIP: >- 27 | terraform_tflint_deep, 28 | no-commit-to-branch, 29 | terraform_tflint_nocreds, 30 | terraform_tfsec 31 | - uses: stefanzweifel/git-auto-commit-action@v4 32 | if: ${{ failure() }} 33 | with: 34 | commit_message: Apply automatic changes 35 | commit_options: "--no-verify" 36 | # Optional commit user and author settings 37 | commit_user_name: Linter Bot 38 | commit_user_email: noreply@rhythmictech.com 39 | commit_author: Linter Bot 40 | tflint: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - name: Setup Terraform 45 | uses: hashicorp/setup-terraform@v1 46 | with: 47 | terraform_version: ~0.12.29 48 | - name: Terraform init 49 | run: terraform init --backend=false 50 | - name: tflint 51 | uses: reviewdog/action-tflint@master 52 | with: 53 | github_token: ${{ secrets.GITHUB_TOKEN }} 54 | reporter: github-pr-check 55 | filter_mode: added 56 | flags: --module 57 | level: error 58 | tfsec: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - name: Setup Terraform 63 | uses: hashicorp/setup-terraform@v1 64 | with: 65 | terraform_version: ~0.12.29 66 | - name: Terraform init 67 | run: terraform init --backend=false 68 | - name: tfsec 69 | uses: reviewdog/action-tfsec@master 70 | with: 71 | github_token: ${{ secrets.GITHUB_TOKEN }} 72 | reporter: github-pr-check 73 | filter_mode: added 74 | level: warning 75 | misspell: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v2 79 | - name: misspell 80 | uses: reviewdog/action-misspell@v1 81 | with: 82 | github_token: ${{ secrets.GITHUB_TOKEN }} 83 | locale: "US" 84 | reporter: github-pr-check 85 | filter_mode: added 86 | level: error 87 | yamllint: 88 | runs-on: ubuntu-latest 89 | steps: 90 | - uses: actions/checkout@v2 91 | - name: yamllint 92 | uses: reviewdog/action-yamllint@v1 93 | with: 94 | github_token: ${{ secrets.GITHUB_TOKEN }} 95 | reporter: github-pr-check 96 | filter_mode: added 97 | level: error 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | 11 | # local tmp 12 | tmp 13 | tmp/* 14 | 15 | **/*.zip 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ".terraform" 2 | repos: 3 | - repo: https://github.com/antonbabenko/pre-commit-terraform 4 | rev: v1.50.0 5 | hooks: 6 | - id: terraform_docs 7 | always_run: true 8 | args: 9 | - --args=--sort-by-required 10 | - id: terraform_fmt 11 | - id: terraform_tflint 12 | alias: terraform_tflint_nocreds 13 | name: terraform_tflint_nocreds 14 | - id: terraform_tfsec 15 | args: 16 | - "--args=--exclude-downloaded-modules" 17 | - repo: local 18 | hooks: 19 | - id: terraform_validate 20 | name: terraform_validate 21 | entry: | 22 | bash -c ' 23 | AWS_DEFAULT_REGION=us-east-1 24 | declare -a DIRS 25 | for FILE in "$@" 26 | do 27 | DIRS+=($(dirname "$FILE")) 28 | done 29 | for DIR in $(printf "%s\n" "${DIRS[@]}" | sort -u) 30 | do 31 | cd $(dirname "$FILE") 32 | terraform init --backend=false 33 | terraform validate . 34 | cd .. 35 | done 36 | ' 37 | language: system 38 | verbose: true 39 | files: \.tf(vars)?$ 40 | exclude: examples 41 | - repo: https://github.com/pre-commit/pre-commit-hooks 42 | rev: v3.4.0 43 | hooks: 44 | - id: check-case-conflict 45 | - id: check-json 46 | - id: check-merge-conflict 47 | - id: check-symlinks 48 | - id: check-yaml 49 | args: 50 | - --unsafe 51 | - id: end-of-file-fixer 52 | - id: mixed-line-ending 53 | args: 54 | - --fix=lf 55 | - id: no-commit-to-branch 56 | args: 57 | - --branch 58 | - main 59 | - --branch 60 | - master 61 | - --branch 62 | - prod 63 | - id: pretty-format-json 64 | exclude: package-lock.json$ 65 | args: 66 | - --autofix 67 | - --top-keys=name,Name 68 | - id: trailing-whitespace 69 | args: 70 | - --markdown-linebreak-ext=md 71 | exclude: README.md 72 | -------------------------------------------------------------------------------- /.terraform-version: -------------------------------------------------------------------------------- 1 | latest:^0.14 2 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | config { 2 | module = true 3 | } 4 | 5 | rule "terraform_deprecated_interpolation" { 6 | enabled = true 7 | } 8 | 9 | rule "terraform_unused_declarations" { 10 | enabled = true 11 | } 12 | 13 | rule "terraform_comment_syntax" { 14 | enabled = true 15 | } 16 | 17 | rule "terraform_documented_outputs" { 18 | enabled = true 19 | } 20 | 21 | rule "terraform_documented_variables" { 22 | enabled = true 23 | } 24 | 25 | rule "terraform_typed_variables" { 26 | enabled = true 27 | } 28 | 29 | rule "terraform_module_pinned_source" { 30 | enabled = true 31 | } 32 | 33 | rule "terraform_naming_convention" { 34 | enabled = true 35 | format = "snake_case" 36 | } 37 | 38 | rule "terraform_required_version" { 39 | enabled = true 40 | } 41 | 42 | rule "terraform_required_providers" { 43 | enabled = true 44 | } 45 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | truthy: 2 | check-keys: false 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rhythmic Technologies, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-aws-secure-password 2 | Creates a password with a Lambda data source and saves it in a secrets manager secret, allowing the creation of passwords without saving them in state 3 | 4 | [![tflint](https://github.com/rhythmictech/terraform-aws-secure-password/workflows/tflint/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-secure-password/actions?query=workflow%3Atflint+event%3Apush+branch%3Amain) 5 | [![tfsec](https://github.com/rhythmictech/terraform-aws-secure-password/workflows/tfsec/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-secure-password/actions?query=workflow%3Atfsec+event%3Apush+branch%3Amain) 6 | [![yamllint](https://github.com/rhythmictech/terraform-aws-secure-password/workflows/yamllint/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-secure-password/actions?query=workflow%3Ayamllint+event%3Apush+branch%3Amain) 7 | [![misspell](https://github.com/rhythmictech/terraform-aws-secure-password/workflows/misspell/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-secure-password/actions?query=workflow%3Amisspell+event%3Apush+branch%3Amain) 8 | [![pre-commit-check](https://github.com/rhythmictech/terraform-aws-secure-password/workflows/pre-commit-check/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-secure-password/actions?query=workflow%3Apre-commit-check+event%3Apush+branch%3Amain) 9 | follow on Twitter 10 | 11 | ## Example 12 | Here's what using the module will look like 13 | ```hcl 14 | module "secure_password" { 15 | source = "rhythmictech/secure-password/aws" 16 | version = "~> 1.0.0-rc1" 17 | 18 | name = "my-secure-pass" 19 | length = 24 20 | } 21 | 22 | output "secret_name" { 23 | value = module.secure_password.secret_name 24 | } 25 | 26 | ``` 27 | 28 | ## About 29 | Creates a password with a Lambda data source and saves it in a secrets manager secret, allowing the creation of passwords without saving them in state 30 | 31 | 32 | ## Requirements 33 | 34 | | Name | Version | 35 | |------|---------| 36 | | [terraform](#requirement\_terraform) | >= 0.12.28 | 37 | | [aws](#requirement\_aws) | >= 2.45 | 38 | | [external](#requirement\_external) | >= 1.2 | 39 | | [null](#requirement\_null) | >= 2.1 | 40 | | [random](#requirement\_random) | >= 2.3 | 41 | 42 | ## Providers 43 | 44 | | Name | Version | 45 | |------|---------| 46 | | [aws](#provider\_aws) | >= 2.45 | 47 | | [external](#provider\_external) | >= 1.2 | 48 | | [null](#provider\_null) | >= 2.1 | 49 | | [random](#provider\_random) | >= 2.3 | 50 | 51 | ## Modules 52 | 53 | | Name | Source | Version | 54 | |------|--------|---------| 55 | | [lambda\_invocation](#module\_lambda\_invocation) | matti/resource/shell | ~>1.0.7 | 56 | | [lambda\_invocation\_result](#module\_lambda\_invocation\_result) | matti/resource/shell | ~>1.0.7 | 57 | | [lambda\_version](#module\_lambda\_version) | rhythmictech/find-release-by-semver/github | ~> 1.0 | 58 | 59 | ## Resources 60 | 61 | | Name | Type | 62 | |------|------| 63 | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 64 | | [aws_iam_role_policy.secret_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | 65 | | [aws_iam_role_policy_attachment.lambda_basic_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 66 | | [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | 67 | | [aws_secretsmanager_secret.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource | 68 | | [null_resource.lambda_zip](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | 69 | | [random_string.trigger](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | 70 | | [aws_iam_policy_document.assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 71 | | [aws_iam_policy_document.secret_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 72 | | [external_external.sha](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | 73 | 74 | ## Inputs 75 | 76 | | Name | Description | Type | Default | Required | 77 | |------|-------------|------|---------|:--------:| 78 | | [length](#input\_length) | Length of the password to be created | `number` | n/a | yes | 79 | | [name](#input\_name) | Moniker to apply to all resources in the module | `string` | n/a | yes | 80 | | [keepers](#input\_keepers) | Arbitrary map of values that when changed will force a new password | `map(string)` | `{}` | no | 81 | | [lambda\_version\_constraint](#input\_lambda\_version\_constraint) | NPM-style version constraint for the version of the lambda code you want to use | `string` | `"^1.0.3"` | no | 82 | | [lower](#input\_lower) | Whether to use lower case characters | `bool` | `true` | no | 83 | | [min\_lower](#input\_min\_lower) | Minimum number of lowercase letters | `number` | `0` | no | 84 | | [min\_numeric](#input\_min\_numeric) | Minimum number of numeric characters to use. Must be at least 1 | `number` | `1` | no | 85 | | [min\_special](#input\_min\_special) | Minimum number of special characters to use. Must be at least 1 | `number` | `1` | no | 86 | | [min\_upper](#input\_min\_upper) | Minimum number of uppercase characters to use. Must be at least 1 | `number` | `1` | no | 87 | | [number](#input\_number) | Whether to use numbers | `bool` | `true` | no | 88 | | [override\_special](#input\_override\_special) | Supply your own list of special characters to use for string generation | `string` | `"!@#$%&*()-_=+[]{}<>:?"` | no | 89 | | [secret\_description](#input\_secret\_description) | Set a description for the secret | `string` | `"A password created by Terraform"` | no | 90 | | [special](#input\_special) | Whether to use special characters | `bool` | `true` | no | 91 | | [tags](#input\_tags) | User-Defined tags | `map(string)` | `{}` | no | 92 | | [upper](#input\_upper) | Whether to use uppercase characters | `bool` | `true` | no | 93 | 94 | ## Outputs 95 | 96 | | Name | Description | 97 | |------|-------------| 98 | | [lambda\_version](#output\_lambda\_version) | The selected version of the Lambda code | 99 | | [lambda\_version\_info](#output\_lambda\_version\_info) | all information about the selected version of the Lambda code | 100 | | [result](#output\_result) | String result of Lambda execution | 101 | | [secret\_arn](#output\_secret\_arn) | ARN of the secret containing the password | 102 | | [secret\_name](#output\_secret\_name) | Name of the secret containing the password | 103 | 104 | 105 | ## The Giants Underneath this Module 106 | - [pre-commit.com](pre-commit.com) 107 | - [terraform.io](terraform.io) 108 | - [github.com/tfutils/tfenv](github.com/tfutils/tfenv) 109 | - [github.com/segmentio/terraform-docs](github.com/segmentio/terraform-docs) 110 | -------------------------------------------------------------------------------- /bin/install-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'installing brew packages' 4 | brew update 5 | brew tap liamg/tfsec 6 | brew install tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils 7 | brew upgrade tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils 8 | 9 | echo 'installing pre-commit hooks' 10 | pre-commit install 11 | 12 | echo 'setting pre-commit hooks to auto-install on clone in the future' 13 | git config --global init.templateDir ~/.git-template 14 | pre-commit init-templatedir ~/.git-template 15 | 16 | echo 'installing terraform with tfenv' 17 | tfenv install 18 | -------------------------------------------------------------------------------- /bin/install-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'installing dependencies' 4 | sudo apt install python3-pip gawk &&\ 5 | pip3 install pre-commit 6 | tfdocs_latest_dl_url=$(curl -sL https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E "https://.+?-linux-amd64" | tail -n1) 7 | curl -L "$tfdocs_latest_dl_url" > terraform-docs && chmod +x terraform-docs && sudo mv terraform-docs /usr/bin/ 8 | curl -L "$(curl -sL https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ 9 | env GO111MODULE=on go get -u github.com/liamg/tfsec/cmd/tfsec 10 | git clone https://github.com/tfutils/tfenv.git ~/.tfenv || true 11 | mkdir -p ~/.local/bin/ 12 | . ~/.profile 13 | ln -s ~/.tfenv/bin/* ~/.local/bin 14 | 15 | echo 'installing pre-commit hooks' 16 | pre-commit install 17 | 18 | echo 'setting pre-commit hooks to auto-install on clone in the future' 19 | git config --global init.templateDir ~/.git-template 20 | pre-commit init-templatedir ~/.git-template 21 | 22 | echo 'installing terraform with tfenv' 23 | tfenv install 24 | -------------------------------------------------------------------------------- /bin/install.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | unameOut="$(uname -s)" 3 | case "${unameOut}" in 4 | Linux*) 5 | ./bin/install-ubuntu.sh 6 | ;; 7 | Darwin*) 8 | ./bin/install-macos.sh 9 | ;; 10 | esac 11 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # basic example 2 | A basic example for this repository 3 | 4 | ## Code 5 | Look to [main.tf](./main.tf), or be helpful and copy/paste that code here. 6 | 7 | ## Applying 8 | ``` 9 | > terraform apply 10 | 11 | Apply complete! Resources: 0 added, 0 changed, 0 destroyed. 12 | 13 | Outputs: 14 | 15 | example = { 16 | "tags_module" = { 17 | "name" = "TEST" 18 | "name32" = "TEST" 19 | "name6" = "TEST" 20 | "namenosymbols" = "TEST" 21 | "tags" = { 22 | "Name" = "TEST" 23 | "terraform_managed" = true 24 | "terraform_module" = "terraform-terraform-tags-1.0.0" 25 | "terraform_root_module" = "." 26 | "terraform_workspace" = "default" 27 | } 28 | "tags_as_list_of_maps" = [ 29 | { 30 | "key" = "Name" 31 | "value" = "TEST" 32 | }, 33 | { 34 | "key" = "terraform_managed" 35 | "value" = true 36 | }, 37 | { 38 | "key" = "terraform_module" 39 | "value" = "terraform-terraform-tags-1.0.0" 40 | }, 41 | { 42 | "key" = "terraform_root_module" 43 | "value" = "." 44 | }, 45 | { 46 | "key" = "terraform_workspace" 47 | "value" = "default" 48 | }, 49 | ] 50 | "tags_no_name" = { 51 | "terraform_managed" = true 52 | "terraform_module" = "terraform-terraform-tags-1.0.0" 53 | "terraform_root_module" = "." 54 | "terraform_workspace" = "default" 55 | } 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | module "secure_password" { 2 | source = "rhythmictech/secure-password/aws" 3 | version = "~> 1.0.0-rc1" 4 | 5 | name = "my-secure-pass" 6 | length = 24 7 | } 8 | 9 | output "secret_name" { 10 | value = module.secure_password.secret_name 11 | } 12 | -------------------------------------------------------------------------------- /getsha.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | ARGS=($(jq -r --unbuffered '.repo_full_name,.tag')) 3 | curl -Ls "https://github.com/${ARGS[0]}/releases/download/${ARGS[1]}/lambda.sha256base64" | jq -cR '{"sha": .}' 4 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | lambda_repo_full_name = "${local.lambda_repo_owner}/${local.lambda_repo_name}" 3 | lambda_repo_owner = "rhythmictech" 4 | lambda_repo_name = "lambda-aws-secure-password" 5 | } 6 | 7 | module "lambda_version" { 8 | source = "rhythmictech/find-release-by-semver/github" 9 | version = "~> 1.1" 10 | 11 | repo_name = local.lambda_repo_name 12 | repo_owner = local.lambda_repo_owner 13 | version_constraint = var.lambda_version_constraint 14 | } 15 | 16 | locals { 17 | lambda_version = module.lambda_version.target_version 18 | lambda_version_tag = module.lambda_version.version_info.release_tag 19 | zipfile = "lambda-${local.lambda_version}.zip" 20 | } 21 | 22 | resource "null_resource" "lambda_zip" { 23 | triggers = { 24 | on_version_change = local.lambda_version 25 | } 26 | 27 | provisioner "local-exec" { 28 | command = "curl -LsSfo ${local.zipfile} https://github.com/${local.lambda_repo_full_name}/releases/download/${local.lambda_version_tag}/lambda.zip" 29 | } 30 | } 31 | 32 | data "external" "sha" { 33 | program = [ 34 | "${path.module}/getsha.sh" 35 | ] 36 | 37 | query = { 38 | repo_full_name = local.lambda_repo_full_name 39 | tag = local.lambda_version_tag 40 | } 41 | } 42 | 43 | data "aws_iam_policy_document" "assume" { 44 | statement { 45 | actions = [ 46 | "sts:AssumeRole", 47 | ] 48 | 49 | principals { 50 | type = "Service" 51 | identifiers = ["lambda.amazonaws.com"] 52 | } 53 | } 54 | } 55 | 56 | resource "aws_iam_role" "this" { 57 | name_prefix = "${var.name}-" 58 | description = "Role for ${var.name} to create secret" 59 | 60 | assume_role_policy = data.aws_iam_policy_document.assume.json 61 | tags = var.tags 62 | 63 | lifecycle { 64 | create_before_destroy = true 65 | } 66 | } 67 | 68 | resource "aws_iam_role_policy_attachment" "lambda_basic_execution" { 69 | role = aws_iam_role.this.name 70 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 71 | } 72 | 73 | data "aws_iam_policy_document" "secret_write" { 74 | statement { 75 | actions = [ 76 | "secretsmanager:UpdateSecret", 77 | "secretsmanager:PutSecretValue" 78 | ] 79 | 80 | resources = [ 81 | aws_secretsmanager_secret.this.arn 82 | ] 83 | } 84 | } 85 | 86 | resource "aws_iam_role_policy" "secret_write" { 87 | name_prefix = "${var.name}-secret-write-" 88 | policy = data.aws_iam_policy_document.secret_write.json 89 | role = aws_iam_role.this.name 90 | } 91 | 92 | resource "aws_lambda_function" "this" { 93 | description = "Uses ${local.lambda_repo_name} version ${local.lambda_version} to generate a random password and save it to a SecretsManager Secret" 94 | filename = local.zipfile 95 | function_name = var.name 96 | handler = "index.handler" 97 | role = aws_iam_role.this.arn 98 | runtime = "nodejs12.x" 99 | source_code_hash = data.external.sha.result.sha 100 | 101 | tags = merge( 102 | var.tags, 103 | { 104 | "Name" = var.name 105 | } 106 | ) 107 | 108 | lifecycle { 109 | ignore_changes = [ 110 | last_modified 111 | ] 112 | } 113 | 114 | depends_on = [ 115 | null_resource.lambda_zip 116 | ] 117 | } 118 | 119 | resource "aws_secretsmanager_secret" "this" { 120 | name_prefix = "${var.name}-" 121 | description = var.secret_description 122 | tags = merge( 123 | var.tags, 124 | { 125 | Name = var.name 126 | } 127 | ) 128 | } 129 | 130 | locals { 131 | password_params = { 132 | length = var.length 133 | upper = var.upper 134 | min_upper = var.min_upper 135 | lower = var.lower 136 | min_lower = var.min_lower 137 | number = var.number 138 | min_numeric = var.min_numeric 139 | special = var.special 140 | min_special = var.min_special 141 | override_special = var.override_special 142 | secret_name = aws_secretsmanager_secret.this.name 143 | secret_description = var.secret_description 144 | } 145 | } 146 | 147 | # # Using a random_string as a trigger for when the password should be re-created. 148 | # # This allows the Terraform re-creation logic to be duplicated exactly. 149 | resource "random_string" "trigger" { 150 | keepers = var.keepers 151 | length = var.length 152 | lower = var.lower 153 | min_lower = var.min_lower 154 | min_numeric = var.min_numeric 155 | min_special = var.min_special 156 | min_upper = var.min_upper 157 | number = var.number 158 | override_special = var.override_special 159 | special = var.special 160 | upper = var.upper 161 | } 162 | 163 | module "lambda_invocation" { 164 | source = "matti/resource/shell" 165 | version = "~>1.0.7" 166 | 167 | command = "aws lambda invoke --function-name ${aws_lambda_function.this.function_name} --payload '${jsonencode(local.password_params)}' ${path.module}/tmp/lambda_invocation_output" 168 | trigger = random_string.trigger.result 169 | 170 | depends = [ 171 | aws_lambda_function.this.arn, 172 | aws_secretsmanager_secret.this 173 | ] 174 | } 175 | 176 | # must use a managed resource rather than a data resource here as a data resource triggers on every run 177 | module "lambda_invocation_result" { 178 | source = "matti/resource/shell" 179 | version = "~>1.0.7" 180 | 181 | command = "cat ${path.module}/tmp/lambda_invocation_output" 182 | depends = [module.lambda_invocation] 183 | trigger = module.lambda_invocation.id 184 | working_dir = path.module 185 | } 186 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "lambda_version" { 2 | description = "The selected version of the Lambda code" 3 | value = module.lambda_version.target_version 4 | } 5 | 6 | output "lambda_version_info" { 7 | description = "all information about the selected version of the Lambda code" 8 | value = module.lambda_version.version_info 9 | } 10 | 11 | output "result" { 12 | description = "String result of Lambda execution" 13 | value = module.lambda_invocation_result.stdout 14 | } 15 | 16 | output "secret_arn" { 17 | description = "ARN of the secret containing the password" 18 | value = aws_secretsmanager_secret.this.arn 19 | } 20 | 21 | output "secret_name" { 22 | description = "Name of the secret containing the password" 23 | value = aws_secretsmanager_secret.this.name 24 | } 25 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "name" { 3 | description = "Moniker to apply to all resources in the module" 4 | type = string 5 | } 6 | 7 | variable "secret_description" { 8 | default = "A password created by Terraform" #tfsec:ignore:GEN001 9 | description = "Set a description for the secret" 10 | type = string 11 | } 12 | 13 | variable "tags" { 14 | default = {} 15 | description = "User-Defined tags" 16 | type = map(string) 17 | } 18 | 19 | variable "lambda_version_constraint" { 20 | default = "^1.0.3" 21 | description = "NPM-style version constraint for the version of the lambda code you want to use" 22 | type = string 23 | } 24 | 25 | variable "length" { 26 | description = "Length of the password to be created" 27 | type = number 28 | } 29 | 30 | variable "lower" { 31 | default = true 32 | description = "Whether to use lower case characters" 33 | type = bool 34 | } 35 | 36 | variable "min_lower" { 37 | default = 0 38 | description = "Minimum number of lowercase letters" 39 | type = number 40 | } 41 | 42 | variable "number" { 43 | default = true 44 | description = "Whether to use numbers" 45 | type = bool 46 | } 47 | 48 | variable "min_numeric" { 49 | default = 1 50 | description = "Minimum number of numeric characters to use. Must be at least 1" 51 | type = number 52 | } 53 | 54 | variable "special" { 55 | default = true 56 | description = "Whether to use special characters" 57 | type = bool 58 | } 59 | 60 | variable "min_special" { 61 | default = 1 62 | description = "Minimum number of special characters to use. Must be at least 1" 63 | type = number 64 | } 65 | 66 | variable "override_special" { 67 | default = "!@#$%&*()-_=+[]{}<>:?" 68 | description = "Supply your own list of special characters to use for string generation" 69 | type = string 70 | } 71 | 72 | variable "upper" { 73 | default = true 74 | description = "Whether to use uppercase characters" 75 | type = bool 76 | } 77 | 78 | variable "min_upper" { 79 | default = 1 80 | description = "Minimum number of uppercase characters to use. Must be at least 1" 81 | type = number 82 | } 83 | 84 | variable "keepers" { 85 | default = {} 86 | description = "Arbitrary map of values that when changed will force a new password" 87 | type = map(string) 88 | } 89 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.28" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.45" 8 | } 9 | 10 | external = { 11 | source = "hashicorp/external" 12 | version = ">= 1.2" 13 | } 14 | 15 | null = { 16 | source = "hashicorp/null" 17 | version = ">= 2.1" 18 | } 19 | 20 | random = { 21 | source = "hashicorp/random" 22 | version = ">= 2.3" 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------