feat: initial release of terraform-aws-rds-proxy 🎉

This commit is contained in:
Bryant Biggs 2021-01-04 20:10:46 -05:00
commit 402de162e6
27 changed files with 2843 additions and 0 deletions

30
.editorconfig Normal file
View file

@ -0,0 +1,30 @@
# EditorConfig is awesome: http://EditorConfig.org
# Uses editorconfig to maintain consistent coding styles
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
[*.{tf,tfvars}]
indent_size = 2
indent_style = space
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[Makefile]
tab_width = 2
indent_style = tab
[COMMIT_EDITMSG]
max_line_length = 0

76
.github/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@clowd.haus. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

37
.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# terraform lockfile
.terraform.lock.hcl
# Crash log files
crash.log
# Exclude all .tfvars files, which are likely to contain sentitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
#
*.tfvars
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
#
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc

10
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,10 @@
repos:
- repo: git://github.com/antonbabenko/pre-commit-terraform
rev: v1.45.0
hooks:
- id: terraform_fmt
- id: terraform_docs
- repo: git://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: check-merge-conflict

10
.releaserc.json Normal file
View file

@ -0,0 +1,10 @@
{
"branches": [
"master"
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}

201
LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 Clowd Haus, LLC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

141
README.md Normal file
View file

@ -0,0 +1,141 @@
# AWS RDS Proxy Terraform module
Terraform module which creates an AWS RDS Proxy and its supporting resources.
The following resources are supported:
- [AWS RDS Proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy)
- [AWS RDS Proxy Default Target Group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_default_target_group)
- [AWS RDS Proxy Target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_target)
## Usage
See [`examples`](./examples) directory for working examples to reference:
```hcl
module "rds_proxy" {
source = "clowdhaus/rds-proxy/aws"
name = "rds-proxy"
iam_role_name = "rds-proxy-role"
vpc_subnet_ids = ["subnet-30ef7b3c", "subnet-1ecda77b", "subnet-ca09ddbc"]
vpc_security_group_ids = ["sg-f1d03a88"]
secrets = {
"superuser" = {
description = "Aurora PostgreSQL superuser password
arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:superuser-6gsjLD"
kms_key_id = "6ca29066-552a-46c5-a7d7-7bf9a15fc255"
}
}
engine_family = "POSTGRESQL"
db_host = "myendpoint.cluster-custom-123456789012.us-east-1.rds.amazonaws.com"
db_name = "example"
# Target Aurora cluster
target_db_cluster = true
db_cluster_identifier = "myendpoint"
tags = {
Terraform = "true"
Environment = "dev"
}
}
```
## Examples
Examples codified under the [`examples`](./examples) are intended to give users references for how to use the module(s) as well as testing/validating changes to the source code of the module(s). If contributing to the project, please be sure to make any appropriate updates to the relevant examples to allow maintainers to test your changes and to keep the examples up to date for users. Thank you!
- [IAM auth. w/ MySQL Aurora cluster](./examples/mysql_iam_cluster)
- [IAM auth. w/ MySQL RDS instance](./examples/mysql_iam_instance)
- [IAM auth. w/ PostgreSQL Aurora cluster](./examples/postgresql_iam_cluster)
- [IAM auth. w/ PostgreSQL RDS instance](./examples/postgresql_iam_instance)
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.12.26 |
| aws | >= 3.9 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 3.9 |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| auth | Configuration block(s) with authorization mechanisms to connect to the associated instances or clusters | `map(string)` | `{}` | no |
| auth\_scheme | The type of authentication that the proxy uses for connections from the proxy to the underlying database. One of `SECRETS` | `string` | `"SECRETS"` | no |
| connection\_borrow\_timeout | The number of seconds for a proxy to wait for a connection to become available in the connection pool | `number` | `null` | no |
| create\_iam\_policy | Determines whether an IAM policy is created | `bool` | `true` | no |
| create\_iam\_role | Determines whether an IAM role is created | `bool` | `true` | no |
| create\_proxy | Determines whether a proxy and its resources will be created | `bool` | `true` | no |
| db\_cluster\_identifier | DB cluster identifier | `string` | `""` | no |
| db\_host | The identifier to use for the database endpoint | `string` | `""` | no |
| db\_instance\_identifier | DB instance identifier | `string` | `""` | no |
| db\_name | The name of the database | `string` | `""` | no |
| debug\_logging | Whether the proxy includes detailed information about SQL statements in its logs | `bool` | `false` | no |
| engine\_family | The kind of database engine that the proxy will connect to. Valid values are `MYSQL` or `POSTGRESQL` | `string` | `""` | no |
| iam\_auth | Whether to require or disallow AWS Identity and Access Management (IAM) authentication for connections to the proxy. One of `DISABLED`, `REQUIRED` | `string` | `"REQUIRED"` | no |
| iam\_creation\_wait\_duration | Time duration delay to wait for IAM resource creation/propagation. For example, 30s for 30 seconds or 5m for 5 minutes. Updating this value by itself will not trigger a delay. | `string` | `"30s"` | no |
| iam\_policy\_name | The name of the role policy. If omitted, Terraform will assign a random, unique name | `string` | `""` | no |
| iam\_role\_description | The description of the role | `string` | `""` | no |
| iam\_role\_force\_detach\_policies | Specifies to force detaching any policies the role has before destroying it | `bool` | `true` | no |
| iam\_role\_max\_session\_duration | The maximum session duration (in seconds) that you want to set for the specified role | `number` | `43200` | no |
| iam\_role\_name | The name of the role. If omitted, Terraform will assign a random, unique name | `string` | `""` | no |
| iam\_role\_path | The path to the role | `string` | `null` | no |
| iam\_role\_permissions\_boundary | The ARN of the policy that is used to set the permissions boundary for the role | `string` | `null` | no |
| iam\_role\_tags | A map of tags to apply to the IAM role | `map(string)` | `{}` | no |
| idle\_client\_timeout | The number of seconds that a connection to the proxy can be inactive before the proxy disconnects it | `number` | `1800` | no |
| init\_query | One or more SQL statements for the proxy to run when opening each new database connection | `string` | `""` | no |
| log\_group\_kms\_key\_id | The ARN of the KMS Key to use when encrypting log data | `string` | `null` | no |
| log\_group\_retention\_in\_days | Specifies the number of days you want to retain log events in the log group | `number` | `30` | no |
| log\_group\_tags | A map of tags to apply to the CloudWatch log group | `map(string)` | `{}` | no |
| manage\_log\_group | Determines whether Terraform will create/manage the CloudWatch log group or not. Note - this will fail if set to true after the log group has been created as the resource will already exist | `bool` | `true` | no |
| max\_connections\_percent | The maximum size of the connection pool for each target in a target group | `number` | `90` | no |
| max\_idle\_connections\_percent | Controls how actively the proxy closes idle database connections in the connection pool | `number` | `50` | no |
| name | The identifier for the proxy. This name must be unique for all proxies owned by your AWS account in the specified AWS Region. An identifier must begin with a letter and must contain only ASCII letters, digits, and hyphens; it can't end with a hyphen or contain two consecutive hyphens | `string` | `""` | no |
| proxy\_tags | A map of tags to apply to the RDS Proxy | `map(string)` | `{}` | no |
| require\_tls | A Boolean parameter that specifies whether Transport Layer Security (TLS) encryption is required for connections to the proxy | `bool` | `true` | no |
| role\_arn | The Amazon Resource Name (ARN) of the IAM role that the proxy uses to access secrets in AWS Secrets Manager | `string` | `""` | no |
| secrets | Map of secerets to be used by RDS Proxy for authentication to the database | `map(object({ arn = string, description = string, kms_key_id = string }))` | `{}` | no |
| session\_pinning\_filters | Each item in the list represents a class of SQL operations that normally cause all later statements in a session using a proxy to be pinned to the same underlying database connection | `list(string)` | `[]` | no |
| tags | A map of tags to use on all resources | `map(string)` | `{}` | no |
| target\_db\_cluster | Determines whether DB cluster is targetted by proxy | `bool` | `false` | no |
| target\_db\_instance | Determines whether DB instance is targetted by proxy | `bool` | `false` | no |
| use\_policy\_name\_prefix | Whether to use unique name beginning with the specified `iam_policy_name` | `bool` | `false` | no |
| use\_role\_name\_prefix | Whether to use unique name beginning with the specified `iam_role_name` | `bool` | `false` | no |
| vpc\_security\_group\_ids | One or more VPC security group IDs to associate with the new proxy | `list(string)` | `[]` | no |
| vpc\_subnet\_ids | One or more VPC subnet IDs to associate with the new proxy | `list(string)` | `[]` | no |
## Outputs
| Name | Description |
|------|-------------|
| log\_group\_arn | The Amazon Resource Name (ARN) of the CloudWatch log group |
| proxy\_arn | The Amazon Resource Name (ARN) for the proxy |
| proxy\_default\_target\_group\_arn | The Amazon Resource Name (ARN) for the default target group |
| proxy\_default\_target\_group\_id | The ID for the default target group |
| proxy\_default\_target\_group\_name | The name of the default target group |
| proxy\_endpoint | The endpoint that you can use to connect to the proxy |
| proxy\_id | The ID for the proxy |
| proxy\_target\_endpoint | Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type |
| proxy\_target\_id | Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/) |
| proxy\_target\_port | Port for the target RDS DB Instance or Aurora DB Cluster |
| proxy\_target\_rds\_resource\_id | Identifier representing the DB Instance or DB Cluster target |
| proxy\_target\_target\_arn | Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API |
| proxy\_target\_tracked\_cluster\_id | DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS\_INSTANCE target that is part of a DB Cluster |
| proxy\_target\_type | Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER` |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## License
Apache-2.0 Licensed. See [LICENSE](LICENSE).

View file

@ -0,0 +1,71 @@
# RDS Proxy - IAM Authentication & MySQL Cluster
Configuration in this directory creates:
- AWS RDS Proxy w/ IAM authentication enabled for an RDS Aurora MySQL cluster
## Usage
To run this example you need to execute:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
## Validation
An EC2 instance configuration has been provided for use in validating the example configuration. After provisioning the configuration, there are some outputs that have been provided to aid in validating changes. To perform validation, after the EC2 instance finishes provisioning:
1. Connect to the EC2 instance using Session Manager
2. Copy the output from `superuser_proxy_iam_token` and paste it into the Session Manager window - this generates the token for connecting to the proxy with IAM auth.
3. Copy the output from `superuser_proxy_iam_connect` and paste it into the window
4. You should now be connected to the `example` database in the RDS instance via the AWS Proxy using IAM authentication
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.12.26 |
| aws | >= 3.9 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 3.9 |
| random | n/a |
## Inputs
No input.
## Outputs
| Name | Description |
|------|-------------|
| log\_group\_arn | The Amazon Resource Name (ARN) of the CloudWatch log group |
| proxy\_arn | The Amazon Resource Name (ARN) for the proxy |
| proxy\_default\_target\_group\_arn | The Amazon Resource Name (ARN) for the default target group |
| proxy\_default\_target\_group\_id | The ID for the default target group |
| proxy\_default\_target\_group\_name | The name of the default target group |
| proxy\_endpoint | The endpoint that you can use to connect to the proxy |
| proxy\_id | The ID for the proxy |
| proxy\_target\_endpoint | Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type |
| proxy\_target\_id | Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/) |
| proxy\_target\_port | Port for the target RDS DB Instance or Aurora DB Cluster |
| proxy\_target\_rds\_resource\_id | Identifier representing the DB Instance or DB Cluster target |
| proxy\_target\_target\_arn | Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API |
| proxy\_target\_tracked\_cluster\_id | DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS\_INSTANCE target that is part of a DB Cluster |
| proxy\_target\_type | Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER` |
| superuser\_db\_password\_connect | Connect to database using superuser with username/password directly to database |
| superuser\_proxy\_iam\_connect | Connect to RDS Proxy using IAM auth via token generated |
| superuser\_proxy\_iam\_token | Gerate connection token for connecting to RDS Proxy with IAM auth |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Apache-2.0 Licensed. See [LICENSE](../../LICENSE).

