diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0a26a19 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=500'] + - id: check-executables-have-shebangs + - id: pretty-format-json + args: ['--autofix', '--no-sort-keys', '--indent=2'] + - id: check-byte-order-marker + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-symlinks + - id: detect-private-key + - id: check-merge-conflict + - id: detect-aws-credentials + args: ['--allow-missing-credentials'] + - id: trailing-whitespace +- repo: git://github.com/antonbabenko/pre-commit-terraform + rev: v1.29.0 + hooks: + - id: terraform_fmt + - id: terraform_docs + - id: terraform_tflint \ No newline at end of file diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..c42ad9a --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,3 @@ +rule "aws_iam_role_invalid_permissions_boundary" { + enabled = false +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d955a86 --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +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. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e992c54 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +ifneq (,) +.error This Makefile requires GNU Make. +endif + +.PHONY: hooks validate + +help: + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +hooks: ## Commit hooks setup + @pre-commit install + @pre-commit gc + @pre-commit autoupdate + +validate: ## Validate files with pre-commit hooks + @pre-commit run --all-files diff --git a/README.md b/README.md index 799a68c..b0da867 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,189 @@ # terraform-aws-rds-aurora -Terraform module which creates AWS RDS Aurora resources +Terraform module which creates AWS RDS Aurora resources. This module was created to work with Secrets Manager. + +## Terraform versions + +Terraform 0.12. Pin module version to `~> v1.0`. Submit pull-requests to `master` branch. + +## Usage + +```hcl +module "rds-aurora-mysql" { + source = "umotif-public/rds-aurora/aws" + version = "~> 1.0.0" + + name_prefix = "example-aurora-mysql" + engine = "aurora-mysql" + engine_version = "5.7.12" + deletion_protection = true + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + replica_count = 2 + instance_type = "db.r5.large" + apply_immediately = true + skip_final_snapshot = true + + db_parameter_group_name = "default" + db_cluster_parameter_group_name = "default" + + iam_database_authentication_enabled = true + + allowed_cidr_blocks = ["10.10.0.0/24", "20.10.0.0/24"] + + create_security_group = true + + monitoring_interval = 60 + enabled_cloudwatch_logs_exports = [ + { + name = "audit", + retention_in_days = "60" + kms_key_id = module.kms-cloudwatch.key_arn + }, + { + name = "error" + kms_key_id = module.kms-cloudwatch.key_arn + }, + { + name = "general", + retention_in_days = "30" + }, + { + name = "slowquery", + } + ] + + tags = { + Environment = "test" + } +} +``` + +## Assumptions + +Module is to be used with Terraform > 0.12. + +## Examples + +* [Aurora MySQL](https://github.com/umotif-public/terraform-aws-rds-aurora/tree/master/examples/aurora-mysql) + +## Authors + +Module managed by [Marcin Cuber](https://github.com/marcincuber) [LinkedIn](https://www.linkedin.com/in/marcincuber/). + + +## Requirements + +| Name | Version | +|------|---------| +| terraform | ~> 0.12.6 | +| aws | ~> 2.45 | +| random | ~> 2.2 | + +## Providers + +| Name | Version | +|------|---------| +| aws | ~> 2.45 | +| random | ~> 2.2 | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| allowed\_cidr\_blocks | A list of CIDR blocks which are allowed to access the database | `list(string)` | `[]` | no | +| allowed\_security\_groups | A list of Security Group ID's to allow access to. | `list(string)` | `[]` | no | +| apply\_immediately | Determines whether or not any DB modifications are applied immediately, or during the maintenance window | `bool` | `false` | no | +| auto\_minor\_version\_upgrade | Determines whether minor engine upgrades will be performed automatically in the maintenance window | `bool` | `true` | no | +| backtrack\_window | The target backtrack window, in seconds. Only available for aurora engine currently. To disable backtracking, set this value to 0. Defaults to 0. Must be between 0 and 259200 (72 hours) | `number` | `0` | no | +| backup\_retention\_period | How long to keep backups for (in days) | `number` | `7` | no | +| ca\_cert\_identifier | The identifier of the CA certificate for the DB instance. | `string` | `"rds-ca-2019"` | no | +| copy\_tags\_to\_snapshot | Copy all Cluster tags to snapshots. | `bool` | `false` | no | +| create\_monitoring\_role | Whether to create the IAM role for RDS enhanced monitoring | `bool` | `true` | no | +| create\_security\_group | Whether to create security group for RDS cluster | `bool` | `true` | no | +| database\_name | Name for an automatically created database on cluster creation | `string` | `""` | no | +| db\_cluster\_parameter\_group\_name | The name of a DB Cluster parameter group to use | `string` | `null` | no | +| db\_parameter\_group\_name | The name of a DB parameter group to use | `string` | `null` | no | +| db\_subnet\_group\_name | The existing subnet group name to use | `string` | `""` | no | +| deletion\_protection | If the DB instance should have deletion protection enabled | `bool` | `false` | no | +| enable\_http\_endpoint | Whether or not to enable the Data API for a serverless Aurora database engine. | `bool` | `false` | no | +| enabled\_cloudwatch\_logs\_exports | List of object which define log types to export to cloudwatch. See in examples. | `list` | `[]` | no | +| engine | Aurora database engine type, currently aurora, aurora-mysql or aurora-postgresql | `string` | `"aurora"` | no | +| engine\_mode | The database engine mode. Valid values: global, parallelquery, provisioned, serverless. | `string` | `"provisioned"` | no | +| engine\_version | Aurora database engine version. | `string` | `"5.7.12"` | no | +| final\_snapshot\_identifier\_prefix | The prefix name to use when creating a final snapshot on cluster destroy, appends a random 8 digits to name to ensure it's unique too. | `string` | `"final"` | no | +| global\_cluster\_identifier | The global cluster identifier specified on aws\_rds\_global\_cluster | `string` | `""` | no | +| iam\_database\_authentication\_enabled | Specifies whether IAM Database authentication should be enabled or not. Not all versions and instances are supported. Refer to the AWS documentation to see which versions are supported. | `bool` | `true` | no | +| iam\_roles | A List of ARNs for the IAM roles to associate to the RDS Cluster. | `list(string)` | `[]` | no | +| instance\_type | Instance type to use | `string` | n/a | yes | +| instances\_parameters | Individual settings for instances. | `list` | `[]` | no | +| kms\_key\_id | The ARN for the KMS encryption key if one is set to the cluster. | `string` | `""` | no | +| monitoring\_interval | The interval (seconds) between points when Enhanced Monitoring metrics are collected. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60. | `number` | `0` | no | +| monitoring\_role\_arn | IAM role for RDS to send enhanced monitoring metrics to CloudWatch | `string` | `""` | no | +| name\_prefix | Prefix Name used across all resources | `string` | n/a | yes | +| password | Master DB password | `string` | `""` | no | +| performance\_insights\_enabled | Specifies whether Performance Insights is enabled or not. | `bool` | `false` | no | +| performance\_insights\_kms\_key\_id | The ARN for the KMS key to encrypt Performance Insights data. | `string` | `""` | no | +| permissions\_boundary | The ARN of the policy that is used to set the permissions boundary for the role. | `string` | `null` | no | +| port | The port on which to accept connections | `string` | `""` | no | +| predefined\_metric\_type | The metric type to scale on. Valid values are RDSReaderAverageCPUUtilization and RDSReaderAverageDatabaseConnections. | `string` | `"RDSReaderAverageCPUUtilization"` | no | +| preferred\_backup\_window | When to perform DB backups | `string` | `"02:00-03:00"` | no | +| preferred\_cluster\_maintenance\_window | When to perform maintenance on the cluster | `string` | `"sun:05:00-sun:06:00"` | no | +| preferred\_instance\_maintenance\_window | When to perform maintenance on the instances | `string` | `"sun:05:00-sun:06:00"` | no | +| publicly\_accessible | Whether the DB should have a public IP address | `bool` | `false` | no | +| replica\_count | Number of reader nodes to create. If `replica_scale_enable` is `true`, the value of `replica_scale_min` is used instead. | `number` | `1` | no | +| replica\_scale\_connections | Average number of connections to trigger autoscaling at. Default value is 70% of db.r4.large's default max\_connections | `number` | `700` | no | +| replica\_scale\_cpu | CPU usage to trigger autoscaling at | `number` | `70` | no | +| replica\_scale\_enabled | Whether to enable autoscaling for RDS Aurora (MySQL) read replicas | `bool` | `false` | no | +| replica\_scale\_in\_cooldown | Cooldown in seconds before allowing further scaling operations after a scale in | `number` | `300` | no | +| replica\_scale\_max | Maximum number of replicas to allow scaling for | `number` | `0` | no | +| replica\_scale\_min | Minimum number of replicas to allow scaling for | `number` | `2` | no | +| replica\_scale\_out\_cooldown | Cooldown in seconds before allowing further scaling operations after a scale out | `number` | `300` | no | +| replication\_source\_identifier | ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica. | `string` | `""` | no | +| scaling\_configuration | Map of nested attributes with scaling properties. Only valid when engine\_mode is set to `serverless` | `map(string)` | `{}` | no | +| security\_group\_description | The description of the security group. If value is set to empty string it will contain cluster name in the description. | `string` | `""` | no | +| skip\_final\_snapshot | Should a final snapshot be created on cluster destroy | `bool` | `false` | no | +| snapshot\_identifier | DB snapshot to create this database from | `string` | `""` | no | +| source\_region | The source region for an encrypted replica DB cluster. | `string` | `""` | no | +| storage\_encrypted | Specifies whether the underlying storage layer should be encrypted | `bool` | `true` | no | +| subnets | List of subnet IDs to use | `list(string)` | `[]` | no | +| tags | A map of tags to add to all resources. | `map(string)` | `{}` | no | +| username | Master DB username | `string` | `"root"` | no | +| vpc\_id | VPC ID | `string` | n/a | yes | +| vpc\_security\_group\_ids | List of VPC security groups to associate to the cluster in addition to the SG that can be created in this module. | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| rds\_cluster\_arn | The ID of the aurora cluster | +| rds\_cluster\_endpoint | The cluster endpoint | +| rds\_cluster\_id | The ID of the cluster | +| rds\_cluster\_instance\_endpoints | A list of all cluster instance endpoints | +| rds\_cluster\_master\_password | The master password | +| rds\_cluster\_master\_username | The master username | +| rds\_cluster\_port | The port | +| rds\_cluster\_reader\_endpoint | The cluster reader endpoint | +| rds\_cluster\_resource\_id | The Resource ID of the cluster | +| security\_group\_id | The security group ID of the cluster | + + + +## License + +See LICENSE for full details. + +## Pre-commit hooks + +### Install dependencies + +* [`pre-commit`](https://pre-commit.com/#install) +* [`terraform-docs`](https://github.com/segmentio/terraform-docs) required for `terraform_docs` hooks. +* [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook. + +#### MacOS + +```bash +brew install pre-commit terraform-docs tflint +``` \ No newline at end of file diff --git a/examples/aurora-mysql/README.md b/examples/aurora-mysql/README.md new file mode 100644 index 0000000..c2e598a --- /dev/null +++ b/examples/aurora-mysql/README.md @@ -0,0 +1,9 @@ + +## Example deployment flow + +```bash +terraform init +terraform validate +terraform plan +terraform apply --auto-approve +``` \ No newline at end of file diff --git a/examples/aurora-mysql/main.tf b/examples/aurora-mysql/main.tf new file mode 100644 index 0000000..0eceb28 --- /dev/null +++ b/examples/aurora-mysql/main.tf @@ -0,0 +1,183 @@ +provider "aws" { + region = "eu-west-1" +} + +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +##### +# VPC and subnets +##### +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 2.32" + + name = "simple-vpc" + + cidr = "10.0.0.0/16" + + azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] + + enable_nat_gateway = false +} + +############# +# KMS key +############# +module "kms" { + source = "umotif-public/kms/aws" + version = "~> 1.0" + + alias_name = "rds-kms-test-key" + deletion_window_in_days = 7 + enable_key_rotation = true + policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "Enable IAM User Permissions", + "Effect" : "Allow", + "Principal" : { + "AWS" : [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", + data.aws_caller_identity.current.arn + ] + }, + "Action" : "kms:*", + "Resource" : "*" + }, + { + "Sid" : "Allow use of the key", + "Effect" : "Allow", + "Principal" : { + "Service" : ["rds.amazonaws.com", "monitoring.rds.amazonaws.com"] + }, + "Action" : [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ], + "Resource" : "*" + } + ] + } + ) + + tags = { + Environment = "test" + } +} + +module "kms-cloudwatch" { + source = "umotif-public/kms/aws" + version = "~> 1.0" + + alias_name = "cloudwatch-kms-test-key" + deletion_window_in_days = 7 + enable_key_rotation = true + policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "Enable IAM User Permissions", + "Effect" : "Allow", + "Principal" : { + "AWS" : [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", + data.aws_caller_identity.current.arn + ] + }, + "Action" : "kms:*", + "Resource" : "*" + }, + { + "Effect" : "Allow", + "Principal" : { "Service" : "logs.${data.aws_region.current.name}.amazonaws.com" }, + "Action" : [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Resource" : "*" + } + ] + } + ) + + tags = { + Environment = "test" + } +} +############# +# RDS Aurora +############# +module "aurora" { + source = "../../" + + name_prefix = "example-aurora-mysql" + database_name = "databaseName" + engine = "aurora-mysql" + engine_version = "5.7.12" + deletion_protection = false + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + kms_key_id = module.kms.key_arn + + replica_count = 1 + instance_type = "db.t3.medium" + apply_immediately = true + skip_final_snapshot = true + db_parameter_group_name = aws_db_parameter_group.aurora_mysql_db_57_parameter_group.id + db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.aurora_mysql_db_57_cluster_parameter_group.id + iam_database_authentication_enabled = true + + enabled_cloudwatch_logs_exports = [ + { + name = "audit", + retention_in_days = "60" + kms_key_id = module.kms-cloudwatch.key_arn + }, + { + name = "error" + kms_key_id = module.kms-cloudwatch.key_arn + }, + { + name = "general", + retention_in_days = "30" + }, + { + name = "slowquery", + } + ] + + allowed_cidr_blocks = ["10.10.0.0/24", "20.10.0.0/24"] + + monitoring_interval = 60 + + create_security_group = true + + tags = { + Environment = "test" + } +} + +resource "aws_db_parameter_group" "aurora_mysql_db_57_parameter_group" { + name = "test-aurora-mysql-db-57-parameter-group" + family = "aurora-mysql5.7" +} + +resource "aws_rds_cluster_parameter_group" "aurora_mysql_db_57_cluster_parameter_group" { + name = "test-aurora-mysql-db-57-cluster-parameter-group" + family = "aurora-mysql5.7" +} + diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..7638e8a --- /dev/null +++ b/main.tf @@ -0,0 +1,256 @@ +##### +# Security Group resources +##### +resource "aws_security_group" "main" { + count = var.create_security_group ? 1 : 0 + + name_prefix = "${var.name_prefix}-sg-" + vpc_id = var.vpc_id + + description = var.security_group_description == "" ? "Control traffic to/from RDS Aurora ${var.name_prefix}" : var.security_group_description + + tags = merge(var.tags, + { + Name = "${var.name_prefix}-sg" + } + ) +} + +resource "aws_security_group_rule" "main_egress" { + count = var.create_security_group ? 1 : 0 + + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = join("", aws_security_group.main.*.id) +} + +resource "aws_security_group_rule" "main_default_ingress" { + count = var.create_security_group ? length(var.allowed_security_groups) : 0 + + description = "Ingress allowed from SGs" + + type = "ingress" + from_port = aws_rds_cluster.main.port + to_port = aws_rds_cluster.main.port + protocol = "tcp" + source_security_group_id = element(var.allowed_security_groups, count.index) + security_group_id = join("", aws_security_group.main.*.id) +} + +resource "aws_security_group_rule" "main_cidr_ingress" { + count = var.create_security_group && length(var.allowed_cidr_blocks) > 0 ? 1 : 0 + + description = "Ingress allowed from CIDRs" + + type = "ingress" + from_port = aws_rds_cluster.main.port + to_port = aws_rds_cluster.main.port + protocol = "tcp" + cidr_blocks = var.allowed_cidr_blocks + security_group_id = join("", aws_security_group.main.*.id) +} + +##### +# RDS Aurora Resources +##### + +resource "random_password" "master_password" { + length = 12 + special = false +} + +resource "random_id" "snapshot_identifier" { + keepers = { + id = var.name_prefix + } + + byte_length = 4 +} + +resource "aws_db_subnet_group" "main" { + count = var.db_subnet_group_name == "" ? 1 : 0 + + name_prefix = "${var.name_prefix}-" + description = "DB Subnet Group For Aurora cluster ${var.name_prefix}" + subnet_ids = var.subnets + + tags = merge( + var.tags, + { + Name = "${var.name_prefix}" + } + ) +} + +resource "aws_rds_cluster" "main" { + global_cluster_identifier = var.global_cluster_identifier + cluster_identifier = var.name_prefix + replication_source_identifier = var.replication_source_identifier + + source_region = var.source_region + engine = var.engine + engine_mode = var.engine_mode + engine_version = var.engine_version + enable_http_endpoint = var.enable_http_endpoint + + kms_key_id = var.kms_key_id + + database_name = var.database_name + master_username = var.username + master_password = var.password == "" ? random_password.master_password.result : var.password + + final_snapshot_identifier = "${var.final_snapshot_identifier_prefix}-${var.name_prefix}-${random_id.snapshot_identifier.hex}" + skip_final_snapshot = var.skip_final_snapshot + snapshot_identifier = var.snapshot_identifier + copy_tags_to_snapshot = var.copy_tags_to_snapshot + + deletion_protection = var.deletion_protection + backup_retention_period = var.backup_retention_period + preferred_backup_window = var.preferred_backup_window + preferred_maintenance_window = var.preferred_cluster_maintenance_window + apply_immediately = var.apply_immediately + + port = var.port == "" ? var.engine == "aurora-postgresql" ? "5432" : "3306" : var.port + db_subnet_group_name = var.db_subnet_group_name == "" ? join("", aws_db_subnet_group.main.*.name) : var.db_subnet_group_name + vpc_security_group_ids = compact(concat(aws_security_group.main.*.id, var.vpc_security_group_ids)) + storage_encrypted = var.storage_encrypted + + db_cluster_parameter_group_name = var.db_cluster_parameter_group_name + iam_database_authentication_enabled = var.iam_database_authentication_enabled + + backtrack_window = (var.engine == "aurora-mysql" || var.engine == "aurora") && var.engine_mode != "serverless" ? var.backtrack_window : 0 + iam_roles = var.iam_roles + + enabled_cloudwatch_logs_exports = [for log in var.enabled_cloudwatch_logs_exports : log.name] + + dynamic "scaling_configuration" { + for_each = length(keys(var.scaling_configuration)) == 0 ? [] : [var.scaling_configuration] + + content { + auto_pause = lookup(scaling_configuration.value, "auto_pause", null) + max_capacity = lookup(scaling_configuration.value, "max_capacity", null) + min_capacity = lookup(scaling_configuration.value, "min_capacity", null) + seconds_until_auto_pause = lookup(scaling_configuration.value, "seconds_until_auto_pause", null) + timeout_action = lookup(scaling_configuration.value, "timeout_action", null) + } + } + + tags = var.tags + + lifecycle { + ignore_changes = [master_username, master_password] + } + + depends_on = [aws_cloudwatch_log_group.audit_log_group] +} + +resource "aws_rds_cluster_instance" "main" { + count = var.replica_scale_enabled ? var.replica_scale_min : var.replica_count + + identifier = try(var.instances_parameters[count.index].instance_name, "${var.name_prefix}-${count.index + 1}") + cluster_identifier = aws_rds_cluster.main.id + + engine = var.engine + engine_version = var.engine_version + instance_class = try(var.instances_parameters[count.index].instance_type, var.instance_type) + promotion_tier = try(var.instances_parameters[count.index].instance_promotion_tier, count.index + 1) + + publicly_accessible = var.publicly_accessible + + db_subnet_group_name = var.db_subnet_group_name == "" ? join("", aws_db_subnet_group.main.*.name) : var.db_subnet_group_name + db_parameter_group_name = var.db_parameter_group_name + + preferred_maintenance_window = var.preferred_instance_maintenance_window + apply_immediately = var.apply_immediately + + monitoring_role_arn = var.create_monitoring_role ? join("", aws_iam_role.rds_enhanced_monitoring.*.arn) : var.monitoring_role_arn + monitoring_interval = var.monitoring_interval + auto_minor_version_upgrade = var.auto_minor_version_upgrade + performance_insights_enabled = var.performance_insights_enabled + performance_insights_kms_key_id = var.performance_insights_kms_key_id + ca_cert_identifier = var.ca_cert_identifier + + tags = var.tags +} + +##### +# Enhanced monitoring IAM +##### +resource "aws_iam_role" "rds_enhanced_monitoring" { + count = var.create_monitoring_role && var.monitoring_interval > 0 ? 1 : 0 + + name_prefix = "${var.name_prefix}-rds-" + + assume_role_policy = < 0 ? 1 : 0 + + role = join("", aws_iam_role.rds_enhanced_monitoring.*.name) + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" +} + +resource "aws_cloudwatch_log_group" "audit_log_group" { + count = length(var.enabled_cloudwatch_logs_exports) > 0 ? length(var.enabled_cloudwatch_logs_exports) : 0 + + name = "/aws/rds/cluster/${var.name_prefix}/${lookup(var.enabled_cloudwatch_logs_exports[count.index], "name")}" + + retention_in_days = lookup(var.enabled_cloudwatch_logs_exports[count.index], "retention_in_days", null) + kms_key_id = lookup(var.enabled_cloudwatch_logs_exports[count.index], "kms_key_id", null) + tags = var.tags +} + +##### +# RDS Read Replicas Scaling +##### +resource "aws_appautoscaling_target" "read_replica" { + count = var.replica_scale_enabled ? 1 : 0 + + max_capacity = var.replica_scale_max + min_capacity = var.replica_scale_min + resource_id = "cluster:${aws_rds_cluster.main.cluster_identifier}" + scalable_dimension = "rds:cluster:ReadReplicaCount" + service_namespace = "rds" +} + +resource "aws_appautoscaling_policy" "read_replica" { + count = var.replica_scale_enabled ? 1 : 0 + + name = "target-metric" + policy_type = "TargetTrackingScaling" + resource_id = "cluster:${aws_rds_cluster.main.cluster_identifier}" + scalable_dimension = "rds:cluster:ReadReplicaCount" + service_namespace = "rds" + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = var.predefined_metric_type + } + + scale_in_cooldown = var.replica_scale_in_cooldown + scale_out_cooldown = var.replica_scale_out_cooldown + target_value = var.predefined_metric_type == "RDSReaderAverageCPUUtilization" ? var.replica_scale_cpu : var.replica_scale_connections + } + + depends_on = [aws_appautoscaling_target.read_replica] +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..0509207 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,52 @@ +// aws_rds_cluster +output "rds_cluster_arn" { + description = "The ID of the aurora cluster" + value = aws_rds_cluster.main.arn +} + +output "rds_cluster_id" { + description = "The ID of the cluster" + value = aws_rds_cluster.main.id +} + +output "rds_cluster_resource_id" { + description = "The Resource ID of the cluster" + value = aws_rds_cluster.main.cluster_resource_id +} + +output "rds_cluster_endpoint" { + description = "The cluster endpoint" + value = aws_rds_cluster.main.endpoint +} + +output "rds_cluster_reader_endpoint" { + description = "The cluster reader endpoint" + value = aws_rds_cluster.main.reader_endpoint +} + +output "rds_cluster_master_password" { + description = "The master password" + value = aws_rds_cluster.main.master_password + sensitive = true +} + +output "rds_cluster_port" { + description = "The port" + value = aws_rds_cluster.main.port +} + +output "rds_cluster_master_username" { + description = "The master username" + value = aws_rds_cluster.main.master_username +} + +output "rds_cluster_instance_endpoints" { + description = "A list of all cluster instance endpoints" + value = aws_rds_cluster_instance.main.*.endpoint +} + +output "security_group_id" { + description = "The security group ID of the cluster" + value = join("", aws_security_group.main.*.id) +} + diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..b2b4576 --- /dev/null +++ b/variables.tf @@ -0,0 +1,351 @@ +variable "create_security_group" { + description = "Whether to create security group for RDS cluster" + type = bool + default = true +} + +variable "name_prefix" { + description = "Prefix Name used across all resources" + type = string +} + +variable "subnets" { + description = "List of subnet IDs to use" + type = list(string) + default = [] +} + +variable "replica_count" { + description = "Number of reader nodes to create. If `replica_scale_enable` is `true`, the value of `replica_scale_min` is used instead." + default = 1 +} + +variable "allowed_security_groups" { + description = "A list of Security Group ID's to allow access to." + type = list(string) + default = [] +} + +variable "allowed_cidr_blocks" { + description = "A list of CIDR blocks which are allowed to access the database" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "instance_type" { + description = "Instance type to use" + type = string +} + +variable "publicly_accessible" { + description = "Whether the DB should have a public IP address" + type = bool + default = false +} + +variable "database_name" { + description = "Name for an automatically created database on cluster creation" + type = string + default = "" +} + +variable "username" { + description = "Master DB username" + type = string + default = "root" +} + +variable "password" { + description = "Master DB password" + type = string + default = "" +} + +variable "final_snapshot_identifier_prefix" { + description = "The prefix name to use when creating a final snapshot on cluster destroy, appends a random 8 digits to name to ensure it's unique too." + type = string + default = "final" +} + +variable "skip_final_snapshot" { + description = "Should a final snapshot be created on cluster destroy" + type = bool + default = false +} + +variable "deletion_protection" { + description = "If the DB instance should have deletion protection enabled" + type = bool + default = false +} + +variable "backup_retention_period" { + description = "How long to keep backups for (in days)" + type = number + default = 7 +} + +variable "preferred_backup_window" { + description = "When to perform DB backups" + type = string + default = "02:00-03:00" +} + +variable "port" { + description = "The port on which to accept connections" + type = string + default = "" +} + +variable "apply_immediately" { + description = "Determines whether or not any DB modifications are applied immediately, or during the maintenance window" + type = bool + default = false +} + +variable "monitoring_interval" { + description = "The interval (seconds) between points when Enhanced Monitoring metrics are collected. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60." + type = number + default = 0 +} + +variable "auto_minor_version_upgrade" { + description = "Determines whether minor engine upgrades will be performed automatically in the maintenance window" + type = bool + default = true +} + +variable "db_parameter_group_name" { + description = "The name of a DB parameter group to use" + type = string + default = null +} + +variable "db_cluster_parameter_group_name" { + description = "The name of a DB Cluster parameter group to use" + type = string + default = null +} + +variable "scaling_configuration" { + description = "Map of nested attributes with scaling properties. Only valid when engine_mode is set to `serverless`" + type = map(string) + default = {} +} + +variable "snapshot_identifier" { + description = "DB snapshot to create this database from" + type = string + default = "" +} + +variable "storage_encrypted" { + description = "Specifies whether the underlying storage layer should be encrypted" + type = bool + default = true +} + +variable "kms_key_id" { + description = "The ARN for the KMS encryption key if one is set to the cluster." + type = string + default = "" +} + +variable "engine" { + description = "Aurora database engine type, currently aurora, aurora-mysql or aurora-postgresql" + type = string + default = "aurora" +} + +variable "engine_version" { + description = "Aurora database engine version." + type = string + default = "5.7.12" +} + +variable "enable_http_endpoint" { + description = "Whether or not to enable the Data API for a serverless Aurora database engine." + type = bool + default = false +} + +variable "replica_scale_enabled" { + description = "Whether to enable autoscaling for RDS Aurora (MySQL) read replicas" + type = bool + default = false +} + +variable "replica_scale_max" { + description = "Maximum number of replicas to allow scaling for" + type = number + default = 0 +} + +variable "replica_scale_min" { + description = "Minimum number of replicas to allow scaling for" + type = number + default = 2 +} + +variable "replica_scale_cpu" { + description = "CPU usage to trigger autoscaling at" + type = number + default = 70 +} + +variable "replica_scale_connections" { + description = "Average number of connections to trigger autoscaling at. Default value is 70% of db.r4.large's default max_connections" + type = number + default = 700 +} + +variable "replica_scale_in_cooldown" { + description = "Cooldown in seconds before allowing further scaling operations after a scale in" + type = number + default = 300 +} + +variable "replica_scale_out_cooldown" { + description = "Cooldown in seconds before allowing further scaling operations after a scale out" + type = number + default = 300 +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} + +variable "performance_insights_enabled" { + description = "Specifies whether Performance Insights is enabled or not." + type = bool + default = false +} + +variable "performance_insights_kms_key_id" { + description = "The ARN for the KMS key to encrypt Performance Insights data." + type = string + default = "" +} + +variable "iam_database_authentication_enabled" { + description = "Specifies whether IAM Database authentication should be enabled or not. Not all versions and instances are supported. Refer to the AWS documentation to see which versions are supported." + type = bool + default = true +} + +variable "enabled_cloudwatch_logs_exports" { + description = "List of object which define log types to export to cloudwatch. See in examples." + type = list + default = [] +} + +variable "global_cluster_identifier" { + description = "The global cluster identifier specified on aws_rds_global_cluster" + type = string + default = "" +} + +variable "engine_mode" { + description = "The database engine mode. Valid values: global, parallelquery, provisioned, serverless." + type = string + default = "provisioned" +} + +variable "replication_source_identifier" { + description = "ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica." + default = "" +} + +variable "source_region" { + description = "The source region for an encrypted replica DB cluster." + default = "" +} + +variable "vpc_security_group_ids" { + description = "List of VPC security groups to associate to the cluster in addition to the SG that can be created in this module." + type = list(string) + default = [] +} + +variable "db_subnet_group_name" { + description = "The existing subnet group name to use" + type = string + default = "" +} + +variable "predefined_metric_type" { + description = "The metric type to scale on. Valid values are RDSReaderAverageCPUUtilization and RDSReaderAverageDatabaseConnections." + default = "RDSReaderAverageCPUUtilization" +} + +variable "backtrack_window" { + description = "The target backtrack window, in seconds. Only available for aurora engine currently. To disable backtracking, set this value to 0. Defaults to 0. Must be between 0 and 259200 (72 hours)" + type = number + default = 0 +} + +variable "copy_tags_to_snapshot" { + description = "Copy all Cluster tags to snapshots." + type = bool + default = false +} + +variable "iam_roles" { + description = "A List of ARNs for the IAM roles to associate to the RDS Cluster." + type = list(string) + default = [] +} + +variable "security_group_description" { + description = "The description of the security group. If value is set to empty string it will contain cluster name in the description." + type = string + default = "" +} + +variable "ca_cert_identifier" { + description = "The identifier of the CA certificate for the DB instance." + type = string + default = "rds-ca-2019" +} + +variable "instances_parameters" { + description = "Individual settings for instances." + default = [] +} + +variable "preferred_cluster_maintenance_window" { + description = "When to perform maintenance on the cluster" + type = string + default = "sun:05:00-sun:06:00" +} + +variable "preferred_instance_maintenance_window" { + description = "When to perform maintenance on the instances" + type = string + default = "sun:05:00-sun:06:00" +} + +variable "permissions_boundary" { + description = "The ARN of the policy that is used to set the permissions boundary for the role." + type = string + default = null +} + +variable "monitoring_role_arn" { + description = "IAM role for RDS to send enhanced monitoring metrics to CloudWatch" + type = string + default = "" +} + +variable "create_monitoring_role" { + description = "Whether to create the IAM role for RDS enhanced monitoring" + type = bool + default = true +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..5d78bc3 --- /dev/null +++ b/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = "~> 0.12.6" + + required_providers { + aws = "~> 2.45" + random = "~> 2.2" + } +}