├── .github
└── workflows
│ ├── terraform-CD.yml
│ └── terraform-CI.yml
├── .gitignore
├── Makefile
├── README.md
├── assets
└── get_secret.py
├── data_sources.tf
├── hooks
└── pre-commit
├── iam.tf
├── locals.tf
├── main.tf
├── outputs.tf
├── requirements.txt
├── terraform.tf
├── test_data
├── probe_role
│ ├── data_sources.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ ├── terraform.tf
│ └── variables.tf
└── secret
│ ├── datasources.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ ├── terraform.tf
│ └── variables.tf
├── tests
├── __init__.py
├── conftest.py
└── test_module.py
└── variables.tf
/.github/workflows/terraform-CD.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'Terraform CD'
3 |
4 | on: # yamllint disable-line rule:truthy
5 | push:
6 | # Pattern matched against refs/tags
7 | tags:
8 | - "*" # Push events to every tag not containing /
9 |
10 | permissions:
11 | id-token: write # This is required for requesting the JWT
12 | contents: read
13 |
14 | env:
15 | ROLE_ARN: "arn:aws:iam::493370826424:role/ih-tf-terraform-aws-secret-github"
16 | AWS_DEFAULT_REGION: "us-west-1"
17 |
18 | jobs:
19 | publish:
20 | name: 'Publish Module'
21 | runs-on: ubuntu-latest
22 | environment: production
23 | timeout-minutes: 60
24 | # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
25 | defaults:
26 | run:
27 | shell: bash
28 |
29 | steps:
30 | # Checkout the repository to the GitHub Actions runner
31 | - name: Checkout
32 | uses: actions/checkout@v3
33 |
34 | - name: Configure AWS Credentials
35 | uses: aws-actions/configure-aws-credentials@v2
36 | with:
37 | role-to-assume: ${{ env.ROLE_ARN }}
38 | role-session-name: github-actions
39 | aws-region: ${{ env.AWS_DEFAULT_REGION }}
40 |
41 | # Prepare Python environment
42 | - name: Setup Python Environment
43 | run: make bootstrap
44 |
45 | # Publish the module
46 | - name: Publish module
47 | run: |
48 | ih-registry upload
49 |
--------------------------------------------------------------------------------
/.github/workflows/terraform-CI.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'Terraform CI'
3 |
4 | on: # yamllint disable-line rule:truthy
5 | pull_request:
6 |
7 | permissions:
8 | id-token: write # This is required for requesting the JWT
9 | contents: read
10 |
11 | jobs:
12 | terraform:
13 | name: 'Terraform Test'
14 | runs-on: ubuntu-latest
15 | timeout-minutes: 120
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
18 | ROLE_ARN: "arn:aws:iam::303467602807:role/secret-tester"
19 |
20 | # Use the Bash shell regardless whether the GitHub Actions runner
21 | # is ubuntu-latest, macos-latest, or windows-latest
22 | defaults:
23 | run:
24 | shell: bash
25 |
26 | steps:
27 | # Checkout the repository to the GitHub Actions runner
28 | - name: Checkout
29 | uses: actions/checkout@v3
30 |
31 | - name: Configure AWS Credentials
32 | uses: aws-actions/configure-aws-credentials@v2
33 | with:
34 | role-to-assume: ${{ env.ROLE_ARN }}
35 | role-session-name: "terraform-ci"
36 | aws-region: "us-west-1"
37 |
38 | # Install the latest version of Terraform CLI
39 | - name: Setup Terraform
40 | uses: hashicorp/setup-terraform@v2
41 | with:
42 | terraform_wrapper: false
43 |
44 | # Prepare Python environment
45 | - name: Setup Python Environment
46 | run: make bootstrap
47 |
48 | # Run all required linters
49 | - name: Code Style Check
50 | run: make lint
51 |
52 | # Generates an execution plan for Terraform
53 | - name: Terraform Tests
54 | run: make test
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local .terraform directories
2 | **/.terraform/*
3 |
4 | # .tfstate files
5 | *.tfstate
6 | *.tfstate.*
7 |
8 | # Crash log files
9 | crash.log
10 |
11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most
12 | # .tfvars files are managed as part of configuration and so should be included in
13 | # version control.
14 | #
15 | # example.tfvars
16 |
17 | # Ignore override files as they are usually used to override resources locally and so
18 | # are not checked in
19 | override.tf
20 | override.tf.json
21 | *_override.tf
22 | *_override.tf.json
23 |
24 | # Include override files you do wish to add to version control using negated pattern
25 | #
26 | # !example_override.tf
27 |
28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
29 | # example: *tfplan*
30 | .terraform.lock.hcl
31 | terraform.tfvars
32 | tf.plan
33 | .idea
34 | /docs/_build/
35 | /plan.stderr
36 | /plan.stdout
37 | __pycache__
38 | .pytest_cache
39 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .DEFAULT_GOAL := help
2 |
3 | define PRINT_HELP_PYSCRIPT
4 | import re, sys
5 |
6 | for line in sys.stdin:
7 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
8 | if match:
9 | target, help = match.groups()
10 | print("%-40s %s" % (target, help))
11 | endef
12 | export PRINT_HELP_PYSCRIPT
13 |
14 | help: install-hooks
15 | @python -c "$$PRINT_HELP_PYSCRIPT" < Makefile
16 |
17 | .PHONY: install-hooks
18 | install-hooks: ## Install repo hooks
19 | @echo "Checking and installing hooks"
20 | @test -d .git/hooks || (echo "Looks like you are not in a Git repo" ; exit 1)
21 | @test -L .git/hooks/pre-commit || ln -fs ../../hooks/pre-commit .git/hooks/pre-commit
22 | @chmod +x .git/hooks/pre-commit
23 |
24 |
25 | .PHONY: test
26 | test: ## Run tests on the module
27 | rm -f test_data/test_module/.terraform.lock.hcl
28 | pytest -xvvs tests/
29 |
30 |
31 | .PHONY: bootstrap
32 | bootstrap: ## bootstrap the development environment
33 | pip install -U "pip ~= 23.1"
34 | pip install -U "setuptools ~= 68.0"
35 | pip install -r requirements.txt
36 |
37 | .PHONY: clean
38 | clean: ## clean the repo from cruft
39 | rm -rf .pytest_cache
40 | find . -name '.terraform' -exec rm -fr {} +
41 |
42 | .PHONY: fmt
43 | fmt: format
44 |
45 | .PHONY: format
46 | format: ## Use terraform fmt to format all files in the repo
47 | @echo "Formatting terraform files"
48 | terraform fmt -recursive
49 | black tests
50 |
51 | define BROWSER_PYSCRIPT
52 | import os, webbrowser, sys
53 |
54 | from urllib.request import pathname2url
55 |
56 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
57 | endef
58 | export BROWSER_PYSCRIPT
59 |
60 | BROWSER := python -c "$$BROWSER_PYSCRIPT"
61 |
62 | .PHONY: docs
63 | docs: ## generate Sphinx HTML documentation, including API docs
64 | $(MAKE) -C docs clean
65 | $(MAKE) -C docs html
66 | $(BROWSER) docs/_build/html/index.html
67 |
68 | .PHONY: lint
69 | lint: ## Lint the module
70 | @echo "Check code style"
71 | black --check tests
72 | terraform fmt -check
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # terraform-aws-secret
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | ~> 1.5 |
7 | | [aws](#requirement\_aws) | ~> 5.11 |
8 |
9 | ## Providers
10 |
11 | | Name | Version |
12 | |------|---------|
13 | | [aws](#provider\_aws) | ~> 5.11 |
14 | | [external](#provider\_external) | n/a |
15 |
16 | ## Modules
17 |
18 | No modules.
19 |
20 | ## Resources
21 |
22 | | Name | Type |
23 | |------|------|
24 | | [aws_secretsmanager_secret.secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
25 | | [aws_secretsmanager_secret_version.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
26 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
27 | | [aws_iam_policy_document.permission-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
28 | | [aws_iam_role.caller_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source |
29 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
30 | | [external_external.secret_value](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source |
31 |
32 | ## Inputs
33 |
34 | | Name | Description | Type | Default | Required |
35 | |------|-------------|------|---------|:--------:|
36 | | [admins](#input\_admins) | List of role ARNs that will have all permissions of the secret. | `list(string)` | `null` | no |
37 | | [environment](#input\_environment) | Name of environment. | `string` | n/a | yes |
38 | | [owner](#input\_owner) | A tag owner with this value will be placed on a secret. | `string` | `null` | no |
39 | | [readers](#input\_readers) | List of role ARNs that will have read permissions of the secret. | `list(string)` | `null` | no |
40 | | [secret\_description](#input\_secret\_description) | The secret description in AWS Secretsmanager. | `string` | n/a | yes |
41 | | [secret\_name](#input\_secret\_name) | Name of the secret in AWS Secretsmanager. Either secret\_name or secret\_name\_prefix must be set. | `string` | `null` | no |
42 | | [secret\_name\_prefix](#input\_secret\_name\_prefix) | Name prefix of the secret in AWS Secretsmanager. Either secret\_name or secret\_name\_prefix must be set. | `string` | `null` | no |
43 | | [secret\_value](#input\_secret\_value) | Optional value of the secret. | `string` | `null` | no |
44 | | [service\_name](#input\_service\_name) | Descriptive name of a service that will use this secret. | `string` | `"unknown"` | no |
45 | | [tags](#input\_tags) | Tags to apply to secret and other resources the module creates. | `map(string)` | `{}` | no |
46 | | [writers](#input\_writers) | List of role ARNs that will have write permissions of the secret. | `list(string)` | `null` | no |
47 |
48 | ## Outputs
49 |
50 | | Name | Description |
51 | |------|-------------|
52 | | [secret\_arn](#output\_secret\_arn) | ARN of the created secret |
53 | | [secret\_id](#output\_secret\_id) | ID of the created secret |
54 | | [secret\_name](#output\_secret\_name) | Name of the created secret |
55 | | [secret\_value](#output\_secret\_value) | The current secret value. If the value isn't set yet, return `null`. |
56 |
--------------------------------------------------------------------------------
/assets/get_secret.py:
--------------------------------------------------------------------------------
1 | import json
2 | import sys
3 |
4 | import boto3
5 | from botocore.exceptions import ClientError
6 |
7 |
8 | def get_secret(secretsmanager_client, secret_id):
9 | """
10 | Retrieve a value of a secret by its name.
11 | """
12 | try:
13 | response = secretsmanager_client.get_secret_value(
14 | SecretId=secret_id,
15 | )
16 | return response["SecretString"]
17 | except ClientError as e:
18 | if e.response["Error"]["Code"] == "ResourceNotFoundException":
19 | return None
20 | raise
21 |
22 |
23 | def get_client(region, role_arn):
24 | sts = boto3.client("sts")
25 | iam_role = sts.assume_role(
26 | RoleArn=role_arn, RoleSessionName="terraform-aws-secret-data-source"
27 | )
28 | session = boto3.Session(
29 | region_name=region,
30 | aws_access_key_id=iam_role["Credentials"]["AccessKeyId"],
31 | aws_secret_access_key=iam_role["Credentials"]["SecretAccessKey"],
32 | aws_session_token=iam_role["Credentials"]["SessionToken"],
33 | )
34 | return session.client("secretsmanager")
35 |
36 |
37 | if __name__ == "__main__":
38 |
39 | print(
40 | json.dumps(
41 | {
42 | "SECRET_VALUE": get_secret(
43 | get_client(region=sys.argv[1], role_arn=sys.argv[3]),
44 | secret_id=sys.argv[2],
45 | )
46 | }
47 | )
48 | )
49 |
--------------------------------------------------------------------------------
/data_sources.tf:
--------------------------------------------------------------------------------
1 | # Data sources are defined here
2 | data "aws_caller_identity" "current" {}
3 | data "aws_region" "current" {}
4 |
5 | data "aws_iam_role" "caller_role" {
6 | name = split("/", split(":", data.aws_caller_identity.current.arn)[5])[1]
7 | }
8 |
9 | data "external" "secret_value" {
10 | program = [
11 | "python", "${path.module}/assets/get_secret.py", data.aws_region.current.name, aws_secretsmanager_secret.secret.id, data.aws_iam_role.caller_role.arn
12 | ]
13 | depends_on = [
14 | aws_secretsmanager_secret_version.current
15 | ]
16 | }
17 |
18 | data "aws_iam_role" "accessanalyzer" {
19 | name = "AWSServiceRoleForAccessAnalyzer"
20 | }
21 |
--------------------------------------------------------------------------------
/hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "Happy coding!"
4 |
--------------------------------------------------------------------------------
/iam.tf:
--------------------------------------------------------------------------------
1 | data "aws_iam_policy_document" "permission-policy" {
2 | statement {
3 | principals {
4 | identifiers = ["*"]
5 | type = "AWS"
6 | }
7 | condition {
8 | test = "ArnLike"
9 | values = concat(var.admins == null ? [] : var.admins, [data.aws_iam_role.caller_role.arn])
10 | variable = "aws:PrincipalArn"
11 | }
12 | actions = local.all_actions
13 | resources = [
14 | "*"
15 | ]
16 | }
17 |
18 | ## Writers
19 | dynamic "statement" {
20 | for_each = var.writers != null ? [{}] : []
21 | content {
22 | principals {
23 | identifiers = ["*"]
24 | type = "AWS"
25 | }
26 | condition {
27 | test = "ArnLike"
28 | values = var.writers
29 | variable = "aws:PrincipalArn"
30 | }
31 | actions = concat(
32 | local.list_actions,
33 | local.read_actions,
34 | local.write_actions
35 | )
36 | resources = [
37 | "*"
38 | ]
39 | }
40 | }
41 |
42 | dynamic "statement" {
43 | for_each = var.writers != null ? [{}] : []
44 | content {
45 | effect = "Deny"
46 | principals {
47 | identifiers = ["*"]
48 | type = "AWS"
49 | }
50 | condition {
51 | test = "ArnLike"
52 | values = var.writers
53 | variable = "aws:PrincipalArn"
54 | }
55 | actions = concat(
56 | local.admin_actions,
57 | local.permission_management_actions,
58 | local.tagging_actions
59 | )
60 | resources = [
61 | "*"
62 | ]
63 | }
64 | }
65 |
66 | ## Readers
67 | dynamic "statement" {
68 | for_each = var.readers != null ? [{}] : []
69 | content {
70 | principals {
71 | identifiers = ["*"]
72 | type = "AWS"
73 | }
74 | condition {
75 | test = "ArnLike"
76 | values = local.readers_only
77 | variable = "aws:PrincipalArn"
78 | }
79 | actions = local.read_actions
80 | resources = [
81 | "*"
82 | ]
83 | }
84 | }
85 |
86 | dynamic "statement" {
87 | for_each = var.readers != null ? [{}] : []
88 | content {
89 | effect = "Deny"
90 | principals {
91 | identifiers = ["*"]
92 | type = "AWS"
93 | }
94 | condition {
95 | test = "ArnLike"
96 | values = local.readers_only
97 | variable = "aws:PrincipalArn"
98 | }
99 | actions = concat(
100 | local.list_actions,
101 | local.admin_actions,
102 | local.write_actions,
103 | local.permission_management_actions,
104 | local.tagging_actions
105 | )
106 | resources = [
107 | "*"
108 | ]
109 | }
110 | }
111 |
112 | # Access Analyzer permissions
113 | statement {
114 | effect = "Allow"
115 | principals {
116 | type = "AWS"
117 | identifiers = [
118 | data.aws_iam_role.accessanalyzer.arn
119 | ]
120 | }
121 | actions = local.access_analyzer_actions
122 | resources = ["*"]
123 | }
124 |
125 | statement {
126 | effect = "Deny"
127 | principals {
128 | type = "AWS"
129 | identifiers = [
130 | data.aws_iam_role.accessanalyzer.arn
131 | ]
132 | }
133 | actions = setsubtract(
134 | concat(
135 | local.list_actions,
136 | local.read_actions,
137 | local.write_actions,
138 | local.admin_actions,
139 | local.permission_management_actions,
140 | local.tagging_actions,
141 | ),
142 | local.access_analyzer_actions
143 | )
144 | resources = ["*"]
145 | }
146 |
147 | ## The rest
148 | statement {
149 | effect = "Deny"
150 | principals {
151 | type = "AWS"
152 | identifiers = ["*"]
153 | }
154 | actions = local.all_actions
155 | resources = [
156 | "*"
157 | ]
158 | condition {
159 | test = "ArnNotLike"
160 | values = concat(
161 | [
162 | data.aws_iam_role.caller_role.arn,
163 | data.aws_iam_role.accessanalyzer.arn
164 | ],
165 | var.admins == null ? [] : var.admins,
166 | var.writers == null ? [] : var.writers,
167 | var.readers == null ? [] : var.readers
168 | )
169 | variable = "aws:PrincipalArn"
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/locals.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | access_analyzer_actions = [
3 | "secretsmanager:DescribeSecret",
4 | "secretsmanager:GetResourcePolicy",
5 | "secretsmanager:ListSecrets",
6 | ]
7 | list_actions = [
8 | "secretsmanager:BatchGetSecretValue",
9 | "secretsmanager:ListSecrets",
10 | ]
11 | admin_actions = [
12 | "secretsmanager:CreateSecret",
13 | "secretsmanager:DeleteSecret",
14 | "secretsmanager:StopReplicationToReplica",
15 | "secretsmanager:ReplicateSecretToRegions",
16 | "secretsmanager:RemoveRegionsFromReplication",
17 | ]
18 | read_actions = [
19 | "secretsmanager:DescribeSecret",
20 | "secretsmanager:GetSecretValue",
21 | "secretsmanager:GetRandomPassword",
22 | "secretsmanager:ListSecretVersionIds",
23 | "secretsmanager:GetResourcePolicy",
24 | ]
25 | write_actions = [
26 | "secretsmanager:PutSecretValue",
27 | "secretsmanager:CancelRotateSecret",
28 | "secretsmanager:UpdateSecret",
29 | "secretsmanager:RestoreSecret",
30 | "secretsmanager:RotateSecret",
31 | "secretsmanager:UpdateSecretVersionStage",
32 | ]
33 | permission_management_actions = [
34 | "secretsmanager:DeleteResourcePolicy",
35 | "secretsmanager:PutResourcePolicy",
36 | "secretsmanager:ValidateResourcePolicy",
37 | ]
38 | tagging_actions = [
39 | "secretsmanager:TagResource",
40 | "secretsmanager:UntagResource",
41 | ]
42 | all_actions = ["*"]
43 |
44 | default_module_tags = {
45 | environment : var.environment
46 | service : var.service_name
47 | account : data.aws_caller_identity.current.account_id
48 | created_by_module : "infrahouse/secret/aws"
49 | }
50 |
51 | readers_only = var.readers != null ? (
52 | var.writers != null ? setsubtract(
53 | toset(var.readers),
54 | toset(var.writers)
55 | ) : var.readers
56 | ) : null
57 | }
58 |
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "aws_secretsmanager_secret" "secret" {
3 | description = var.secret_description
4 | name = var.secret_name
5 | name_prefix = var.secret_name_prefix
6 | recovery_window_in_days = 0
7 | policy = data.aws_iam_policy_document.permission-policy.json
8 | tags = merge(
9 | {
10 | owner : var.owner == null ? data.aws_iam_role.caller_role.arn : var.owner
11 | },
12 | var.tags,
13 | local.default_module_tags,
14 | )
15 | }
16 |
17 | resource "aws_secretsmanager_secret_version" "current" {
18 | secret_id = aws_secretsmanager_secret.secret.id
19 | secret_string = var.secret_value == null ? "NoValue" : var.secret_value
20 | version_stages = [
21 | var.secret_value == null ? "INITIAL" : "AWSCURRENT"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/outputs.tf:
--------------------------------------------------------------------------------
1 | output "secret_name" {
2 | description = "Name of the created secret"
3 | value = aws_secretsmanager_secret.secret.name
4 | }
5 |
6 | output "secret_arn" {
7 | description = "ARN of the created secret"
8 | value = aws_secretsmanager_secret.secret.arn
9 | }
10 |
11 | output "secret_id" {
12 | description = "ID of the created secret"
13 | value = aws_secretsmanager_secret.secret.id
14 | }
15 |
16 | output "secret_value" {
17 | description = "The current secret value. If the value isn't set yet, return `null`."
18 | value = data.external.secret_value.result["SECRET_VALUE"]
19 | sensitive = true
20 | }
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | black ~= 24.3
2 | boto3 ~= 1.26
3 | botocore ~= 1.26
4 | infrahouse-toolkit ~= 2.0
5 | myst-parser ~= 2.0
6 | pytest-timeout ~= 2.1
7 | pytest-rerunfailures ~= 12.0
8 | requests ~= 2.31
9 |
--------------------------------------------------------------------------------
/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.5"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = "~> 5.11"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test_data/probe_role/data_sources.tf:
--------------------------------------------------------------------------------
1 | data "aws_iam_policy_document" "permissions" {
2 | statement {
3 | actions = [
4 | "secretsmanager:*"
5 | ]
6 | resources = [
7 | "*"
8 | ]
9 | }
10 | }
11 |
12 | data "aws_iam_policy_document" "trust" {
13 | statement {
14 | actions = ["sts:AssumeRole"]
15 | principals {
16 | type = "AWS"
17 | identifiers = var.trusted_arns
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test_data/probe_role/main.tf:
--------------------------------------------------------------------------------
1 | resource "aws_iam_role" "probe" {
2 | assume_role_policy = data.aws_iam_policy_document.trust.json
3 | }
4 |
5 | resource "aws_iam_role_policy" "probe" {
6 | policy = data.aws_iam_policy_document.permissions.json
7 | role = aws_iam_role.probe.id
8 | }
9 |
--------------------------------------------------------------------------------
/test_data/probe_role/outputs.tf:
--------------------------------------------------------------------------------
1 | output "role_name" {
2 | value = aws_iam_role.probe.name
3 | }
4 |
5 | output "role_arn" {
6 | value = aws_iam_role.probe.arn
7 | }
8 |
--------------------------------------------------------------------------------
/test_data/probe_role/providers.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | assume_role {
3 | role_arn = var.role_arn
4 | }
5 | region = var.region
6 | default_tags {
7 | tags = {
8 | "created_by" : "infrahouse/terraform-aws-secret" # GitHub repository that created a resource
9 | }
10 |
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test_data/probe_role/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 5.11"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test_data/probe_role/variables.tf:
--------------------------------------------------------------------------------
1 | variable "region" {}
2 | variable "role_arn" {}
3 |
4 | variable "trusted_arns" {
5 | description = "List of ARNs allowed to assume the probe role"
6 | type = list(string)
7 | }
8 |
--------------------------------------------------------------------------------
/test_data/secret/datasources.tf:
--------------------------------------------------------------------------------
1 | data "aws_caller_identity" "this" {}
2 |
--------------------------------------------------------------------------------
/test_data/secret/main.tf:
--------------------------------------------------------------------------------
1 | module "test" {
2 | source = "../../"
3 | secret_description = "Foo description"
4 | secret_name = var.secret_name
5 | secret_name_prefix = var.secret_name_prefix
6 | admins = var.admins
7 | writers = var.writers
8 | readers = var.readers
9 | secret_value = var.secret_value == "generate" ? random_password.value.result : var.secret_value
10 | tags = var.tags
11 | environment = "development"
12 | }
13 |
14 | resource "random_password" "value" {
15 | length = 16
16 | }
17 |
--------------------------------------------------------------------------------
/test_data/secret/outputs.tf:
--------------------------------------------------------------------------------
1 | output "secret_value" {
2 | value = module.test.secret_value
3 | sensitive = true
4 | }
5 |
6 | output "secret_arn" {
7 | value = module.test.secret_arn
8 | }
9 |
10 | output "secret_name" {
11 | value = module.test.secret_name
12 | }
13 |
--------------------------------------------------------------------------------
/test_data/secret/providers.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = var.region
3 | assume_role {
4 | role_arn = var.role_arn
5 | }
6 | default_tags {
7 | tags = {
8 | "created_by" : "infrahouse/terraform-aws-secret" # GitHub repository that created a resource
9 | }
10 |
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test_data/secret/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | //noinspection HILUnresolvedReference
3 | required_providers {
4 | aws = {
5 | source = "hashicorp/aws"
6 | version = "~> 5.11"
7 | }
8 | cloudinit = {
9 | source = "hashicorp/cloudinit"
10 | version = "~> 2.3"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test_data/secret/variables.tf:
--------------------------------------------------------------------------------
1 | variable "region" {}
2 | variable "role_arn" {}
3 |
4 | variable "admins" { default = null }
5 | variable "writers" { default = null }
6 | variable "readers" { default = null }
7 | variable "secret_name" {
8 | default = "foo"
9 | }
10 | variable "secret_name_prefix" { default = null }
11 | variable "secret_value" {
12 | default = "bar"
13 | }
14 | variable "tags" { default = null }
15 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/infrahouse/terraform-aws-secret/01731570de723f3db0b128046e39ca724d333691/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from textwrap import dedent
2 |
3 | import boto3
4 | import pytest
5 | import logging
6 | from os import path as osp
7 |
8 | from infrahouse_toolkit.logging import setup_logging
9 | from infrahouse_toolkit.terraform import terraform_apply
10 |
11 | # "303467602807" is our test account
12 | TEST_ACCOUNT = "303467602807"
13 | TEST_ROLE_ARN = "arn:aws:iam::303467602807:role/secret-tester"
14 | DEFAULT_PROGRESS_INTERVAL = 10
15 | TRACE_TERRAFORM = False
16 | UBUNTU_CODENAME = "jammy"
17 |
18 | LOG = logging.getLogger(__name__)
19 | REGION = "us-east-2"
20 | TEST_ZONE = "ci-cd.infrahouse.com"
21 | TERRAFORM_ROOT_DIR = "test_data"
22 |
23 |
24 | setup_logging(LOG, debug=True)
25 |
26 |
27 | def pytest_addoption(parser):
28 | parser.addoption(
29 | "--keep-after",
30 | action="store_true",
31 | default=False,
32 | help="If specified, don't destroy resources.",
33 | )
34 | parser.addoption(
35 | "--test-role-arn",
36 | action="store",
37 | default=TEST_ROLE_ARN,
38 | help=f"AWS IAM role ARN that will create resources. Default, {TEST_ROLE_ARN}",
39 | )
40 |
41 |
42 | @pytest.fixture(scope="session")
43 | def keep_after(request):
44 | return request.config.getoption("--keep-after")
45 |
46 |
47 | @pytest.fixture(scope="session")
48 | def test_role_arn(request):
49 | return request.config.getoption("--test-role-arn")
50 |
51 |
52 | @pytest.fixture(scope="session")
53 | def aws_iam_role():
54 | sts = boto3.client("sts")
55 | return sts.assume_role(
56 | RoleArn=TEST_ROLE_ARN, RoleSessionName=TEST_ROLE_ARN.split("/")[1]
57 | )
58 |
59 |
60 | @pytest.fixture(scope="session")
61 | def boto3_session(aws_iam_role):
62 | return boto3.Session(
63 | aws_access_key_id=aws_iam_role["Credentials"]["AccessKeyId"],
64 | aws_secret_access_key=aws_iam_role["Credentials"]["SecretAccessKey"],
65 | aws_session_token=aws_iam_role["Credentials"]["SessionToken"],
66 | )
67 |
68 |
69 | @pytest.fixture(scope="session")
70 | def ec2_client(boto3_session):
71 | assert boto3_session.client("sts").get_caller_identity()["Account"] == TEST_ACCOUNT
72 | return boto3_session.client("ec2", region_name=REGION)
73 |
74 |
75 | @pytest.fixture(scope="session")
76 | def ec2_client_map(ec2_client, boto3_session):
77 | regions = [reg["RegionName"] for reg in ec2_client.describe_regions()["Regions"]]
78 | ec2_map = {reg: boto3_session.client("ec2", region_name=reg) for reg in regions}
79 |
80 | return ec2_map
81 |
82 |
83 | @pytest.fixture()
84 | def route53_client(boto3_session):
85 | return boto3_session.client("route53", region_name=REGION)
86 |
87 |
88 | @pytest.fixture()
89 | def elbv2_client(boto3_session):
90 | return boto3_session.client("elbv2", region_name=REGION)
91 |
92 |
93 | @pytest.fixture()
94 | def autoscaling_client(boto3_session):
95 | assert boto3_session.client("sts").get_caller_identity()["Account"] == TEST_ACCOUNT
96 | return boto3_session.client("autoscaling", region_name=REGION)
97 |
98 |
99 | @pytest.fixture()
100 | def secretsmanager_client(boto3_session):
101 | assert boto3_session.client("sts").get_caller_identity()["Account"] == TEST_ACCOUNT
102 | return boto3_session.client("secretsmanager", region_name=REGION)
103 |
104 |
105 | def get_secretsmanager_client_by_role(role_name):
106 | response = boto3.client("sts").assume_role(
107 | RoleArn=role_name, RoleSessionName=TEST_ROLE_ARN.split("/")[1]
108 | )
109 | # noinspection PyUnresolvedReferences
110 | return boto3.Session(
111 | aws_access_key_id=response["Credentials"]["AccessKeyId"],
112 | aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
113 | aws_session_token=response["Credentials"]["SessionToken"],
114 | ).client("secretsmanager", region_name=REGION)
115 |
116 |
117 | @pytest.fixture()
118 | def probe_role(boto3_session, keep_after):
119 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "probe_role")
120 | # Create service network
121 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
122 | fp.write(
123 | dedent(
124 | f"""
125 | role_arn = "{TEST_ROLE_ARN}"
126 | region = "{REGION}"
127 | trusted_arns = [
128 | "arn:aws:iam::990466748045:user/aleks",
129 | "{TEST_ROLE_ARN}"
130 | ]
131 | """
132 | )
133 | )
134 | with terraform_apply(
135 | terraform_module_dir,
136 | destroy_after=not keep_after,
137 | json_output=True,
138 | enable_trace=TRACE_TERRAFORM,
139 | ) as tf_output:
140 | yield tf_output
141 |
--------------------------------------------------------------------------------
/tests/test_module.py:
--------------------------------------------------------------------------------
1 | import json
2 | from os import path as osp
3 | from textwrap import dedent
4 |
5 | import pytest
6 | from botocore.exceptions import ClientError
7 | from infrahouse_toolkit.terraform import terraform_apply
8 |
9 | from tests.conftest import (
10 | LOG,
11 | TRACE_TERRAFORM,
12 | REGION,
13 | TERRAFORM_ROOT_DIR,
14 | get_secretsmanager_client_by_role,
15 | )
16 |
17 |
18 | @pytest.mark.parametrize("probe_role_suffix", ["", "*"])
19 | def test_module(probe_role, keep_after, test_role_arn, probe_role_suffix):
20 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
21 | probe_role_arn = probe_role["role_arn"]["value"]
22 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
23 | fp.write(
24 | dedent(
25 | f"""
26 | region = "{REGION}"
27 | role_arn = "{test_role_arn}"
28 |
29 | admins = [
30 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
31 | "{probe_role_arn}{probe_role_suffix}"
32 | ]
33 | """
34 | )
35 | )
36 |
37 | with terraform_apply(
38 | terraform_module_dir,
39 | destroy_after=not keep_after,
40 | json_output=True,
41 | enable_trace=TRACE_TERRAFORM,
42 | ) as tf_output:
43 | LOG.info("%s", json.dumps(tf_output, indent=4))
44 |
45 |
46 | def test_module_no_access(probe_role, secretsmanager_client, keep_after, test_role_arn):
47 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
48 | probe_role_arn = probe_role["role_arn"]["value"]
49 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
50 | fp.write(
51 | dedent(
52 | f"""
53 | region = "{REGION}"
54 | role_arn = "{test_role_arn}"
55 |
56 | admins = [
57 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
58 | "{test_role_arn}"
59 | ]
60 | """
61 | )
62 | )
63 |
64 | with terraform_apply(
65 | terraform_module_dir,
66 | destroy_after=not keep_after,
67 | json_output=True,
68 | enable_trace=TRACE_TERRAFORM,
69 | ) as tf_output:
70 | LOG.info("%s", json.dumps(tf_output, indent=4))
71 | sm_client = get_secretsmanager_client_by_role(probe_role_arn)
72 | with pytest.raises(ClientError) as err:
73 | sm_client.get_secret_value(
74 | SecretId="foo",
75 | )
76 | assert err.type is ClientError
77 | # Example
78 | # e = {
79 | # "Message": (
80 | # "User: "
81 | # "arn:aws:sts::303467602807:assumed-role/terraform-20240606193517506500000001/secret-tester "
82 | # "is not authorized to perform: secretsmanager:GetSecretValue on resource: foo with "
83 | # "an explicit deny in a resource-based policy"
84 | # ),
85 | # "Code": "AccessDeniedException",
86 | # }
87 | assert err.value.response["Error"]["Code"] == "AccessDeniedException"
88 |
89 |
90 | @pytest.mark.parametrize("probe_role_suffix", ["", "*"])
91 | def test_module_reads(
92 | probe_role, secretsmanager_client, keep_after, test_role_arn, probe_role_suffix
93 | ):
94 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
95 | probe_role_arn = probe_role["role_arn"]["value"]
96 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
97 | fp.write(
98 | dedent(
99 | f"""
100 | region = "{REGION}"
101 | role_arn = "{test_role_arn}"
102 |
103 | admins = [
104 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
105 | "{test_role_arn}"
106 | ]
107 | readers = [
108 | "{probe_role_arn}{probe_role_suffix}"
109 | ]
110 | """
111 | )
112 | )
113 |
114 | with terraform_apply(
115 | terraform_module_dir,
116 | destroy_after=not keep_after,
117 | json_output=True,
118 | enable_trace=TRACE_TERRAFORM,
119 | ) as tf_output:
120 | LOG.info("%s", json.dumps(tf_output, indent=4))
121 | sm_client = get_secretsmanager_client_by_role(probe_role["role_arn"]["value"])
122 | # Can read
123 | assert (
124 | sm_client.get_secret_value(
125 | SecretId="foo",
126 | )["SecretString"]
127 | == "bar"
128 | )
129 |
130 | # Can't write
131 | with pytest.raises(ClientError) as err:
132 | sm_client.put_secret_value(
133 | SecretId="foo",
134 | SecretString="barbar",
135 | )
136 | assert err.type is ClientError
137 | assert err.value.response["Error"]["Code"] == "AccessDeniedException"
138 |
139 |
140 | @pytest.mark.parametrize("probe_role_suffix", ["", "*"])
141 | def test_module_writes(
142 | probe_role, secretsmanager_client, keep_after, test_role_arn, probe_role_suffix
143 | ):
144 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
145 | probe_role_arn = probe_role["role_arn"]["value"]
146 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
147 | fp.write(
148 | dedent(
149 | f"""
150 | region = "{REGION}"
151 | role_arn = "{test_role_arn}"
152 |
153 | admins = [
154 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
155 | "{test_role_arn}"
156 | ]
157 | writers = [
158 | "{probe_role_arn}{probe_role_suffix}"
159 | ]
160 | """
161 | )
162 | )
163 |
164 | with terraform_apply(
165 | terraform_module_dir,
166 | destroy_after=not keep_after,
167 | json_output=True,
168 | enable_trace=TRACE_TERRAFORM,
169 | ) as tf_output:
170 | LOG.info("%s", json.dumps(tf_output, indent=4))
171 | sm_client = get_secretsmanager_client_by_role(probe_role["role_arn"]["value"])
172 |
173 | # Can read
174 | assert (
175 | sm_client.get_secret_value(
176 | SecretId="foo",
177 | )["SecretString"]
178 | == "bar"
179 | )
180 |
181 | # Can write
182 | sm_client.put_secret_value(
183 | SecretId="foo",
184 | SecretString="barbar",
185 | )
186 | assert (
187 | sm_client.get_secret_value(
188 | SecretId="foo",
189 | )["SecretString"]
190 | == "barbar"
191 | )
192 |
193 | # Can't delete
194 | with pytest.raises(ClientError) as err:
195 | sm_client.delete_secret(SecretId="foo", ForceDeleteWithoutRecovery=True)
196 | assert err.type is ClientError
197 | assert err.value.response["Error"]["Code"] == "AccessDeniedException"
198 |
199 |
200 | def test_module_secret_value(probe_role, keep_after, test_role_arn):
201 | probe_role_arn = probe_role["role_arn"]["value"]
202 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
203 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
204 | fp.write(
205 | dedent(
206 | f"""
207 | region = "{REGION}"
208 | role_arn = "{test_role_arn}"
209 | admins = [
210 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
211 | ]
212 |
213 | writers = [
214 | "{probe_role_arn}"
215 | ]
216 | secret_value = "generate"
217 | """
218 | )
219 | )
220 |
221 | with terraform_apply(
222 | terraform_module_dir,
223 | destroy_after=not keep_after,
224 | json_output=True,
225 | enable_trace=TRACE_TERRAFORM,
226 | ) as tf_output:
227 | LOG.info("%s", json.dumps(tf_output, indent=4))
228 |
229 | secret_value_0 = tf_output["secret_value"]["value"]
230 | sm_client = get_secretsmanager_client_by_role(probe_role_arn)
231 | # Can read
232 | assert (
233 | sm_client.get_secret_value(
234 | SecretId="foo",
235 | )["SecretString"]
236 | == secret_value_0
237 | )
238 |
239 | # Overwrite the secret and make sure Terraform reverts the secret
240 | sm_client.put_secret_value(
241 | SecretId="foo",
242 | SecretString="barbar",
243 | )
244 |
245 | with terraform_apply(
246 | terraform_module_dir,
247 | destroy_after=not keep_after,
248 | json_output=True,
249 | enable_trace=TRACE_TERRAFORM,
250 | ):
251 | sm_client = get_secretsmanager_client_by_role(probe_role_arn)
252 | assert (
253 | sm_client.get_secret_value(
254 | SecretId="foo",
255 | )["SecretString"]
256 | == secret_value_0
257 | )
258 |
259 |
260 | def test_module_external_value(probe_role, keep_after, test_role_arn):
261 | """
262 | Create a secret, set the value outside of Terraform
263 | """
264 | probe_role_arn = probe_role["role_arn"]["value"]
265 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
266 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
267 | fp.write(
268 | dedent(
269 | f"""
270 | region = "{REGION}"
271 | role_arn = "{test_role_arn}"
272 | admins = [
273 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
274 | ]
275 |
276 | writers = [
277 | "{probe_role_arn}"
278 | ]
279 | secret_value = null
280 | """
281 | )
282 | )
283 | # Ensure destroy
284 | with terraform_apply(
285 | terraform_module_dir,
286 | destroy_after=True,
287 | json_output=True,
288 | enable_trace=TRACE_TERRAFORM,
289 | ) as tf_output:
290 | LOG.info("%s", json.dumps(tf_output, indent=4))
291 |
292 | # The test itself
293 | with terraform_apply(
294 | terraform_module_dir,
295 | destroy_after=not keep_after,
296 | json_output=True,
297 | enable_trace=TRACE_TERRAFORM,
298 | ) as tf_output:
299 | LOG.info("%s", json.dumps(tf_output, indent=4))
300 |
301 | # secret_value_0 = tf_output["secret_value"]["value"]
302 | sm_client = get_secretsmanager_client_by_role(probe_role_arn)
303 | assert (
304 | sm_client.get_secret_value(
305 | SecretId="foo",
306 | )["SecretString"]
307 | == "NoValue"
308 | )
309 |
310 | # Overwrite the secret and make sure Terraform reverts the secret
311 | sm_client.put_secret_value(
312 | SecretId="foo",
313 | SecretString="barbar",
314 | )
315 |
316 | with terraform_apply(
317 | terraform_module_dir,
318 | destroy_after=not keep_after,
319 | json_output=True,
320 | enable_trace=TRACE_TERRAFORM,
321 | ):
322 | sm_client = get_secretsmanager_client_by_role(probe_role_arn)
323 | assert (
324 | sm_client.get_secret_value(
325 | SecretId="foo",
326 | )["SecretString"]
327 | == "barbar"
328 | )
329 |
330 |
331 | def test_module_name_prefix(keep_after, test_role_arn):
332 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
333 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
334 | fp.write(
335 | dedent(
336 | f"""
337 | region = "{REGION}"
338 | role_arn = "{test_role_arn}"
339 | secret_name = null
340 | secret_name_prefix = "some_secret"
341 |
342 | admins = [
343 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
344 | ]
345 | """
346 | )
347 | )
348 |
349 | with terraform_apply(
350 | terraform_module_dir,
351 | destroy_after=not keep_after,
352 | json_output=True,
353 | enable_trace=TRACE_TERRAFORM,
354 | ) as tf_output:
355 | LOG.info("%s", json.dumps(tf_output, indent=4))
356 | assert tf_output["secret_name"]["value"].startswith("some_secret")
357 |
358 |
359 | def test_module_tags(secretsmanager_client, keep_after, test_role_arn):
360 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
361 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
362 | fp.write(
363 | dedent(
364 | f"""
365 | region = "{REGION}"
366 | role_arn = "{test_role_arn}"
367 | tags = {{
368 | tag1: "value1"
369 | }}
370 | """
371 | )
372 | )
373 |
374 | with terraform_apply(
375 | terraform_module_dir,
376 | destroy_after=not keep_after,
377 | json_output=True,
378 | enable_trace=TRACE_TERRAFORM,
379 | ) as tf_output:
380 | LOG.info("%s", json.dumps(tf_output, indent=4))
381 | response = secretsmanager_client.describe_secret(
382 | SecretId=tf_output["secret_arn"]["value"]
383 | )
384 | assert response["Tags"] == [
385 | {
386 | "Key": "owner",
387 | "Value": test_role_arn,
388 | },
389 | {
390 | "Key": "tag1",
391 | "Value": "value1",
392 | },
393 | {
394 | "Key": "environment",
395 | "Value": "development",
396 | },
397 | {
398 | "Key": "created_by_module",
399 | "Value": "infrahouse/secret/aws",
400 | },
401 | {
402 | "Key": "service",
403 | "Value": "unknown",
404 | },
405 | {
406 | "Key": "created_by",
407 | "Value": "infrahouse/terraform-aws-secret",
408 | },
409 | {
410 | "Key": "account",
411 | "Value": "303467602807",
412 | },
413 | ]
414 |
415 |
416 | def test_module_duplicate_role(
417 | probe_role, secretsmanager_client, keep_after, test_role_arn
418 | ):
419 | terraform_module_dir = osp.join(TERRAFORM_ROOT_DIR, "secret")
420 | probe_role_arn = probe_role["role_arn"]["value"]
421 | with open(osp.join(terraform_module_dir, "terraform.tfvars"), "w") as fp:
422 | fp.write(
423 | dedent(
424 | f"""
425 | region = "{REGION}"
426 | role_arn = "{test_role_arn}"
427 |
428 | admins = [
429 | "arn:aws:iam::303467602807:role/aws-reserved/sso.amazonaws.com/us-west-1/AWSReservedSSO_AWSAdministratorAccess_422821c726d81c14",
430 | "{test_role_arn}"
431 | ]
432 | writers = [
433 | "{probe_role_arn}"
434 | ]
435 | readers = [
436 | "{probe_role_arn}"
437 | ]
438 | """
439 | )
440 | )
441 |
442 | with terraform_apply(
443 | terraform_module_dir,
444 | destroy_after=not keep_after,
445 | json_output=True,
446 | enable_trace=TRACE_TERRAFORM,
447 | ) as tf_output:
448 | LOG.info("%s", json.dumps(tf_output, indent=4))
449 | sm_client = get_secretsmanager_client_by_role(probe_role["role_arn"]["value"])
450 |
451 | # Can read
452 | assert (
453 | sm_client.get_secret_value(
454 | SecretId="foo",
455 | )["SecretString"]
456 | == "bar"
457 | )
458 |
459 | # Can write
460 | sm_client.put_secret_value(
461 | SecretId="foo",
462 | SecretString="barbar",
463 | )
464 | assert (
465 | sm_client.get_secret_value(
466 | SecretId="foo",
467 | )["SecretString"]
468 | == "barbar"
469 | )
470 |
471 | # Can't delete
472 | with pytest.raises(ClientError) as err:
473 | sm_client.delete_secret(SecretId="foo", ForceDeleteWithoutRecovery=True)
474 | assert err.type is ClientError
475 | assert err.value.response["Error"]["Code"] == "AccessDeniedException"
476 |
--------------------------------------------------------------------------------
/variables.tf:
--------------------------------------------------------------------------------
1 | variable "admins" {
2 | description = "List of role ARNs that will have all permissions of the secret."
3 | default = null
4 | type = list(string)
5 | }
6 |
7 | variable "owner" {
8 | description = "A tag owner with this value will be placed on a secret."
9 | default = null
10 | type = string
11 | }
12 |
13 | variable "readers" {
14 | description = "List of role ARNs that will have read permissions of the secret."
15 | default = null
16 | type = list(string)
17 | }
18 |
19 | variable "writers" {
20 | description = "List of role ARNs that will have write permissions of the secret."
21 | default = null
22 | type = list(string)
23 | }
24 |
25 | variable "secret_name" {
26 | description = "Name of the secret in AWS Secretsmanager. Either secret_name or secret_name_prefix must be set."
27 | type = string
28 | default = null
29 | }
30 |
31 | variable "secret_name_prefix" {
32 | description = "Name prefix of the secret in AWS Secretsmanager. Either secret_name or secret_name_prefix must be set."
33 | type = string
34 | default = null
35 | }
36 |
37 | variable "secret_description" {
38 | description = "The secret description in AWS Secretsmanager."
39 | type = string
40 | }
41 |
42 | variable "secret_value" {
43 | description = "Optional value of the secret."
44 | type = string
45 | default = null
46 | }
47 |
48 | variable "environment" {
49 | description = "Name of environment."
50 | type = string
51 | }
52 |
53 | variable "service_name" {
54 | description = "Descriptive name of a service that will use this secret."
55 | type = string
56 | default = "unknown"
57 | }
58 |
59 | variable "tags" {
60 | description = "Tags to apply to secret and other resources the module creates."
61 | type = map(string)
62 | default = {}
63 | }
64 |
--------------------------------------------------------------------------------