View file

@ -0,0 +1,284 @@
provider "aws" {
region = local.region
}
locals {
region = "us-east-1"
name = "example-${replace(basename(path.cwd), "_", "-")}"
db_name = "example"
db_username = random_pet.users.id # using random here due to secrets taking at least 7 days before fully deleting from account
db_password = random_password.password.result
db_proxy_resource_id = element(split(":", module.rds_proxy.proxy_arn), 6)
db_iam_connect_prefix = "arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${local.db_proxy_resource_id}"
tags = {
Example = local.name
Environment = "dev"
}
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
################################################################################
# Supporting Resources
################################################################################
resource "random_pet" "users" {
length = 2
separator = "_"
}
resource "random_password" "password" {
length = 16
special = false
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.64.0"
name = local.name
cidr = "10.0.0.0/18"
azs = ["${local.region}a", "${local.region}b", "${local.region}c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"]
database_subnets = ["10.0.7.0/24", "10.0.8.0/24", "10.0.9.0/24"]
create_database_subnet_group = true
enable_nat_gateway = true
single_nat_gateway = true
tags = local.tags
}
module "rds" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "3.0.0"
name = local.name
database_name = local.db_name
username = local.db_username
password = local.db_password
# When using RDS Proxy w/ IAM auth - Database must be username/password auth, not IAM
iam_database_authentication_enabled = false
engine = "aurora-mysql"
engine_version = "5.7"
replica_count = 1
instance_type = "db.t3.medium"
storage_encrypted = false
apply_immediately = true
skip_final_snapshot = true
vpc_id = module.vpc.vpc_id
subnets = module.vpc.database_subnets
allowed_security_groups = [module.rds_proxy_sg.this_security_group_id]
db_subnet_group_name = local.name # Created by VPC module
db_parameter_group_name = aws_db_parameter_group.aurora_db_mysql57_parameter_group.id
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.aurora_cluster_mysql57_parameter_group.id
tags = local.tags
}
resource "aws_db_parameter_group" "aurora_db_mysql57_parameter_group" {
name = "example-aurora-db-57-parameter-group"
family = "aurora-mysql5.7"
description = "example-aurora-db-57-parameter-group"
}
resource "aws_rds_cluster_parameter_group" "aurora_cluster_mysql57_parameter_group" {
name = "example-aurora-57-cluster-parameter-group"
family = "aurora-mysql5.7"
description = "example-aurora-57-cluster-parameter-group"
}
################################################################################
# Test Resources
################################################################################
resource "aws_iam_instance_profile" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.name
}
data "aws_iam_policy_document" "ec2_test_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ec2_test" {
name_prefix = local.name
force_detach_policies = true
assume_role_policy = data.aws_iam_policy_document.ec2_test_assume.json
}
data "aws_iam_policy_document" "ec2_test" {
statement {
actions = ["rds-db:connect"]
resources = ["${local.db_iam_connect_prefix}/${local.db_username}"]
}
}
resource "aws_iam_role_policy" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.id
policy = data.aws_iam_policy_document.ec2_test.json
}
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2_test.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
module "ec2_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "ec2"
description = "EC2 RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
egress_rules = ["all-all"]
tags = local.tags
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.16.0"
name = local.name
instance_count = 1
associate_public_ip_address = true
iam_instance_profile = aws_iam_instance_profile.ec2_test.name
user_data = <<-EOT
#!/usr/bin/env bash
mkdir -p /home/ssm-user/ && wget -O /home/ssm-user/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
apt update
apt install awscli mysql-server -y
EOT
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [module.ec2_sg.this_security_group_id]
subnet_ids = module.vpc.private_subnets
tags = local.tags
}
################################################################################
# Secrets - DB user passwords
################################################################################
data "aws_kms_alias" "secretsmanager" {
name = "alias/aws/secretsmanager"
}
resource "aws_secretsmanager_secret" "superuser" {
name = local.db_username
description = "Database superuser, ${local.db_username}, databse connection values"
kms_key_id = data.aws_kms_alias.secretsmanager.id
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "superuser" {
secret_id = aws_secretsmanager_secret.superuser.id
secret_string = jsonencode({
username = local.db_username
password = local.db_password
})
}
################################################################################
# RDS Proxy
################################################################################
module "rds_proxy_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds_proxy"
description = "PostgreSQL RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet MySQL access"
rule = "mysql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
egress_with_cidr_blocks = [
{
description = "Database subnet MySQL access"
rule = "mysql-tcp"
cidr_blocks = join(",", module.vpc.database_subnets_cidr_blocks)
},
]
tags = local.tags
}
module "rds_proxy" {
source = "../../"
create_proxy = true
name = local.name
iam_role_name = local.name
vpc_subnet_ids = module.vpc.private_subnets
vpc_security_group_ids = [module.rds_proxy_sg.this_security_group_id]
secrets = {
"${local.db_username}" = {
description = aws_secretsmanager_secret.superuser.description
arn = aws_secretsmanager_secret.superuser.arn
kms_key_id = aws_secretsmanager_secret.superuser.kms_key_id
}
}
engine_family = "MYSQL"
db_host = module.rds.this_rds_cluster_endpoint
db_name = module.rds.this_rds_cluster_database_name
debug_logging = true
# Target Aurora cluster
target_db_cluster = true
db_cluster_identifier = module.rds.this_rds_cluster_id
tags = local.tags
}

View file

@ -0,0 +1,89 @@
# RDS Proxy
output "proxy_id" {
description = "The ID for the proxy"
value = module.rds_proxy.proxy_id
}
output "proxy_arn" {
description = "The Amazon Resource Name (ARN) for the proxy"
value = module.rds_proxy.proxy_arn
}
output "proxy_endpoint" {
description = "The endpoint that you can use to connect to the proxy"
value = module.rds_proxy.proxy_endpoint
}
# Proxy Default Target Group
output "proxy_default_target_group_id" {
description = "The ID for the default target group"
value = module.rds_proxy.proxy_default_target_group_id
}
output "proxy_default_target_group_arn" {
description = "The Amazon Resource Name (ARN) for the default target group"
value = module.rds_proxy.proxy_default_target_group_arn
}
output "proxy_default_target_group_name" {
description = "The name of the default target group"
value = module.rds_proxy.proxy_default_target_group_name
}
# Proxy Target
output "proxy_target_endpoint" {
description = "Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type"
value = module.rds_proxy.proxy_target_endpoint
}
output "proxy_target_id" {
description = "Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/)"
value = module.rds_proxy.proxy_target_id
}
output "proxy_target_port" {
description = "Port for the target RDS DB Instance or Aurora DB Cluster"
value = module.rds_proxy.proxy_target_port
}
output "proxy_target_rds_resource_id" {
description = "Identifier representing the DB Instance or DB Cluster target"
value = module.rds_proxy.proxy_target_rds_resource_id
}
output "proxy_target_target_arn" {
description = "Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API"
value = module.rds_proxy.proxy_target_target_arn
}
output "proxy_target_tracked_cluster_id" {
description = "DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS_INSTANCE target that is part of a DB Cluster"
value = module.rds_proxy.proxy_target_tracked_cluster_id
}
output "proxy_target_type" {
description = "Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`"
value = module.rds_proxy.proxy_target_type
}
# CloudWatch logs
output "log_group_arn" {
description = "The Amazon Resource Name (ARN) of the CloudWatch log group"
value = module.rds_proxy.log_group_arn
}
# For aiding in testing & verification
output "superuser_db_password_connect" {
description = "Connect to database using superuser with username/password directly to database"
value = "mysql --host=${module.rds.this_rds_cluster_endpoint} --user=${local.db_username} --password=${local.db_password} ${module.rds.this_rds_cluster_database_name}"
}
output "superuser_proxy_iam_token" {
description = "Gerate connection token for connecting to RDS Proxy with IAM auth"
value = "TOKEN=$(aws rds generate-db-auth-token --hostname ${module.rds_proxy.proxy_endpoint} --port 3306 --region ${local.region} --username ${local.db_username})"
}
output "superuser_proxy_iam_connect" {
description = "Connect to RDS Proxy using IAM auth via token generated"
value = "mysql --host=${module.rds_proxy.proxy_endpoint} --user=${local.db_username} --password=$TOKEN ${module.rds.this_rds_cluster_database_name} --ssl-ca=/home/ssm-user/AmazonRootCA1.pem --enable-cleartext-plugin"
}

View file

@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.26"
required_providers {
aws = ">= 3.9"
}
}

View file

@ -0,0 +1,71 @@
# RDS Proxy - IAM Authentication & MySQL Instance
Configuration in this directory creates:
- AWS RDS Proxy w/ IAM authentication enabled for an RDS MySQL instance
## Usage
To run this example you need to execute:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
## Validation
An EC2 instance configuration has been provided for use in validating the example configuration. After provisioning the configuration, there are some outputs that have been provided to aid in validating changes. To perform validation, after the EC2 instance finishes provisioning:
1. Connect to the EC2 instance using Session Manager
2. Copy the output from `superuser_proxy_iam_token` and paste it into the Session Manager window - this generates the token for connecting to the proxy with IAM auth.
3. Copy the output from `superuser_proxy_iam_connect` and paste it into the window
4. You should now be connected to the `example` database in the Aurora cluster via the AWS Proxy using IAM authentication
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.12.26 |
| aws | >= 3.9 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 3.9 |
| random | n/a |
## Inputs
No input.
## Outputs
| Name | Description |
|------|-------------|
| log\_group\_arn | The Amazon Resource Name (ARN) of the CloudWatch log group |
| proxy\_arn | The Amazon Resource Name (ARN) for the proxy |
| proxy\_default\_target\_group\_arn | The Amazon Resource Name (ARN) for the default target group |
| proxy\_default\_target\_group\_id | The ID for the default target group |
| proxy\_default\_target\_group\_name | The name of the default target group |
| proxy\_endpoint | The endpoint that you can use to connect to the proxy |
| proxy\_id | The ID for the proxy |
| proxy\_target\_endpoint | Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type |
| proxy\_target\_id | Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/) |
| proxy\_target\_port | Port for the target RDS DB Instance or Aurora DB Cluster |
| proxy\_target\_rds\_resource\_id | Identifier representing the DB Instance or DB Cluster target |
| proxy\_target\_target\_arn | Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API |
| proxy\_target\_tracked\_cluster\_id | DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS\_INSTANCE target that is part of a DB Cluster |
| proxy\_target\_type | Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER` |
| superuser\_db\_password\_connect | Connect to database using superuser with username/password directly to database |
| superuser\_proxy\_iam\_connect | Connect to RDS Proxy using IAM auth via token generated |
| superuser\_proxy\_iam\_token | Gerate connection token for connecting to RDS Proxy with IAM auth |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Apache-2.0 Licensed. See [LICENSE](../../LICENSE).

View file

@ -0,0 +1,294 @@
provider "aws" {
region = local.region
}
locals {
region = "us-east-1"
name = "example-${replace(basename(path.cwd), "_", "-")}"
db_name = "example"
db_username = random_pet.users.id # using random here due to secrets taking at least 7 days before fully deleting from account
db_password = random_password.password.result
db_proxy_resource_id = element(split(":", module.rds_proxy.proxy_arn), 6)
db_iam_connect_prefix = "arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${local.db_proxy_resource_id}"
tags = {
Example = local.name
Environment = "dev"
}
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
################################################################################
# Supporting Resources
################################################################################
resource "random_pet" "users" {
length = 2
separator = "_"
}
resource "random_password" "password" {
length = 16
special = false
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.64.0"
name = local.name
cidr = "10.0.0.0/18"
azs = ["${local.region}a", "${local.region}b", "${local.region}c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"]
database_subnets = ["10.0.7.0/24", "10.0.8.0/24", "10.0.9.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
tags = local.tags
}
module "rds_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds"
description = "MySQL RDS example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet MySQL access"
rule = "mysql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
tags = local.tags
}
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "2.20.0"
name = local.db_name
username = local.db_username
password = local.db_password
# When using RDS Proxy w/ IAM auth - Database must be username/password auth, not IAM
iam_database_authentication_enabled = false
identifier = local.name
engine = "mysql"
engine_version = "5.7.31"
family = "mysql5.7"
major_engine_version = "5.7"
port = 3306
instance_class = "db.t3.micro"
allocated_storage = 5
storage_encrypted = false
apply_immediately = true
vpc_security_group_ids = [module.rds_sg.this_security_group_id]
subnet_ids = module.vpc.database_subnets
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
backup_retention_period = 0
deletion_protection = false
tags = local.tags
}
################################################################################
# Test Resources
################################################################################
resource "aws_iam_instance_profile" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.name
}
data "aws_iam_policy_document" "ec2_test_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ec2_test" {
name_prefix = local.name
force_detach_policies = true
assume_role_policy = data.aws_iam_policy_document.ec2_test_assume.json
}
data "aws_iam_policy_document" "ec2_test" {
statement {
actions = ["rds-db:connect"]
resources = ["${local.db_iam_connect_prefix}/${local.db_username}"]
}
}
resource "aws_iam_role_policy" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.id
policy = data.aws_iam_policy_document.ec2_test.json
}
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2_test.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
module "ec2_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "ec2"
description = "EC2 RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
egress_rules = ["all-all"]
tags = local.tags
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.16.0"
name = local.name
instance_count = 1
associate_public_ip_address = true
iam_instance_profile = aws_iam_instance_profile.ec2_test.name
user_data = <<-EOT
#!/usr/bin/env bash
mkdir -p /home/ssm-user/ && wget -O /home/ssm-user/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
apt update
apt install awscli mysql-server -y
EOT
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [module.ec2_sg.this_security_group_id]
subnet_ids = module.vpc.private_subnets
tags = local.tags
}
################################################################################
# Secrets - DB user passwords
################################################################################
data "aws_kms_alias" "secretsmanager" {
name = "alias/aws/secretsmanager"
}
resource "aws_secretsmanager_secret" "superuser" {
name = local.db_username
description = "Database superuser, ${local.db_username}, databse connection values"
kms_key_id = data.aws_kms_alias.secretsmanager.id
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "superuser" {
secret_id = aws_secretsmanager_secret.superuser.id
secret_string = jsonencode({
username = local.db_username
password = local.db_password
})
}
################################################################################
# RDS Proxy
################################################################################
module "rds_proxy_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds_proxy"
description = "MySQL RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet PostgreSQL access"
rule = "mysql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
egress_with_cidr_blocks = [
{
description = "Database subnet MySQL access"
rule = "mysql-tcp"
cidr_blocks = join(",", module.vpc.database_subnets_cidr_blocks)
},
]
tags = local.tags
}
module "rds_proxy" {
source = "../../"
create_proxy = true
name = local.name
iam_role_name = local.name
vpc_subnet_ids = module.vpc.private_subnets
vpc_security_group_ids = [module.rds_proxy_sg.this_security_group_id]
secrets = {
"${local.db_username}" = {
description = aws_secretsmanager_secret.superuser.description
arn = aws_secretsmanager_secret.superuser.arn
kms_key_id = aws_secretsmanager_secret.superuser.kms_key_id
}
}
engine_family = "MYSQL"
db_host = module.rds.this_db_instance_address
db_name = module.rds.this_db_instance_name
debug_logging = true
# Target RDS instance
target_db_instance = true
db_instance_identifier = module.rds.this_db_instance_id
tags = local.tags
}

View file

@ -0,0 +1,89 @@
# RDS Proxy
output "proxy_id" {
description = "The ID for the proxy"
value = module.rds_proxy.proxy_id
}
output "proxy_arn" {
description = "The Amazon Resource Name (ARN) for the proxy"
value = module.rds_proxy.proxy_arn
}
output "proxy_endpoint" {
description = "The endpoint that you can use to connect to the proxy"
value = module.rds_proxy.proxy_endpoint
}
# Proxy Default Target Group
output "proxy_default_target_group_id" {
description = "The ID for the default target group"
value = module.rds_proxy.proxy_default_target_group_id
}
output "proxy_default_target_group_arn" {
description = "The Amazon Resource Name (ARN) for the default target group"
value = module.rds_proxy.proxy_default_target_group_arn
}
output "proxy_default_target_group_name" {
description = "The name of the default target group"
value = module.rds_proxy.proxy_default_target_group_name
}
# Proxy Target
output "proxy_target_endpoint" {
description = "Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type"
value = module.rds_proxy.proxy_target_endpoint
}
output "proxy_target_id" {
description = "Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/)"
value = module.rds_proxy.proxy_target_id
}
output "proxy_target_port" {
description = "Port for the target RDS DB Instance or Aurora DB Cluster"
value = module.rds_proxy.proxy_target_port
}
output "proxy_target_rds_resource_id" {
description = "Identifier representing the DB Instance or DB Cluster target"
value = module.rds_proxy.proxy_target_rds_resource_id
}
output "proxy_target_target_arn" {
description = "Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API"
value = module.rds_proxy.proxy_target_target_arn
}
output "proxy_target_tracked_cluster_id" {
description = "DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS_INSTANCE target that is part of a DB Cluster"
value = module.rds_proxy.proxy_target_tracked_cluster_id
}
output "proxy_target_type" {
description = "Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`"
value = module.rds_proxy.proxy_target_type
}
# CloudWatch logs
output "log_group_arn" {
description = "The Amazon Resource Name (ARN) of the CloudWatch log group"
value = module.rds_proxy.log_group_arn
}
# For aiding in testing & verification
output "superuser_db_password_connect" {
description = "Connect to database using superuser with username/password directly to database"
value = "mysql --host=${module.rds.this_db_instance_address} --user=${local.db_username} --password=${local.db_password} ${module.rds.this_db_instance_name}"
}
output "superuser_proxy_iam_token" {
description = "Gerate connection token for connecting to RDS Proxy with IAM auth"
value = "TOKEN=$(aws rds generate-db-auth-token --hostname ${module.rds_proxy.proxy_endpoint} --port 3306 --region ${local.region} --username ${local.db_username})"
}
output "superuser_proxy_iam_connect" {
description = "Connect to RDS Proxy using IAM auth via token generated"
value = "mysql --host=${module.rds_proxy.proxy_endpoint} --user=${local.db_username} --password=$TOKEN ${module.rds.this_db_instance_name} --ssl-ca=/home/ssm-user/AmazonRootCA1.pem --enable-cleartext-plugin"
}

View file

@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.26"
required_providers {
aws = ">= 3.9"
}
}

View file

@ -0,0 +1,71 @@
# RDS Proxy - IAM Authentication & PostgreSQL Cluster
Configuration in this directory creates:
- AWS RDS Proxy w/ IAM authentication enabled for an RDS Aurora PostgreSQL cluster
## Usage
To run this example you need to execute:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
## Validation
An EC2 instance configuration has been provided for use in validating the example configuration. After provisioning the configuration, there are some outputs that have been provided to aid in validating changes. To perform validation, after the EC2 instance finishes provisioning:
1. Connect to the EC2 instance using Session Manager
2. Copy the output from `superuser_proxy_iam_token` and paste it into the Session Manager window - this generates the token for connecting to the proxy with IAM auth.
3. Copy the output from `superuser_proxy_iam_connect` and paste it into the window - NOTE: remove the string escape slashes `psql \"host...` -> `psql "host...`
4. You should now be connected to the `example` database in the RDS instance via the AWS Proxy using IAM authentication
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.12.26 |
| aws | >= 3.9 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 3.9 |
| random | n/a |
## Inputs
No input.
## Outputs
| Name | Description |
|------|-------------|
| log\_group\_arn | The Amazon Resource Name (ARN) of the CloudWatch log group |
| proxy\_arn | The Amazon Resource Name (ARN) for the proxy |
| proxy\_default\_target\_group\_arn | The Amazon Resource Name (ARN) for the default target group |
| proxy\_default\_target\_group\_id | The ID for the default target group |
| proxy\_default\_target\_group\_name | The name of the default target group |
| proxy\_endpoint | The endpoint that you can use to connect to the proxy |
| proxy\_id | The ID for the proxy |
| proxy\_target\_endpoint | Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type |
| proxy\_target\_id | Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/) |
| proxy\_target\_port | Port for the target RDS DB Instance or Aurora DB Cluster |
| proxy\_target\_rds\_resource\_id | Identifier representing the DB Instance or DB Cluster target |
| proxy\_target\_target\_arn | Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API |
| proxy\_target\_tracked\_cluster\_id | DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS\_INSTANCE target that is part of a DB Cluster |
| proxy\_target\_type | Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER` |
| superuser\_db\_password\_connect | Connect to database using superuser with username/password directly to database |
| superuser\_proxy\_iam\_connect | Connect to RDS Proxy using IAM auth via token generated |
| superuser\_proxy\_iam\_token | Gerate connection token for connecting to RDS Proxy with IAM auth |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Apache-2.0 Licensed. See [LICENSE](../../LICENSE).

View file

@ -0,0 +1,284 @@
provider "aws" {
region = local.region
}
locals {
region = "us-east-1"
name = "example-${replace(basename(path.cwd), "_", "-")}"
db_name = "example"
db_username = random_pet.users.id # using random here due to secrets taking at least 7 days before fully deleting from account
db_password = random_password.password.result
db_proxy_resource_id = element(split(":", module.rds_proxy.proxy_arn), 6)
db_iam_connect_prefix = "arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${local.db_proxy_resource_id}"
tags = {
Example = local.name
Environment = "dev"
}
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
################################################################################
# Supporting Resources
################################################################################
resource "random_pet" "users" {
length = 2
separator = "_"
}
resource "random_password" "password" {
length = 16
special = false
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.64.0"
name = local.name
cidr = "10.0.0.0/18"
azs = ["${local.region}a", "${local.region}b", "${local.region}c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"]
database_subnets = ["10.0.7.0/24", "10.0.8.0/24", "10.0.9.0/24"]
create_database_subnet_group = true
enable_nat_gateway = true
single_nat_gateway = true
tags = local.tags
}
module "rds" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "3.0.0"
name = local.name
database_name = local.db_name
username = local.db_username
password = local.db_password
# When using RDS Proxy w/ IAM auth - Database must be username/password auth, not IAM
iam_database_authentication_enabled = false
engine = "aurora-postgresql"
engine_version = "11.9"
replica_count = 1
instance_type = "db.t3.medium"
storage_encrypted = false
apply_immediately = true
skip_final_snapshot = true
vpc_id = module.vpc.vpc_id
subnets = module.vpc.database_subnets
allowed_security_groups = [module.rds_proxy_sg.this_security_group_id]
db_subnet_group_name = local.name # Created by VPC module
db_parameter_group_name = aws_db_parameter_group.aurora_db_postgres11_parameter_group.id
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.aurora_cluster_postgres11_parameter_group.id
tags = local.tags
}
resource "aws_db_parameter_group" "aurora_db_postgres11_parameter_group" {
name = "example-aurora-db-postgres11-parameter-group"
family = "aurora-postgresql11"
description = "test-aurora-db-postgres11-parameter-group"
}
resource "aws_rds_cluster_parameter_group" "aurora_cluster_postgres11_parameter_group" {
name = "example-aurora-postgres11-cluster-parameter-group"
family = "aurora-postgresql11"
description = "example-aurora-postgres11-cluster-parameter-group"
}
################################################################################
# Test Resources
################################################################################
resource "aws_iam_instance_profile" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.name
}
data "aws_iam_policy_document" "ec2_test_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ec2_test" {
name_prefix = local.name
force_detach_policies = true
assume_role_policy = data.aws_iam_policy_document.ec2_test_assume.json
}
data "aws_iam_policy_document" "ec2_test" {
statement {
actions = ["rds-db:connect"]
resources = ["${local.db_iam_connect_prefix}/${local.db_username}"]
}
}
resource "aws_iam_role_policy" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.id
policy = data.aws_iam_policy_document.ec2_test.json
}
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2_test.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
module "ec2_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "ec2"
description = "EC2 RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
egress_rules = ["all-all"]
tags = local.tags
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.16.0"
name = local.name
instance_count = 1
associate_public_ip_address = true
iam_instance_profile = aws_iam_instance_profile.ec2_test.name
user_data = <<-EOT
#!/usr/bin/env bash
mkdir -p /home/ssm-user/ && wget -O /home/ssm-user/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
apt update
apt install awscli postgresql-client -y
EOT
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [module.ec2_sg.this_security_group_id]
subnet_ids = module.vpc.private_subnets
tags = local.tags
}
################################################################################
# Secrets - DB user passwords
################################################################################
data "aws_kms_alias" "secretsmanager" {
name = "alias/aws/secretsmanager"
}
resource "aws_secretsmanager_secret" "superuser" {
name = local.db_username
description = "Database superuser, ${local.db_username}, databse connection values"
kms_key_id = data.aws_kms_alias.secretsmanager.id
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "superuser" {
secret_id = aws_secretsmanager_secret.superuser.id
secret_string = jsonencode({
username = local.db_username
password = local.db_password
})
}
################################################################################
# RDS Proxy
################################################################################
module "rds_proxy_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds_proxy"
description = "PostgreSQL RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet PostgreSQL access"
rule = "postgresql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
egress_with_cidr_blocks = [
{
description = "Database subnet PostgreSQL access"
rule = "postgresql-tcp"
cidr_blocks = join(",", module.vpc.database_subnets_cidr_blocks)
},
]
tags = local.tags
}
module "rds_proxy" {
source = "../../"
create_proxy = true
name = local.name
iam_role_name = local.name
vpc_subnet_ids = module.vpc.private_subnets
vpc_security_group_ids = [module.rds_proxy_sg.this_security_group_id]
secrets = {
"${local.db_username}" = {
description = aws_secretsmanager_secret.superuser.description
arn = aws_secretsmanager_secret.superuser.arn
kms_key_id = aws_secretsmanager_secret.superuser.kms_key_id
}
}
engine_family = "POSTGRESQL"
db_host = module.rds.this_rds_cluster_endpoint
db_name = module.rds.this_rds_cluster_database_name
debug_logging = true
# Target Aurora cluster
target_db_cluster = true
db_cluster_identifier = module.rds.this_rds_cluster_id
tags = local.tags
}

View file

@ -0,0 +1,89 @@
# RDS Proxy
output "proxy_id" {
description = "The ID for the proxy"
value = module.rds_proxy.proxy_id
}
output "proxy_arn" {
description = "The Amazon Resource Name (ARN) for the proxy"
value = module.rds_proxy.proxy_arn
}
output "proxy_endpoint" {
description = "The endpoint that you can use to connect to the proxy"
value = module.rds_proxy.proxy_endpoint
}
# Proxy Default Target Group
output "proxy_default_target_group_id" {
description = "The ID for the default target group"
value = module.rds_proxy.proxy_default_target_group_id
}
output "proxy_default_target_group_arn" {
description = "The Amazon Resource Name (ARN) for the default target group"
value = module.rds_proxy.proxy_default_target_group_arn
}
output "proxy_default_target_group_name" {
description = "The name of the default target group"
value = module.rds_proxy.proxy_default_target_group_name
}
# Proxy Target
output "proxy_target_endpoint" {
description = "Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type"
value = module.rds_proxy.proxy_target_endpoint
}
output "proxy_target_id" {
description = "Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/)"
value = module.rds_proxy.proxy_target_id
}
output "proxy_target_port" {
description = "Port for the target RDS DB Instance or Aurora DB Cluster"
value = module.rds_proxy.proxy_target_port
}
output "proxy_target_rds_resource_id" {
description = "Identifier representing the DB Instance or DB Cluster target"
value = module.rds_proxy.proxy_target_rds_resource_id
}
output "proxy_target_target_arn" {
description = "Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API"
value = module.rds_proxy.proxy_target_target_arn
}
output "proxy_target_tracked_cluster_id" {
description = "DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS_INSTANCE target that is part of a DB Cluster"
value = module.rds_proxy.proxy_target_tracked_cluster_id
}
output "proxy_target_type" {
description = "Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`"
value = module.rds_proxy.proxy_target_type
}
# CloudWatch logs
output "log_group_arn" {
description = "The Amazon Resource Name (ARN) of the CloudWatch log group"
value = module.rds_proxy.log_group_arn
}
# For aiding in testing & verification
output "superuser_db_password_connect" {
description = "Connect to database using superuser with username/password directly to database"
value = "PGPASSWORD=${local.db_password} psql -h ${module.rds.this_rds_cluster_endpoint} -p 5432 -d ${module.rds.this_rds_cluster_database_name} -U ${local.db_username} --set=sslmode=require"
}
output "superuser_proxy_iam_token" {
description = "Gerate connection token for connecting to RDS Proxy with IAM auth"
value = "TOKEN=$(aws rds generate-db-auth-token --hostname ${module.rds_proxy.proxy_endpoint} --port 5432 --region ${local.region} --username ${local.db_username})"
}
output "superuser_proxy_iam_connect" {
description = "Connect to RDS Proxy using IAM auth via token generated"
value = "psql \"host=${module.rds_proxy.proxy_endpoint} port=5432 sslmode=verify-full sslrootcert=/home/ssm-user/AmazonRootCA1.pem dbname=${module.rds.this_rds_cluster_database_name} user=${local.db_username} password=$TOKEN\""
}

View file

@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.26"
required_providers {
aws = ">= 3.9"
}
}

View file

@ -0,0 +1,71 @@
# RDS Proxy - IAM Authentication & PostgreSQL Instance
Configuration in this directory creates:
- AWS RDS Proxy w/ IAM authentication enabled for an RDS PostgreSQL instance
## Usage
To run this example you need to execute:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
## Validation
An EC2 instance configuration has been provided for use in validating the example configuration. After provisioning the configuration, there are some outputs that have been provided to aid in validating changes. To perform validation, after the EC2 instance finishes provisioning:
1. Connect to the EC2 instance using Session Manager
2. Copy the output from `superuser_proxy_iam_token` and paste it into the Session Manager window - this generates the token for connecting to the proxy with IAM auth.
3. Copy the output from `superuser_proxy_iam_connect` and paste it into the window - NOTE: remove the string escape slashes `psql \"host...` -> `psql "host...`
4. You should now be connected to the `example` database in the Aurora cluster via the AWS Proxy using IAM authentication
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.12.26 |
| aws | >= 3.9 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 3.9 |
| random | n/a |
## Inputs
No input.
## Outputs
| Name | Description |
|------|-------------|
| log\_group\_arn | The Amazon Resource Name (ARN) of the CloudWatch log group |
| proxy\_arn | The Amazon Resource Name (ARN) for the proxy |
| proxy\_default\_target\_group\_arn | The Amazon Resource Name (ARN) for the default target group |
| proxy\_default\_target\_group\_id | The ID for the default target group |
| proxy\_default\_target\_group\_name | The name of the default target group |
| proxy\_endpoint | The endpoint that you can use to connect to the proxy |
| proxy\_id | The ID for the proxy |
| proxy\_target\_endpoint | Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type |
| proxy\_target\_id | Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/) |
| proxy\_target\_port | Port for the target RDS DB Instance or Aurora DB Cluster |
| proxy\_target\_rds\_resource\_id | Identifier representing the DB Instance or DB Cluster target |
| proxy\_target\_target\_arn | Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API |
| proxy\_target\_tracked\_cluster\_id | DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS\_INSTANCE target that is part of a DB Cluster |
| proxy\_target\_type | Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER` |
| superuser\_db\_password\_connect | Connect to database using superuser with username/password directly to database |
| superuser\_proxy\_iam\_connect | Connect to RDS Proxy using IAM auth via token generated |
| superuser\_proxy\_iam\_token | Gerate connection token for connecting to RDS Proxy with IAM auth |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Apache-2.0 Licensed. See [LICENSE](../../LICENSE).

View file

@ -0,0 +1,294 @@
provider "aws" {
region = local.region
}
locals {
region = "us-east-1"
name = "example-${replace(basename(path.cwd), "_", "-")}"
db_name = "example"
db_username = random_pet.users.id # using random here due to secrets taking at least 7 days before fully deleting from account
db_password = random_password.password.result
db_proxy_resource_id = element(split(":", module.rds_proxy.proxy_arn), 6)
db_iam_connect_prefix = "arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${local.db_proxy_resource_id}"
tags = {
Example = local.name
Environment = "dev"
}
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
################################################################################
# Supporting Resources
################################################################################
resource "random_pet" "users" {
length = 2
separator = "_"
}
resource "random_password" "password" {
length = 16
special = false
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.64.0"
name = local.name
cidr = "10.0.0.0/18"
azs = ["${local.region}a", "${local.region}b", "${local.region}c"]
public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"]
database_subnets = ["10.0.7.0/24", "10.0.8.0/24", "10.0.9.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
tags = local.tags
}
module "rds_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds"
description = "PostgreSQL RDS example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet PostgreSQL access"
rule = "postgresql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
tags = local.tags
}
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "2.20.0"
name = local.db_name
username = local.db_username
password = local.db_password
# When using RDS Proxy w/ IAM auth - Database must be username/password auth, not IAM
iam_database_authentication_enabled = false
identifier = local.name
engine = "postgres"
engine_version = "11.9"
family = "postgres11"
major_engine_version = "11"
port = 5432
instance_class = "db.t3.micro"
allocated_storage = 5
storage_encrypted = false
apply_immediately = true
vpc_security_group_ids = [module.rds_sg.this_security_group_id]
subnet_ids = module.vpc.database_subnets
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
backup_retention_period = 0
deletion_protection = false
tags = local.tags
}
################################################################################
# Test Resources
################################################################################
resource "aws_iam_instance_profile" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.name
}
data "aws_iam_policy_document" "ec2_test_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ec2_test" {
name_prefix = local.name
force_detach_policies = true
assume_role_policy = data.aws_iam_policy_document.ec2_test_assume.json
}
data "aws_iam_policy_document" "ec2_test" {
statement {
actions = ["rds-db:connect"]
resources = ["${local.db_iam_connect_prefix}/${local.db_username}"]
}
}
resource "aws_iam_role_policy" "ec2_test" {
name_prefix = local.name
role = aws_iam_role.ec2_test.id
policy = data.aws_iam_policy_document.ec2_test.json
}
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2_test.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
module "ec2_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "ec2"
description = "EC2 RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
egress_rules = ["all-all"]
tags = local.tags
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.16.0"
name = local.name
instance_count = 1
associate_public_ip_address = true
iam_instance_profile = aws_iam_instance_profile.ec2_test.name
user_data = <<-EOT
#!/usr/bin/env bash
mkdir -p /home/ssm-user/ && wget -O /home/ssm-user/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
apt update
apt install awscli postgresql -y
EOT
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [module.ec2_sg.this_security_group_id]
subnet_ids = module.vpc.private_subnets
tags = local.tags
}
################################################################################
# Secrets - DB user passwords
################################################################################
data "aws_kms_alias" "secretsmanager" {
name = "alias/aws/secretsmanager"
}
resource "aws_secretsmanager_secret" "superuser" {
name = local.db_username
description = "Database superuser, ${local.db_username}, databse connection values"
kms_key_id = data.aws_kms_alias.secretsmanager.id
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "superuser" {
secret_id = aws_secretsmanager_secret.superuser.id
secret_string = jsonencode({
username = local.db_username
password = local.db_password
})
}
################################################################################
# RDS Proxy
################################################################################
module "rds_proxy_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.17.0"
name = "rds_proxy"
description = "PostgreSQL RDS Proxy example security group"
vpc_id = module.vpc.vpc_id
revoke_rules_on_delete = true
ingress_with_cidr_blocks = [
{
description = "Private subnet PostgreSQL access"
rule = "postgresql-tcp"
cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks)
}
]
egress_with_cidr_blocks = [
{
description = "Database subnet PostgreSQL access"
rule = "postgresql-tcp"
cidr_blocks = join(",", module.vpc.database_subnets_cidr_blocks)
},
]
tags = local.tags
}
module "rds_proxy" {
source = "../../"
create_proxy = true
name = local.name
iam_role_name = local.name
vpc_subnet_ids = module.vpc.private_subnets
vpc_security_group_ids = [module.rds_proxy_sg.this_security_group_id]
secrets = {
"${local.db_username}" = {
description = aws_secretsmanager_secret.superuser.description
arn = aws_secretsmanager_secret.superuser.arn
kms_key_id = aws_secretsmanager_secret.superuser.kms_key_id
}
}
engine_family = "POSTGRESQL"
db_host = module.rds.this_db_instance_address
db_name = module.rds.this_db_instance_name
debug_logging = true
# Target RDS instance
target_db_instance = true
db_instance_identifier = module.rds.this_db_instance_id
tags = local.tags
}

View file

@ -0,0 +1,89 @@
# RDS Proxy
output "proxy_id" {
description = "The ID for the proxy"
value = module.rds_proxy.proxy_id
}
output "proxy_arn" {
description = "The Amazon Resource Name (ARN) for the proxy"
value = module.rds_proxy.proxy_arn
}
output "proxy_endpoint" {
description = "The endpoint that you can use to connect to the proxy"
value = module.rds_proxy.proxy_endpoint
}
# Proxy Default Target Group
output "proxy_default_target_group_id" {
description = "The ID for the default target group"
value = module.rds_proxy.proxy_default_target_group_id
}
output "proxy_default_target_group_arn" {
description = "The Amazon Resource Name (ARN) for the default target group"
value = module.rds_proxy.proxy_default_target_group_arn
}
output "proxy_default_target_group_name" {
description = "The name of the default target group"
value = module.rds_proxy.proxy_default_target_group_name
}
# Proxy Target
output "proxy_target_endpoint" {
description = "Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type"
value = module.rds_proxy.proxy_target_endpoint
}
output "proxy_target_id" {
description = "Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/)"
value = module.rds_proxy.proxy_target_id
}
output "proxy_target_port" {
description = "Port for the target RDS DB Instance or Aurora DB Cluster"
value = module.rds_proxy.proxy_target_port
}
output "proxy_target_rds_resource_id" {
description = "Identifier representing the DB Instance or DB Cluster target"
value = module.rds_proxy.proxy_target_rds_resource_id
}
output "proxy_target_target_arn" {
description = "Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API"
value = module.rds_proxy.proxy_target_target_arn
}
output "proxy_target_tracked_cluster_id" {
description = "DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS_INSTANCE target that is part of a DB Cluster"
value = module.rds_proxy.proxy_target_tracked_cluster_id
}
output "proxy_target_type" {
description = "Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`"
value = module.rds_proxy.proxy_target_type
}
# CloudWatch logs
output "log_group_arn" {
description = "The Amazon Resource Name (ARN) of the CloudWatch log group"
value = module.rds_proxy.log_group_arn
}
# For aiding in testing & verification
output "superuser_db_password_connect" {
description = "Connect to database using superuser with username/password directly to database"
value = "PGPASSWORD=${local.db_password} psql -h ${module.rds.this_db_instance_address} -p 5432 -d ${module.rds.this_db_instance_name} -U ${local.db_username} --set=sslmode=require"
}
output "superuser_proxy_iam_token" {
description = "Gerate connection token for connecting to RDS Proxy with IAM auth"
value = "TOKEN=$(aws rds generate-db-auth-token --hostname ${module.rds_proxy.proxy_endpoint} --port 5432 --region ${local.region} --username ${local.db_username})"
}
output "superuser_proxy_iam_connect" {
description = "Connect to RDS Proxy using IAM auth via token generated"
value = "psql \"host=${module.rds_proxy.proxy_endpoint} port=5432 sslmode=verify-full sslrootcert=/home/ssm-user/AmazonRootCA1.pem dbname=${module.rds.this_db_instance_name} user=${local.db_username} password=$TOKEN\""
}

View file

@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.26"
required_providers {
aws = ">= 3.9"
}
}

171
main.tf Normal file
View file

@ -0,0 +1,171 @@
locals {
db_identifier_key = var.db_instance_identifier != "" ? "dbInstanceIdentifier" : "dbClusterIdentifier"
db_identifier_value = var.db_instance_identifier != "" ? var.db_instance_identifier : var.db_cluster_identifier
role_arn = var.create_proxy && var.create_iam_role ? aws_iam_role.this[0].arn : var.role_arn
role_name = coalesce(var.iam_role_name, var.name)
policy_name = coalesce(var.iam_policy_name, var.name)
}
data "aws_region" "current" {}
################################################################################
# RDS Proxy
################################################################################
resource "aws_db_proxy" "this" {
count = var.create_proxy ? 1 : 0
name = var.name
debug_logging = var.debug_logging
engine_family = var.engine_family
idle_client_timeout = var.idle_client_timeout
require_tls = var.require_tls
role_arn = local.role_arn
vpc_security_group_ids = var.vpc_security_group_ids
vpc_subnet_ids = var.vpc_subnet_ids
dynamic "auth" {
for_each = var.secrets
content {
auth_scheme = var.auth_scheme
description = auth.value.description
iam_auth = var.iam_auth
secret_arn = auth.value.arn
}
}
tags = merge(var.tags, var.proxy_tags)
depends_on = [aws_cloudwatch_log_group.this]
}
resource "aws_db_proxy_default_target_group" "this" {
count = var.create_proxy ? 1 : 0
db_proxy_name = aws_db_proxy.this[0].name
connection_pool_config {
connection_borrow_timeout = var.connection_borrow_timeout
init_query = var.init_query
max_connections_percent = var.max_connections_percent
max_idle_connections_percent = var.max_idle_connections_percent
session_pinning_filters = var.session_pinning_filters
}
}
resource "aws_db_proxy_target" "db_instance" {
count = var.create_proxy && var.target_db_instance ? 1 : 0
db_proxy_name = aws_db_proxy.this[0].name
target_group_name = aws_db_proxy_default_target_group.this[0].name
db_instance_identifier = var.db_instance_identifier
}
resource "aws_db_proxy_target" "db_cluster" {
count = var.create_proxy && var.target_db_cluster ? 1 : 0
db_proxy_name = aws_db_proxy.this[0].name
target_group_name = aws_db_proxy_default_target_group.this[0].name
db_cluster_identifier = var.db_cluster_identifier
}
################################################################################
# CloudWatch Logs
################################################################################
resource "aws_cloudwatch_log_group" "this" {
count = var.create_proxy && var.manage_log_group && var.debug_logging ? 1 : 0
name = "/aws/rds/proxy/${var.name}"
retention_in_days = var.log_group_retention_in_days
kms_key_id = var.log_group_kms_key_id
tags = merge(var.tags, var.log_group_tags)
}
################################################################################
# IAM Role
################################################################################
data "aws_iam_policy_document" "assume_role" {
count = var.create_proxy && var.create_iam_role ? 1 : 0
statement {
sid = "RDSAssume"
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["rds.amazonaws.com"]
}
}
}
resource "aws_iam_role" "this" {
count = var.create_proxy && var.create_iam_role ? 1 : 0
name = var.use_role_name_prefix ? null : local.role_name
name_prefix = var.use_role_name_prefix ? "${local.role_name}-" : null
description = var.iam_role_description
path = var.iam_role_path
assume_role_policy = data.aws_iam_policy_document.assume_role[0].json
force_detach_policies = var.iam_role_force_detach_policies
max_session_duration = var.iam_role_max_session_duration
permissions_boundary = var.iam_role_permissions_boundary
tags = merge(var.tags, var.iam_role_tags)
}
data "aws_iam_policy_document" "this" {
count = var.create_proxy && var.create_iam_role ? 1 : 0
statement {
sid = "DecryptSecrets"
effect = "Allow"
actions = ["kms:Decrypt"]
resources = distinct([for secret in var.secrets : secret.kms_key_id])
condition {
test = "StringEquals"
variable = "kms:ViaService"
values = [
"secretsmanager.${data.aws_region.current.name}.amazonaws.com"
]
}
}
statement {
sid = "ListSecrets"
effect = "Allow"
actions = [
"secretsmanager:GetRandomPassword",
"secretsmanager:ListSecrets",
]
resources = ["*"]
}
statement {
sid = "GetSecrets"
effect = "Allow"
actions = [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds",
]
resources = distinct([for secret in var.secrets : secret.arn])
}
}
resource "aws_iam_role_policy" "this" {
count = var.create_proxy && var.create_iam_role && var.create_iam_policy ? 1 : 0
name = var.use_policy_name_prefix ? null : local.policy_name
name_prefix = var.use_policy_name_prefix ? "${local.policy_name}-" : null
policy = data.aws_iam_policy_document.this[0].json
role = aws_iam_role.this[0].id
}

73
outputs.tf Normal file
View file

@ -0,0 +1,73 @@
# RDS Proxy
output "proxy_id" {
description = "The ID for the proxy"
value = element(concat(aws_db_proxy.this.*.id, [""]), 0)
}
output "proxy_arn" {
description = "The Amazon Resource Name (ARN) for the proxy"
value = element(concat(aws_db_proxy.this.*.arn, [""]), 0)
}
output "proxy_endpoint" {
description = "The endpoint that you can use to connect to the proxy"
value = element(concat(aws_db_proxy.this.*.endpoint, [""]), 0)
}
# Proxy Default Target Group
output "proxy_default_target_group_id" {
description = "The ID for the default target group"
value = element(concat(aws_db_proxy_default_target_group.this.*.id, [""]), 0)
}
output "proxy_default_target_group_arn" {
description = "The Amazon Resource Name (ARN) for the default target group"
value = element(concat(aws_db_proxy_default_target_group.this.*.id, [""]), 0)
}
output "proxy_default_target_group_name" {
description = "The name of the default target group"
value = element(concat(aws_db_proxy_default_target_group.this.*.id, [""]), 0)
}
# Proxy Target
output "proxy_target_endpoint" {
description = "Hostname for the target RDS DB Instance. Only returned for `RDS_INSTANCE` type"
value = element(concat(aws_db_proxy_target.db_instance.*.endpoint, aws_db_proxy_target.db_cluster.*.endpoint, [""]), 0)
}
output "proxy_target_id" {
description = "Identifier of `db_proxy_name`, `target_group_name`, target type (e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`), and resource identifier separated by forward slashes (/)"
value = element(concat(aws_db_proxy_target.db_instance.*.id, aws_db_proxy_target.db_cluster.*.id, [""]), 0)
}
output "proxy_target_port" {
description = "Port for the target RDS DB Instance or Aurora DB Cluster"
value = element(concat(aws_db_proxy_target.db_instance.*.port, aws_db_proxy_target.db_cluster.*.port, [""]), 0)
}
output "proxy_target_rds_resource_id" {
description = "Identifier representing the DB Instance or DB Cluster target"
value = element(concat(aws_db_proxy_target.db_instance.*.rds_resource_id, aws_db_proxy_target.db_cluster.*.rds_resource_id, [""]), 0)
}
output "proxy_target_target_arn" {
description = "Amazon Resource Name (ARN) for the DB instance or DB cluster. Currently not returned by the RDS API"
value = element(concat(aws_db_proxy_target.db_instance.*.target_arn, aws_db_proxy_target.db_cluster.*.target_arn, [""]), 0)
}
output "proxy_target_tracked_cluster_id" {
description = "DB Cluster identifier for the DB Instance target. Not returned unless manually importing an RDS_INSTANCE target that is part of a DB Cluster"
value = element(concat(aws_db_proxy_target.db_cluster.*.tracked_cluster_id, [""]), 0)
}
output "proxy_target_type" {
description = "Type of target. e.g. `RDS_INSTANCE` or `TRACKED_CLUSTER`"
value = element(concat(aws_db_proxy_target.db_instance.*.type, aws_db_proxy_target.db_cluster.*.type, [""]), 0)
}
# CloudWatch logs
output "log_group_arn" {
description = "The Amazon Resource Name (ARN) of the CloudWatch log group"
value = element(concat(aws_cloudwatch_log_group.this.*.arn, [""]), 0)
}

263
variables.tf Normal file
View file

@ -0,0 +1,263 @@
variable "tags" {
description = "A map of tags to use on all resources"
type = map(string)
default = {}
}
# RDS Proxy
variable "create_proxy" {
description = "Determines whether a proxy and its resources will be created"
type = bool
default = true
}
variable "name" {
description = "The identifier for the proxy. This name must be unique for all proxies owned by your AWS account in the specified AWS Region. An identifier must begin with a letter and must contain only ASCII letters, digits, and hyphens; it can't end with a hyphen or contain two consecutive hyphens"
type = string
default = ""
}
variable "auth" {
description = "Configuration block(s) with authorization mechanisms to connect to the associated instances or clusters"
type = map(string)
default = {}
}
variable "debug_logging" {
description = "Whether the proxy includes detailed information about SQL statements in its logs"
type = bool
default = false
}
variable "engine_family" {
description = "The kind of database engine that the proxy will connect to. Valid values are `MYSQL` or `POSTGRESQL`"
type = string
default = ""
}
variable "idle_client_timeout" {
description = "The number of seconds that a connection to the proxy can be inactive before the proxy disconnects it"
type = number
default = 1800
}
variable "require_tls" {
description = "A Boolean parameter that specifies whether Transport Layer Security (TLS) encryption is required for connections to the proxy"
type = bool
default = true
}
variable "role_arn" {
description = "The Amazon Resource Name (ARN) of the IAM role that the proxy uses to access secrets in AWS Secrets Manager"
type = string
default = ""
}
variable "vpc_security_group_ids" {
description = "One or more VPC security group IDs to associate with the new proxy"
type = list(string)
default = []
}
variable "vpc_subnet_ids" {
description = "One or more VPC subnet IDs to associate with the new proxy"
type = list(string)
default = []
}
variable "auth_scheme" {
description = "The type of authentication that the proxy uses for connections from the proxy to the underlying database. One of `SECRETS`"
type = string
default = "SECRETS"
}
variable "iam_auth" {
description = "Whether to require or disallow AWS Identity and Access Management (IAM) authentication for connections to the proxy. One of `DISABLED`, `REQUIRED`"
type = string
default = "REQUIRED"
}
variable "proxy_tags" {
description = "A map of tags to apply to the RDS Proxy"
type = map(string)
default = {}
}
variable "secrets" {
description = "Map of secerets to be used by RDS Proxy for authentication to the database"
type = map(object({ arn = string, description = string, kms_key_id = string }))
default = {}
}
variable "db_host" {
description = "The identifier to use for the database endpoint"
type = string
default = ""
}
variable "db_name" {
description = "The name of the database"
type = string
default = ""
}
# Proxy Default Target Group
variable "connection_borrow_timeout" {
description = "The number of seconds for a proxy to wait for a connection to become available in the connection pool"
type = number
default = null
}
variable "init_query" {
description = "One or more SQL statements for the proxy to run when opening each new database connection"
type = string
default = ""
}
variable "max_connections_percent" {
description = "The maximum size of the connection pool for each target in a target group"
type = number
default = 90
}
variable "max_idle_connections_percent" {
description = "Controls how actively the proxy closes idle database connections in the connection pool"
type = number
default = 50
}
variable "session_pinning_filters" {
description = "Each item in the list represents a class of SQL operations that normally cause all later statements in a session using a proxy to be pinned to the same underlying database connection"
type = list(string)
default = []
}
# Proxy Target
variable "target_db_instance" {
description = "Determines whether DB instance is targetted by proxy"
type = bool
default = false
}
variable "db_instance_identifier" {
description = "DB instance identifier"
type = string
default = ""
}
variable "target_db_cluster" {
description = "Determines whether DB cluster is targetted by proxy"
type = bool
default = false
}
variable "db_cluster_identifier" {
description = "DB cluster identifier"
type = string
default = ""
}
# CloudWatch Logs
variable "manage_log_group" {
description = "Determines whether Terraform will create/manage the CloudWatch log group or not. Note - this will fail if set to true after the log group has been created as the resource will already exist"
type = bool
default = true
}
variable "log_group_retention_in_days" {
description = "Specifies the number of days you want to retain log events in the log group"
type = number
default = 30
}
variable "log_group_kms_key_id" {
description = "The ARN of the KMS Key to use when encrypting log data"
type = string
default = null
}
variable "log_group_tags" {
description = "A map of tags to apply to the CloudWatch log group"
type = map(string)
default = {}
}
# IAM Role
variable "create_iam_role" {
description = "Determines whether an IAM role is created"
type = bool
default = true
}
variable "iam_role_name" {
description = "The name of the role. If omitted, Terraform will assign a random, unique name"
type = string
default = ""
}
variable "use_role_name_prefix" {
description = "Whether to use unique name beginning with the specified `iam_role_name`"
type = bool
default = false
}
variable "iam_role_description" {
description = "The description of the role"
type = string
default = ""
}
variable "iam_role_path" {
description = "The path to the role"
type = string
default = null
}
variable "iam_role_force_detach_policies" {
description = "Specifies to force detaching any policies the role has before destroying it"
type = bool
default = true
}
variable "iam_role_max_session_duration" {
description = "The maximum session duration (in seconds) that you want to set for the specified role"
type = number
default = 43200 # 12 hours
}
variable "iam_role_permissions_boundary" {
description = "The ARN of the policy that is used to set the permissions boundary for the role"
type = string
default = null
}
variable "iam_role_tags" {
description = "A map of tags to apply to the IAM role"
type = map(string)
default = {}
}
# IAM Policy
variable "create_iam_policy" {
description = "Determines whether an IAM policy is created"
type = bool
default = true
}
variable "iam_policy_name" {
description = "The name of the role policy. If omitted, Terraform will assign a random, unique name"
type = string
default = ""
}
variable "use_policy_name_prefix" {
description = "Whether to use unique name beginning with the specified `iam_policy_name`"
type = bool
default = false
}
variable "iam_creation_wait_duration" {
description = "Time duration delay to wait for IAM resource creation/propagation. For example, 30s for 30 seconds or 5m for 5 minutes. Updating this value by itself will not trigger a delay."
type = string
default = "30s"
}

7
versions.tf Normal file
View file

@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.26"
required_providers {
aws = ">= 3.9"
}
}