diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 924f8c5..96a2e14 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,8 +11,6 @@ repos: hooks: - id: terraform_fmt - id: terragrunt_fmt - # per module tflint config - - id: terraform_tflint # global tflint config - id: terraform_tflint args: diff --git a/.tflint.hcl b/.tflint.hcl index 427121c..062eb57 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -2,3 +2,11 @@ plugin "terraform" { enabled = true preset = "recommended" } + +rule "terraform_required_providers" { + enabled = false +} + +rule "terraform_required_version" { + enabled = false +} diff --git a/modules/aws-acm/.gitignore b/modules/aws-acm/.gitignore new file mode 100644 index 0000000..397af32 --- /dev/null +++ b/modules/aws-acm/.gitignore @@ -0,0 +1,29 @@ +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# 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 + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/modules/aws-acm/README.md b/modules/aws-acm/README.md new file mode 100644 index 0000000..7fcc41d --- /dev/null +++ b/modules/aws-acm/README.md @@ -0,0 +1,220 @@ +# AWS Certificate Manager (ACM) Terraform module + +Terraform module which creates ACM certificates and validates them using Route53 DNS (recommended) or e-mail. + +## Usage with Route53 DNS validation (recommended) + +```hcl +module "acm" { + source = "terraform-modules/acm/aws" + version = "~> 4.0" + + domain_name = "my-domain.com" + zone_id = "Z2ES7B9AZ6SHAE" + + subject_alternative_names = [ + "*.my-domain.com", + "app.sub.my-domain.com", + ] + + wait_for_validation = true + + tags = { + Name = "my-domain.com" + } +} +``` + +## Usage with external DNS validation (e.g. CloudFlare) + +```hcl +module "acm" { + source = "terraform-modules/acm/aws" + version = "~> 4.0" + + domain_name = "weekly.tf" + zone_id = "b7d259641bf30b89887c943ffc9d2138" + + subject_alternative_names = [ + "*.weekly.tf", + ] + + create_route53_records = false + validation_record_fqdns = [ + "_689571ee9a5f9ec307c512c5d851e25a.weekly.tf", + ] + + tags = { + Name = "weekly.tf" + } +} + +``` + +## [Usage with CloudFront](https://aws.amazon.com/premiumsupport/knowledge-center/install-ssl-cloudfront/) + +```hcl +# CloudFront supports US East (N. Virginia) Region only. +provider "aws" { + alias = "us-east-1" + region = "us-east-1" +} + +module "acm" { + source = "terraform-modules/acm/aws" + + providers = { + aws = aws.us-east-1 + } + + domain_name = "my-domain.com" + zone_id = "Z266PL4W4W6MSG" + + wait_for_validation = true + + tags = { + Name = "my-domain.com" + } +} +``` + +## Usage with Route53 DNS validation and separate AWS providers + +```hcl +provider "aws" { + alias = "acm" +} + +provider "aws" { + alias = "route53" +} + +module "acm" { + source = "terraform-modules/acm/aws" + version = "~> 4.0" + + providers = { + aws = aws.acm + } + + domain_name = "my-domain.com" + + subject_alternative_names = [ + "*.my-domain.com", + "app.sub.my-domain.com", + ] + + create_route53_records = false + validation_record_fqdns = module.route53_records.validation_route53_record_fqdns +} + +module "route53_records" { + source = "terraform-modules/acm/aws" + version = "~> 4.0" + + providers = { + aws = aws.route53 + } + + create_certificate = false + create_route53_records_only = true + + distinct_domain_names = module.acm.distinct_domain_names + zone_id = "Z266PL4W4W6MSG" + + acm_certificate_domain_validation_options = module.acm.acm_certificate_domain_validation_options +} +``` + +## Examples + +- [Complete example with DNS validation (recommended)](https://github.com/ToggTrumore/terraform-modules/terraform-aws-acm/tree/main/examples/complete-dns-validation) +- [Complete example with DNS validation via external DNS provider (CloudFlare)](https://github.com/ToggTrumore/terraform-modules/terraform-aws-acm/tree/main/examples/complete-dns-validation-with-cloudflare) +- [Complete example with EMAIL validation](https://github.com/ToggTrumore/terraform-modules/terraform-aws-acm/tree/main/examples/complete-email-validation) +- [Complete example with EMAIL validation and validation domain override](https://github.com/ToggTrumore/terraform-modules/terraform-aws-acm/tree/main/examples/complete-email-validation-with-validation-domain) + +## Conditional creation and validation + +Sometimes you need to have a way to create ACM certificate conditionally but Terraform does not allow to use `count` inside `module` block, so the solution is to specify argument `create_certificate`. + +```hcl +module "acm" { + source = "terraform-modules/acm/aws" + + create_certificate = false + # ... omitted +} +``` + +Similarly, to disable DNS validation of ACM certificate: + +```hcl +module "acm" { + source = "terraform-aws-modules/acm/aws" + + validate_certificate = false + # ... omitted +} +``` + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.12.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.12.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_acm_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource | +| [aws_acm_certificate_validation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource | +| [aws_route53_record.validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acm\_certificate\_domain\_validation\_options](#input\_acm\_certificate\_domain\_validation\_options) | A list of domain\_validation\_options created by the ACM certificate to create required Route53 records from it (used when create\_route53\_records\_only is set to true) | `any` | `{}` | no | +| [certificate\_transparency\_logging\_preference](#input\_certificate\_transparency\_logging\_preference) | Specifies whether certificate details should be added to a certificate transparency log | `bool` | `true` | no | +| [create\_certificate](#input\_create\_certificate) | Whether to create ACM certificate | `bool` | `true` | no | +| [create\_route53\_records](#input\_create\_route53\_records) | When validation is set to DNS, define whether to create the DNS records internally via Route53 or externally using any DNS provider | `bool` | `true` | no | +| [create\_route53\_records\_only](#input\_create\_route53\_records\_only) | Whether to create only Route53 records (e.g. using separate AWS provider) | `bool` | `false` | no | +| [distinct\_domain\_names](#input\_distinct\_domain\_names) | List of distinct domains and SANs (used when create\_route53\_records\_only is set to true) | `list(string)` | `[]` | no | +| [dns\_ttl](#input\_dns\_ttl) | The TTL of DNS recursive resolvers to cache information about this record. | `number` | `60` | no | +| [domain\_name](#input\_domain\_name) | A domain name for which the certificate should be issued | `string` | `""` | no | +| [subject\_alternative\_names](#input\_subject\_alternative\_names) | A list of domains that should be SANs in the issued certificate | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [validate\_certificate](#input\_validate\_certificate) | Whether to validate certificate by creating Route53 record | `bool` | `true` | no | +| [validation\_allow\_overwrite\_records](#input\_validation\_allow\_overwrite\_records) | Whether to allow overwrite of Route53 records | `bool` | `true` | no | +| [validation\_method](#input\_validation\_method) | Which method to use for validation. DNS or EMAIL are valid, NONE can be used for certificates that were imported into ACM and then into Terraform. | `string` | `"DNS"` | no | +| [validation\_option](#input\_validation\_option) | The domain name that you want ACM to use to send you validation emails. This domain name is the suffix of the email addresses that you want ACM to use. | `any` | `{}` | no | +| [validation\_record\_fqdns](#input\_validation\_record\_fqdns) | When validation is set to DNS and the DNS validation records are set externally, provide the fqdns for the validation | `list(string)` | `[]` | no | +| [wait\_for\_validation](#input\_wait\_for\_validation) | Whether to wait for the validation to complete | `bool` | `true` | no | +| [zone\_id](#input\_zone\_id) | The ID of the hosted zone to contain this record. Required when validating via Route53 | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_status](#output\_acm\_certificate\_status) | Status of the certificate. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | +| [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. | +| [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. | +| [validation\_route53\_record\_fqdns](#output\_validation\_route53\_record\_fqdns) | List of FQDNs built using the zone domain and name. | + diff --git a/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/README.md b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/README.md new file mode 100644 index 0000000..f97af83 --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/README.md @@ -0,0 +1,64 @@ +# Complete ACM example with external CloudFlare DNS validation + +Configuration in this directory creates an ACM certificate (valid for the domain name and wildcard) while the DNS validation is done via an external DNS provider. + +For this example CloudFlare DNS is used but any DNS provider could be used instead. + +This is a complete example which fits most of scenarios. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 2.53 | +| [cloudflare](#requirement\_cloudflare) | >= 3.4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [cloudflare](#provider\_cloudflare) | >= 3.4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [cloudflare_record.validation](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource | +| [cloudflare_zone.this](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zone) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_status](#output\_acm\_certificate\_status) | Status of the certificate. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | +| [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. | +| [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. | +| [validation\_route53\_record\_fqdns](#output\_validation\_route53\_record\_fqdns) | List of FQDNs built using the zone domain and name. | + diff --git a/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/main.tf b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/main.tf new file mode 100644 index 0000000..7e368c4 --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/main.tf @@ -0,0 +1,49 @@ +locals { + domain = "terraform-aws-modules.modules.tf" + + # Removing trailing dot from domain - just to be sure :) + domain_name = trimsuffix(local.domain, ".") +} + +module "acm" { + source = "../../" + + providers = { + aws.acm = aws, + aws.dns = aws + } + + domain_name = local.domain_name + zone_id = data.cloudflare_zone.this.id + + subject_alternative_names = [ + "*.alerts.${local.domain_name}", + "new.sub.${local.domain_name}", + "*.${local.domain_name}", + "alerts.${local.domain_name}", + ] + + create_route53_records = false + validation_record_fqdns = cloudflare_record.validation.*.hostname + + tags = { + Name = local.domain_name + } +} + +resource "cloudflare_record" "validation" { + count = length(module.acm.distinct_domain_names) + + zone_id = data.cloudflare_zone.this.id + name = element(module.acm.validation_domains, count.index)["resource_record_name"] + type = element(module.acm.validation_domains, count.index)["resource_record_type"] + value = trimsuffix(element(module.acm.validation_domains, count.index)["resource_record_value"], ".") + ttl = 60 + proxied = false + + allow_overwrite = true +} + +data "cloudflare_zone" "this" { + name = local.domain_name +} diff --git a/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/outputs.tf b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/outputs.tf new file mode 100644 index 0000000..83083be --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/outputs.tf @@ -0,0 +1,34 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = module.acm.acm_certificate_arn +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = module.acm.acm_certificate_domain_validation_options +} + +output "acm_certificate_status" { + description = "Status of the certificate." + value = module.acm.acm_certificate_status +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = module.acm.acm_certificate_validation_emails +} + +output "validation_route53_record_fqdns" { + description = "List of FQDNs built using the zone domain and name." + value = module.acm.validation_route53_record_fqdns +} + +output "distinct_domain_names" { + description = "List of distinct domains names used for the validation." + value = module.acm.distinct_domain_names +} + +output "validation_domains" { + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + value = module.acm.validation_domains +} diff --git a/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/variables.tf b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/versions.tf b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/versions.tf new file mode 100644 index 0000000..ec2f77a --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation-with-cloudflare/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.53" + } + cloudflare = { + source = "cloudflare/cloudflare" + version = ">= 3.4.0" + } + } +} diff --git a/modules/aws-acm/examples/complete-dns-validation/README.md b/modules/aws-acm/examples/complete-dns-validation/README.md new file mode 100644 index 0000000..e7fa8b5 --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation/README.md @@ -0,0 +1,65 @@ +# Complete ACM example with Route53 DNS validation + +Configuration in this directory creates new Route53 zone and ACM certificate (valid for the domain name and wildcard) with one (default) or two instances of AWS providers (one to manage ACM resources, another to manage Route53 records). + +Also, ACM certificate is being validate using DNS method. + +This is a complete example which fits most of scenarios. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 2.53 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.53 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | ../../ | n/a | +| [acm\_only](#module\_acm\_only) | ../../ | n/a | +| [route53\_records\_only](#module\_route53\_records\_only) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_status](#output\_acm\_certificate\_status) | Status of the certificate. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | +| [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. | +| [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. | +| [validation\_route53\_record\_fqdns](#output\_validation\_route53\_record\_fqdns) | List of FQDNs built using the zone domain and name. | + diff --git a/modules/aws-acm/examples/complete-dns-validation/main.tf b/modules/aws-acm/examples/complete-dns-validation/main.tf new file mode 100644 index 0000000..928e9d3 --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation/main.tf @@ -0,0 +1,101 @@ +locals { + # Use existing (via data source) or create new zone (will fail validation, if zone is not reachable) + use_existing_route53_zone = true + + domain = "terraform-aws-modules.modules.tf" + + # Removing trailing dot from domain - just to be sure :) + domain_name = trimsuffix(local.domain, ".") + + zone_id = coalescelist(data.aws_route53_zone.this.*.zone_id, aws_route53_zone.this.*.zone_id)[0] +} + +########################################################## +# Example 1 (default case): +# Using one AWS provider for both ACM and Route53 records +########################################################## + +data "aws_route53_zone" "this" { + count = local.use_existing_route53_zone ? 1 : 0 + + name = local.domain_name + private_zone = false +} + +resource "aws_route53_zone" "this" { + count = !local.use_existing_route53_zone ? 1 : 0 + + name = local.domain_name +} + +module "acm" { + source = "../../" + + providers = { + aws.acm = aws, + aws.dns = aws + } + + domain_name = local.domain_name + zone_id = local.zone_id + + subject_alternative_names = [ + "*.alerts.${local.domain_name}", + "new.sub.${local.domain_name}", + "*.${local.domain_name}", + "alerts.${local.domain_name}", + ] + + tags = { + Name = local.domain_name + } +} + +################################################################ +# Example 2: +# Using separate AWS providers for ACM and Route53 records. +# Useful when these resources belong to different AWS accounts. +################################################################ + +provider "aws" { + alias = "route53" +} + +provider "aws" { + alias = "acm" +} + +module "acm_only" { + source = "../../" + + providers = { + aws = aws.acm + } + + domain_name = local.domain_name + subject_alternative_names = [ + "*.alerts.separated.${local.domain_name}", + "new.sub.separated.${local.domain_name}", + "*.separated.${local.domain_name}", + "alerts.separated.${local.domain_name}", + ] + + create_route53_records = false + validation_record_fqdns = module.route53_records_only.validation_route53_record_fqdns +} + +module "route53_records_only" { + source = "../../" + + providers = { + aws = aws.route53 + } + + create_certificate = false + create_route53_records_only = true + + zone_id = local.zone_id + distinct_domain_names = module.acm_only.distinct_domain_names + + acm_certificate_domain_validation_options = module.acm_only.acm_certificate_domain_validation_options +} diff --git a/modules/aws-acm/examples/complete-dns-validation/outputs.tf b/modules/aws-acm/examples/complete-dns-validation/outputs.tf new file mode 100644 index 0000000..83083be --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation/outputs.tf @@ -0,0 +1,34 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = module.acm.acm_certificate_arn +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = module.acm.acm_certificate_domain_validation_options +} + +output "acm_certificate_status" { + description = "Status of the certificate." + value = module.acm.acm_certificate_status +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = module.acm.acm_certificate_validation_emails +} + +output "validation_route53_record_fqdns" { + description = "List of FQDNs built using the zone domain and name." + value = module.acm.validation_route53_record_fqdns +} + +output "distinct_domain_names" { + description = "List of distinct domains names used for the validation." + value = module.acm.distinct_domain_names +} + +output "validation_domains" { + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + value = module.acm.validation_domains +} diff --git a/modules/aws-acm/examples/complete-dns-validation/variables.tf b/modules/aws-acm/examples/complete-dns-validation/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-acm/examples/complete-dns-validation/versions.tf b/modules/aws-acm/examples/complete-dns-validation/versions.tf new file mode 100644 index 0000000..25f85e5 --- /dev/null +++ b/modules/aws-acm/examples/complete-dns-validation/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.53" + } + } +} diff --git a/modules/aws-acm/examples/complete-email-validation-with-validation-domain/README.md b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/README.md new file mode 100644 index 0000000..a51588c --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/README.md @@ -0,0 +1,70 @@ +# Complete ACM example with EMAIL validation with validation_domain configured + +Configuration in this directory creates new Route53 zone and ACM certificate (valid for the domain name and wildcard). + +ACM certificate will be created with EMAIL validation method, which means that emails will be send to domain owners and it is not possible to automate using Terraform! +The validation domain option is set, which overrides the domain to which validation emails will be sent. + +If you want to use EMAIL validation method make sure that you have access to at least one of these emails in your domain: + +``` +hostmaster@VALIDATION_DOMAIN +postmaster@VALIDATION_DOMAIN +admin@VALIDATION_DOMAIN +administrator@VALIDATION_DOMAIN +webmaster@VALIDATION_DOMAIN +``` + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan -var 'domain_name=foo.bar.com' -var 'validation_domain=bar.com' +$ terraform apply -var 'domain_name=foo.bar.com' -var 'validation_domain=bar.com' +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.12.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.12.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [domain\_name](#input\_domain\_name) | Domain name to use as Route53 zone and ACM certificate | `string` | n/a | yes | +| [validation\_domain](#input\_validation\_domain) | Domain name to use for verifying var.domain\_name | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | + diff --git a/modules/aws-acm/examples/complete-email-validation-with-validation-domain/main.tf b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/main.tf new file mode 100644 index 0000000..9a79ac6 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/main.tf @@ -0,0 +1,27 @@ +resource "aws_route53_zone" "this" { + name = var.domain_name +} + +module "acm" { + source = "../../" + + domain_name = var.domain_name + zone_id = aws_route53_zone.this.zone_id + + # The key is the domain name which you want to change the validation domain for. + # Validation emails will be send to a fixed list of recipients: + # admin@VALIDATION_DOMAIN, administrator@VALIDATION_DOMAIN, hostmaster@VALIDATION_DOMAIN, postmaster@VALIDATION_DOMAIN, webmaster@VALIDATION_DOMAIN + # validation_domain has to be a top-level domain of the actual domain + validation_option = { + (var.domain_name) = { + validation_domain = var.validation_domain + } + } + + validation_method = "EMAIL" + wait_for_validation = false + + tags = { + Name = var.domain_name + } +} diff --git a/modules/aws-acm/examples/complete-email-validation-with-validation-domain/outputs.tf b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/outputs.tf new file mode 100644 index 0000000..edf4f63 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/outputs.tf @@ -0,0 +1,14 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = module.acm.acm_certificate_arn +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = module.acm.acm_certificate_domain_validation_options +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = module.acm.acm_certificate_validation_emails +} diff --git a/modules/aws-acm/examples/complete-email-validation-with-validation-domain/variables.tf b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/variables.tf new file mode 100644 index 0000000..8e22c52 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/variables.tf @@ -0,0 +1,9 @@ +variable "domain_name" { + description = "Domain name to use as Route53 zone and ACM certificate" + type = string +} + +variable "validation_domain" { + description = "Domain name to use for verifying var.domain_name" + type = string +} diff --git a/modules/aws-acm/examples/complete-email-validation-with-validation-domain/versions.tf b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/versions.tf new file mode 100644 index 0000000..e76924e --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation-with-validation-domain/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.12.0" + } + } +} diff --git a/modules/aws-acm/examples/complete-email-validation/README.md b/modules/aws-acm/examples/complete-email-validation/README.md new file mode 100644 index 0000000..c89cd19 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation/README.md @@ -0,0 +1,74 @@ +# Complete ACM example with EMAIL validation + +Configuration in this directory creates new Route53 zone and ACM certificate (valid for the domain name and wildcard). + +ACM certificate will be created with EMAIL validation method, which means that emails will be send to domain owners and it is not possible to automate using Terraform! + +If you want to use EMAIL validation method make sure that you have access to at least one of these emails in your domain: + +``` +hostmaster@... +postmaster@... +admin@... +administrator@... +webmaster@... +hostmaster@... +postmaster@... +admin@... +administrator@... +webmaster@... +``` + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 2.53 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.53 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [domain\_name](#input\_domain\_name) | Domain name to use as Route53 zone and ACM certificate | `string` | `"my-domain-name2.com"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_status](#output\_acm\_certificate\_status) | Status of the certificate. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | + diff --git a/modules/aws-acm/examples/complete-email-validation/main.tf b/modules/aws-acm/examples/complete-email-validation/main.tf new file mode 100644 index 0000000..ce4b1ec --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation/main.tf @@ -0,0 +1,20 @@ +resource "aws_route53_zone" "this" { + name = var.domain_name +} + +module "acm" { + source = "../../" + + domain_name = var.domain_name + zone_id = aws_route53_zone.this.zone_id + + subject_alternative_names = [ + "*.${var.domain_name}", + ] + + validation_method = "EMAIL" + + tags = { + Name = var.domain_name + } +} diff --git a/modules/aws-acm/examples/complete-email-validation/outputs.tf b/modules/aws-acm/examples/complete-email-validation/outputs.tf new file mode 100644 index 0000000..193e5e9 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation/outputs.tf @@ -0,0 +1,19 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = module.acm.acm_certificate_arn +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = module.acm.acm_certificate_domain_validation_options +} + +output "acm_certificate_status" { + description = "Status of the certificate." + value = module.acm.acm_certificate_status +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = module.acm.acm_certificate_validation_emails +} diff --git a/modules/aws-acm/examples/complete-email-validation/variables.tf b/modules/aws-acm/examples/complete-email-validation/variables.tf new file mode 100644 index 0000000..b3a6a45 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation/variables.tf @@ -0,0 +1,5 @@ +variable "domain_name" { + description = "Domain name to use as Route53 zone and ACM certificate" + type = string + default = "my-domain-name2.com" +} diff --git a/modules/aws-acm/examples/complete-email-validation/versions.tf b/modules/aws-acm/examples/complete-email-validation/versions.tf new file mode 100644 index 0000000..25f85e5 --- /dev/null +++ b/modules/aws-acm/examples/complete-email-validation/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.53" + } + } +} diff --git a/modules/aws-acm/main.tf b/modules/aws-acm/main.tf new file mode 100644 index 0000000..0286cd1 --- /dev/null +++ b/modules/aws-acm/main.tf @@ -0,0 +1,79 @@ +locals { + create_certificate = var.create_certificate + create_route53_records_only = var.create_route53_records_only + + # Get distinct list of domains and SANs + distinct_domain_names = coalescelist(var.distinct_domain_names, distinct( + [for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")] + )) + + # Get the list of distinct domain_validation_options, with wildcard + # domain names replaced by the domain name + validation_domains = local.create_certificate || local.create_route53_records_only ? distinct( + [for k, v in try(aws_acm_certificate.this[0].domain_validation_options, var.acm_certificate_domain_validation_options) : merge( + tomap(v), { domain_name = replace(v.domain_name, "*.", "") } + )] + ) : [] +} + +resource "aws_acm_certificate" "this" { + count = local.create_certificate ? 1 : 0 + provider = aws.aws_acm + + domain_name = var.domain_name + subject_alternative_names = var.subject_alternative_names + validation_method = var.validation_method + + options { + certificate_transparency_logging_preference = var.certificate_transparency_logging_preference ? "ENABLED" : "DISABLED" + } + + dynamic "validation_option" { + for_each = var.validation_option + + content { + domain_name = try(validation_option.value["domain_name"], validation_option.key) + validation_domain = validation_option.value["validation_domain"] + } + } + + tags = var.tags + + lifecycle { + create_before_destroy = true + } +} + +data "aws_route53_zone" "selected" { + count = (local.create_certificate || local.create_route53_records_only) && var.validation_method == "DNS" && var.create_route53_records && (var.validate_certificate || local.create_route53_records_only) ? length(local.distinct_domain_names) : 0 + provider = aws.shared_infra + name = var.domain_name + private_zone = false +} + +resource "aws_route53_record" "validation" { + provider = aws.shared_infra + count = (local.create_certificate || local.create_route53_records_only) && var.validation_method == "DNS" && var.create_route53_records && (var.validate_certificate || local.create_route53_records_only) ? length(local.distinct_domain_names) : 0 + + zone_id = data.aws_route53_zone.selected[0].zone_id + name = element(local.validation_domains, count.index)["resource_record_name"] + type = element(local.validation_domains, count.index)["resource_record_type"] + ttl = var.dns_ttl + + records = [ + element(local.validation_domains, count.index)["resource_record_value"] + ] + + allow_overwrite = var.validation_allow_overwrite_records + + depends_on = [aws_acm_certificate.this] +} + +resource "aws_acm_certificate_validation" "this" { + count = local.create_certificate && var.validation_method != "NONE" && var.validate_certificate && var.wait_for_validation ? 1 : 0 + provider = aws.aws_acm + + certificate_arn = aws_acm_certificate.this[0].arn + + validation_record_fqdns = flatten([aws_route53_record.validation.*.fqdn, var.validation_record_fqdns]) +} diff --git a/modules/aws-acm/outputs.tf b/modules/aws-acm/outputs.tf new file mode 100644 index 0000000..b4b3154 --- /dev/null +++ b/modules/aws-acm/outputs.tf @@ -0,0 +1,34 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = element(concat(aws_acm_certificate_validation.this.*.certificate_arn, aws_acm_certificate.this.*.arn, [""]), 0) +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = flatten(aws_acm_certificate.this.*.domain_validation_options) +} + +output "acm_certificate_status" { + description = "Status of the certificate." + value = element(concat(aws_acm_certificate.this.*.status, [""]), 0) +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = flatten(aws_acm_certificate.this.*.validation_emails) +} + +output "validation_route53_record_fqdns" { + description = "List of FQDNs built using the zone domain and name." + value = aws_route53_record.validation.*.fqdn +} + +output "distinct_domain_names" { + description = "List of distinct domains names used for the validation." + value = local.distinct_domain_names +} + +output "validation_domains" { + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + value = local.validation_domains +} diff --git a/modules/aws-acm/variables.tf b/modules/aws-acm/variables.tf new file mode 100644 index 0000000..fb97417 --- /dev/null +++ b/modules/aws-acm/variables.tf @@ -0,0 +1,106 @@ +variable "create_certificate" { + description = "Whether to create ACM certificate" + type = bool + default = true +} + +variable "create_route53_records_only" { + description = "Whether to create only Route53 records (e.g. using separate AWS provider)" + type = bool + default = false +} + +variable "validate_certificate" { + description = "Whether to validate certificate by creating Route53 record" + type = bool + default = true +} + +variable "validation_allow_overwrite_records" { + description = "Whether to allow overwrite of Route53 records" + type = bool + default = true +} + +variable "wait_for_validation" { + description = "Whether to wait for the validation to complete" + type = bool + default = true +} + +variable "certificate_transparency_logging_preference" { + description = "Specifies whether certificate details should be added to a certificate transparency log" + type = bool + default = true +} + +variable "domain_name" { + description = "A domain name for which the certificate should be issued" + type = string + default = "" +} + +variable "subject_alternative_names" { + description = "A list of domains that should be SANs in the issued certificate" + type = list(string) + default = [] +} + +variable "validation_method" { + description = "Which method to use for validation. DNS or EMAIL are valid, NONE can be used for certificates that were imported into ACM and then into Terraform." + type = string + default = "DNS" + + validation { + condition = contains(["DNS", "EMAIL", "NONE"], var.validation_method) + error_message = "Valid values are DNS, EMAIL or NONE." + } +} + +variable "validation_option" { + description = "The domain name that you want ACM to use to send you validation emails. This domain name is the suffix of the email addresses that you want ACM to use." + type = any + default = {} +} + +variable "create_route53_records" { + description = "When validation is set to DNS, define whether to create the DNS records internally via Route53 or externally using any DNS provider" + type = bool + default = true +} + +variable "validation_record_fqdns" { + description = "When validation is set to DNS and the DNS validation records are set externally, provide the fqdns for the validation" + type = list(string) + default = [] +} + +variable "zone_id" { + description = "The ID of the hosted zone to contain this record. Required when validating via Route53" + type = string + default = "" +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "dns_ttl" { + description = "The TTL of DNS recursive resolvers to cache information about this record." + type = number + default = 60 +} + +variable "acm_certificate_domain_validation_options" { + description = "A list of domain_validation_options created by the ACM certificate to create required Route53 records from it (used when create_route53_records_only is set to true)" + type = any + default = {} +} + +variable "distinct_domain_names" { + description = "List of distinct domains and SANs (used when create_route53_records_only is set to true)" + type = list(string) + default = [] +} diff --git a/modules/aws-acm/versions.tf b/modules/aws-acm/versions.tf new file mode 100644 index 0000000..e76924e --- /dev/null +++ b/modules/aws-acm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.12.0" + } + } +} diff --git a/modules/aws-acm/wrappers/README.md b/modules/aws-acm/wrappers/README.md new file mode 100644 index 0000000..6d994ce --- /dev/null +++ b/modules/aws-acm/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/acm/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-acm.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/acm/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-acm/wrappers/main.tf b/modules/aws-acm/wrappers/main.tf new file mode 100644 index 0000000..159282f --- /dev/null +++ b/modules/aws-acm/wrappers/main.tf @@ -0,0 +1,23 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create_certificate = try(each.value.create_certificate, var.defaults.create_certificate, true) + create_route53_records_only = try(each.value.create_route53_records_only, var.defaults.create_route53_records_only, false) + validate_certificate = try(each.value.validate_certificate, var.defaults.validate_certificate, true) + validation_allow_overwrite_records = try(each.value.validation_allow_overwrite_records, var.defaults.validation_allow_overwrite_records, true) + wait_for_validation = try(each.value.wait_for_validation, var.defaults.wait_for_validation, true) + certificate_transparency_logging_preference = try(each.value.certificate_transparency_logging_preference, var.defaults.certificate_transparency_logging_preference, true) + domain_name = try(each.value.domain_name, var.defaults.domain_name, "") + subject_alternative_names = try(each.value.subject_alternative_names, var.defaults.subject_alternative_names, []) + validation_method = try(each.value.validation_method, var.defaults.validation_method, "DNS") + validation_option = try(each.value.validation_option, var.defaults.validation_option, {}) + create_route53_records = try(each.value.create_route53_records, var.defaults.create_route53_records, true) + validation_record_fqdns = try(each.value.validation_record_fqdns, var.defaults.validation_record_fqdns, []) + zone_id = try(each.value.zone_id, var.defaults.zone_id, "") + tags = try(each.value.tags, var.defaults.tags, {}) + dns_ttl = try(each.value.dns_ttl, var.defaults.dns_ttl, 60) + acm_certificate_domain_validation_options = try(each.value.acm_certificate_domain_validation_options, var.defaults.acm_certificate_domain_validation_options, {}) + distinct_domain_names = try(each.value.distinct_domain_names, var.defaults.distinct_domain_names, []) +} diff --git a/modules/aws-acm/wrappers/outputs.tf b/modules/aws-acm/wrappers/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-acm/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-acm/wrappers/variables.tf b/modules/aws-acm/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-acm/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-acm/wrappers/versions.tf b/modules/aws-acm/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-acm/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-alb-master/main.tf b/modules/aws-alb-master/main.tf new file mode 100644 index 0000000..92a64aa --- /dev/null +++ b/modules/aws-alb-master/main.tf @@ -0,0 +1,288 @@ +#------------------------------------------------------------------------------ +# S3 BUCKET - For access logs +#------------------------------------------------------------------------------ +data "aws_elb_service_account" "default" {} + +resource "random_string" "log_s3_name" { + count = var.enable_s3_logs ? 1 : 0 + length = 8 + numeric = true + special = false + upper = false +} + +module "lb_logs_s3" { + source = "../terraform-aws-s3" + count = var.enable_s3_logs ? 1 : 0 + + + bucket = "alb-log-bucket-${random_string.log_s3_name[0].result}" + acl = "log-delivery-write" + + # Allow deletion of non-empty bucket + force_destroy = true + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs + attach_cross_account_policy = false + +} + +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +resource "random_string" "lb_name" { + count = var.use_random_name_for_lb ? 1 : 0 + length = 32 + numeric = true + special = false +} + +resource "aws_lb" "lb" { + name = var.use_random_name_for_lb ? random_string.lb_name[0].result : substr("${var.name_prefix}-lb", 0, 31) + + internal = var.internal + load_balancer_type = "application" + drop_invalid_header_fields = var.drop_invalid_header_fields + subnets = var.internal ? var.private_subnets : var.public_subnets + idle_timeout = var.idle_timeout + enable_deletion_protection = var.enable_deletion_protection + enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing + enable_http2 = var.enable_http2 + ip_address_type = var.ip_address_type + security_groups = compact(var.security_groups) + + dynamic "access_logs" { + for_each = var.enable_s3_logs ? [1] : [] + content { + bucket = module.lb_logs_s3[0].s3_bucket_id + enabled = var.enable_s3_logs + } + } + + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-lb" + }, + ) +} + +resource "aws_wafv2_web_acl_association" "waf_association" { + count = var.waf_web_acl_arn != "" ? 1 : 0 + resource_arn = aws_lb.lb.arn + web_acl_arn = var.waf_web_acl_arn +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +resource "aws_lb_target_group" "lb_http_tgs" { + for_each = { + for name, config in var.http_ports : name => config + if lookup(config, "type", "") == "" || lookup(config, "type", "") == "forward" + } + name = "${var.name_prefix}-http-${each.value.target_group_port}" + port = each.value.target_group_port + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTP" : each.value.target_group_protocol + vpc_id = var.vpc_id + deregistration_delay = var.deregistration_delay + slow_start = var.slow_start + load_balancing_algorithm_type = var.load_balancing_algorithm_type + dynamic "stickiness" { + for_each = var.stickiness == null ? [] : [var.stickiness] + content { + type = stickiness.value.type + cookie_duration = stickiness.value.cookie_duration + enabled = stickiness.value.enabled + } + } + health_check { + enabled = var.target_group_health_check_enabled + interval = var.target_group_health_check_interval + path = var.target_group_health_check_path + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTP" : each.value.target_group_protocol + timeout = var.target_group_health_check_timeout + healthy_threshold = var.target_group_health_check_healthy_threshold + unhealthy_threshold = var.target_group_health_check_unhealthy_threshold + matcher = var.target_group_health_check_matcher + } + target_type = "ip" + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-http-${each.value.target_group_port}" + }, + ) + lifecycle { + create_before_destroy = true + } + depends_on = [aws_lb.lb] +} + +resource "aws_lb_target_group" "lb_https_tgs" { + for_each = { + for name, config in var.https_ports : name => config + if lookup(config, "type", "") == "" || lookup(config, "type", "") == "forward" + } + name = "${var.name_prefix}-https-${each.value.target_group_port}" + port = each.value.target_group_port + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTPS" : each.value.target_group_protocol + vpc_id = var.vpc_id + deregistration_delay = var.deregistration_delay + slow_start = var.slow_start + load_balancing_algorithm_type = var.load_balancing_algorithm_type + dynamic "stickiness" { + for_each = var.stickiness == null ? [] : [var.stickiness] + content { + type = stickiness.value.type + cookie_duration = stickiness.value.cookie_duration + enabled = stickiness.value.enabled + } + } + health_check { + enabled = var.target_group_health_check_enabled + interval = var.target_group_health_check_interval + path = var.target_group_health_check_path + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTPS" : each.value.target_group_protocol + timeout = var.target_group_health_check_timeout + healthy_threshold = var.target_group_health_check_healthy_threshold + unhealthy_threshold = var.target_group_health_check_unhealthy_threshold + matcher = var.target_group_health_check_matcher + } + target_type = "ip" + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-https-${each.value.target_group_port}" + }, + ) + lifecycle { + create_before_destroy = true + } + depends_on = [aws_lb.lb] +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Listeners +#------------------------------------------------------------------------------ +resource "aws_lb_listener" "lb_http_listeners" { + for_each = var.http_ports + load_balancer_arn = aws_lb.lb.arn + port = each.value.listener_port + protocol = "HTTP" + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "redirect" ? [1] : [] + content { + type = "redirect" + + redirect { + host = lookup(each.value, "host", "#{host}") + path = lookup(each.value, "path", "/#{path}") + port = lookup(each.value, "port", "#{port}") + protocol = lookup(each.value, "protocol", "#{protocol}") + query = lookup(each.value, "query", "#{query}") + status_code = lookup(each.value, "status_code", "HTTP_301") + } + } + } + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "fixed-response" ? [1] : [] + content { + type = "fixed-response" + + fixed_response { + content_type = lookup(each.value, "content_type", "text/plain") + message_body = lookup(each.value, "message_body", "Fixed response content") + status_code = lookup(each.value, "status_code", "200") + } + } + } + + # We fallback to using forward type action if type is not defined + dynamic "default_action" { + for_each = (lookup(each.value, "type", "") == "" || lookup(each.value, "type", "") == "forward") ? [1] : [] + content { + target_group_arn = aws_lb_target_group.lb_http_tgs[each.key].arn + type = "forward" + } + } + + tags = var.tags +} + +resource "aws_lb_listener" "lb_https_listeners" { + for_each = var.https_ports + load_balancer_arn = aws_lb.lb.arn + port = each.value.listener_port + protocol = "HTTPS" + ssl_policy = var.ssl_policy + certificate_arn = var.default_certificate_arn + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "redirect" ? [1] : [] + content { + type = "redirect" + + redirect { + host = lookup(each.value, "host", "#{host}") + path = lookup(each.value, "path", "/#{path}") + port = lookup(each.value, "port", "#{port}") + protocol = lookup(each.value, "protocol", "#{protocol}") + query = lookup(each.value, "query", "#{query}") + status_code = lookup(each.value, "status_code", "HTTP_301") + } + } + } + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "fixed-response" ? [1] : [] + content { + type = "fixed-response" + + fixed_response { + content_type = lookup(each.value, "content_type", "text/plain") + message_body = lookup(each.value, "message_body", "Fixed response content") + status_code = lookup(each.value, "status_code", "200") + } + } + } + + # We fallback to using forward type action if type is not defined + dynamic "default_action" { + for_each = (lookup(each.value, "type", "") == "" || lookup(each.value, "type", "") == "forward") ? [1] : [] + content { + target_group_arn = aws_lb_target_group.lb_https_tgs[each.key].arn + type = "forward" + } + } + + tags = var.tags +} + +locals { + list_maps_listener_certificate_arns = flatten([ + for cert_arn in var.additional_certificates_arn_for_https_listeners : [ + for listener in aws_lb_listener.lb_https_listeners : { + name = "${listener}-${cert_arn}" + listener_arn = listener.arn + certificate_arn = cert_arn + } + ] + ]) + + map_listener_certificate_arns = { + for obj in local.list_maps_listener_certificate_arns : obj.name => { + listener_arn = obj.listener_arn, + certificate_arn = obj.certificate_arn + } + } +} + +resource "aws_lb_listener_certificate" "additional_certificates_for_https_listeners" { + for_each = local.map_listener_certificate_arns + listener_arn = each.value.listener_arn + certificate_arn = each.value.certificate_arn +} diff --git a/modules/aws-alb-master/outputs.tf b/modules/aws-alb-master/outputs.tf new file mode 100644 index 0000000..f933d96 --- /dev/null +++ b/modules/aws-alb-master/outputs.tf @@ -0,0 +1,120 @@ +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +output "aws_lb_lb_id" { + description = "The ARN of the load balancer (matches arn)." + value = aws_lb.lb.id +} + +output "aws_lb_lb_arn" { + description = "The ARN of the load balancer (matches id)." + value = aws_lb.lb.arn +} + +output "aws_lb_lb_arn_suffix" { + description = "The ARN suffix for use with CloudWatch Metrics." + value = aws_lb.lb.arn_suffix +} + +output "aws_lb_lb_dns_name" { + description = "The DNS name of the load balancer." + value = aws_lb.lb.dns_name +} + +output "aws_lb_lb_zone_id" { + description = "The canonical hosted zone ID of the load balancer (to be used in a Route 53 Alias record)." + value = aws_lb.lb.zone_id +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +output "lb_http_tgs_ids" { + description = "List of HTTP Target Groups IDs" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.id] +} + +output "lb_http_tgs_arns" { + description = "List of HTTP Target Groups ARNs" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.arn] +} + +output "lb_http_tgs_names" { + description = "List of HTTP Target Groups Names" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.name] +} + +output "lb_http_tgs_ports" { + description = "List of HTTP Target Groups ports" + value = [for tg in aws_lb_target_group.lb_http_tgs : tostring(tg.port)] +} + +output "lb_http_tgs_map_arn_port" { + value = zipmap( + [for tg in aws_lb_target_group.lb_http_tgs : tg.arn], + [for tg in aws_lb_target_group.lb_http_tgs : tostring(tg.port)] + ) +} + +output "lb_https_tgs_ids" { + description = "List of HTTPS Target Groups IDs" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.id] +} + +output "lb_https_tgs_arns" { + description = "List of HTTPS Target Groups ARNs" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.arn] +} + +output "lb_https_tgs_names" { + description = "List of HTTPS Target Groups Names" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.name] +} + +output "lb_https_tgs_ports" { + description = "List of HTTPS Target Groups ports" + value = [for tg in aws_lb_target_group.lb_https_tgs : tostring(tg.port)] +} + +output "lb_https_tgs_map_arn_port" { + value = zipmap( + [for tg in aws_lb_target_group.lb_https_tgs : tg.arn], + [for tg in aws_lb_target_group.lb_https_tgs : tostring(tg.port)] + ) +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Listeners +#------------------------------------------------------------------------------ +output "lb_http_listeners_ids" { + description = "List of HTTP Listeners IDs" + value = [for listener in aws_lb_listener.lb_http_listeners : listener.id] +} + +output "lb_http_listeners_arns" { + description = "List of HTTP Listeners ARNs" + value = [for listener in aws_lb_listener.lb_http_listeners : listener.arn] +} + +output "lb_https_listeners_ids" { + description = "List of HTTPS Listeners IDs" + value = [for listener in aws_lb_listener.lb_https_listeners : listener.id] +} + +output "lb_https_listeners_arns" { + description = "List of HTTPS Listeners ARNs" + value = [for listener in aws_lb_listener.lb_https_listeners : listener.arn] +} + +#------------------------------------------------------------------------------ +# S3 LB Logging Bucket +#------------------------------------------------------------------------------ +output "lb_logs_s3_bucket_id" { + description = "LB Logging S3 Bucket ID" + value = var.enable_s3_logs ? module.lb_logs_s3[0].s3_bucket_id : null +} + +output "lb_logs_s3_bucket_arn" { + description = "LB Logging S3 Bucket ARN" + value = var.enable_s3_logs ? module.lb_logs_s3[0].s3_bucket_arn : null +} \ No newline at end of file diff --git a/modules/aws-alb-master/variables.tf b/modules/aws-alb-master/variables.tf new file mode 100644 index 0000000..f1499f7 --- /dev/null +++ b/modules/aws-alb-master/variables.tf @@ -0,0 +1,249 @@ +#------------------------------------------------------------------------------ +# Misc +#------------------------------------------------------------------------------ +variable "name_prefix" { + description = "Name prefix for resources on AWS" +} + +variable "use_random_name_for_lb" { + description = "If true the LB name will be a random string" + type = bool + default = false +} + +variable "tags" { + type = map(string) + default = {} + description = "Resource tags" +} + +#------------------------------------------------------------------------------ +# AWS Networking +#------------------------------------------------------------------------------ +variable "vpc_id" { + description = "ID of the VPC" +} + +#------------------------------------------------------------------------------ +# S3 logs bucket +#------------------------------------------------------------------------------ +variable "enable_s3_logs" { + description = "(Optional) If true, all resources to send LB logs to S3 will be created" + type = bool + default = true +} + + +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +variable "internal" { + description = "(Optional) If true, the LB will be internal." + type = bool + default = false +} + +variable "security_groups" { + description = "(Optional) A list of security group IDs to assign to the LB." + type = list(string) + default = [] +} + +variable "drop_invalid_header_fields" { + description = "(Optional) Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false). The default is false. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens." + type = bool + default = false +} + +variable "private_subnets" { + description = "A list of private subnet IDs to attach to the LB if it is INTERNAL." + type = list(string) +} + +variable "public_subnets" { + description = "A list of public subnet IDs to attach to the LB if it is NOT internal." + type = list(string) +} + +variable "idle_timeout" { + description = "(Optional) The time in seconds that the connection is allowed to be idle. Default: 60." + type = number + default = 60 +} + +variable "enable_deletion_protection" { + description = "(Optional) If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false." + type = bool + default = false +} + +variable "enable_cross_zone_load_balancing" { + description = "(Optional) If true, cross-zone load balancing of the load balancer will be enabled. Defaults to false." + type = bool + default = false +} + +variable "enable_http2" { + description = "(Optional) Indicates whether HTTP/2 is enabled in the load balancer. Defaults to true." + type = bool + default = true +} + +variable "ip_address_type" { + description = "(Optional) The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack. Defaults to ipv4" + type = string + default = "ipv4" +} + +variable "waf_web_acl_arn" { + description = "ARN of a WAFV2 to associate with the ALB" + type = string + default = "" +} + +#------------------------------------------------------------------------------ +# ACCESS CONTROL TO APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +variable "http_ports" { + description = "Map containing objects to define listeners behaviour based on type field. If type field is `forward`, include listener_port and the target_group_port. For `redirect` type, include listener port, host, path, port, protocol, query and status_code. For `fixed-response`, include listener_port, content_type, message_body and status_code" + type = map(any) + default = { + default_http = { + type = "forward" + listener_port = 80 + target_group_port = 80 + } + } +} + +variable "https_ports" { + description = "Map containing objects to define listeners behaviour based on type field. If type field is `forward`, include listener_port and the target_group_port. For `redirect` type, include listener port, host, path, port, protocol, query and status_code. For `fixed-response`, include listener_port, content_type, message_body and status_code" + type = map(any) + default = { + default_http = { + type = "forward" + listener_port = 443 + target_group_port = 443 + } + } +} + + +variable "http_ingress_cidr_blocks" { + description = "List of CIDR blocks to allowed to access the Load Balancer through HTTP" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "http_ingress_prefix_list_ids" { + description = "List of prefix list IDs blocks to allowed to access the Load Balancer through HTTP" + type = list(string) + default = [] +} + +variable "https_ingress_cidr_blocks" { + description = "List of CIDR blocks to allowed to access the Load Balancer through HTTPS" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "https_ingress_prefix_list_ids" { + description = "List of prefix list IDs blocks to allowed to access the Load Balancer through HTTPS" + type = list(string) + default = [] +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +variable "deregistration_delay" { + description = "(Optional) The amount time for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds. The default value is 300 seconds." + type = number + default = 300 +} + +variable "slow_start" { + description = "(Optional) The amount time for targets to warm up before the load balancer sends them a full share of requests. The range is 30-900 seconds or 0 to disable. The default value is 0 seconds." + type = number + default = 0 +} + +variable "load_balancing_algorithm_type" { + description = "(Optional) Determines how the load balancer selects targets when routing requests. The value is round_robin or least_outstanding_requests. The default is round_robin." + type = string + default = "round_robin" +} + +variable "stickiness" { + description = "(Optional) A Stickiness block. Provide three fields. type, the type of sticky sessions. The only current possible value is lb_cookie. cookie_duration, the time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). enabled, boolean to enable / disable stickiness. Default is true." + type = object({ + type = string + cookie_duration = string + enabled = bool + }) + default = { + type = "lb_cookie" + cookie_duration = 86400 + enabled = true + } +} + +variable "target_group_health_check_enabled" { + description = "(Optional) Indicates whether health checks are enabled. Defaults to true." + type = bool + default = true +} + +variable "target_group_health_check_interval" { + description = "(Optional) The approximate amount of time, in seconds, between health checks of an individual target. Minimum value 5 seconds, Maximum value 300 seconds. Default 30 seconds." + type = number + default = 30 +} + +variable "target_group_health_check_path" { + description = "The destination for the health check request." + type = string + default = "/" +} + +variable "target_group_health_check_timeout" { + description = "(Optional) The amount of time, in seconds, during which no response means a failed health check. The range is 2 to 120 seconds, and the default is 5 seconds." + type = number + default = 5 +} + +variable "target_group_health_check_healthy_threshold" { + description = "(Optional) The number of consecutive health checks successes required before considering an unhealthy target healthy. Defaults to 3." + type = number + default = 3 +} + +variable "target_group_health_check_unhealthy_threshold" { + description = "(Optional) The number of consecutive health check failures required before considering the target unhealthy. Defaults to 3." + type = number + default = 3 +} + +variable "target_group_health_check_matcher" { + description = "The HTTP codes to use when checking for a successful response from a target. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). Default is 200." + type = string + default = "200" +} + +variable "ssl_policy" { + description = "(Optional) The name of the SSL Policy for the listener. . Required if var.https_ports is set." + type = string + default = null +} + +variable "default_certificate_arn" { + description = "(Optional) The ARN of the default SSL server certificate. Required if var.https_ports is set." + type = string + default = null +} + +variable "additional_certificates_arn_for_https_listeners" { + description = "(Optional) List of SSL server certificate ARNs for HTTPS listener. Use it if you need to set additional certificates besides default_certificate_arn" + type = list(any) + default = [] +} \ No newline at end of file diff --git a/modules/aws-alb-master/versions.tf b/modules/aws-alb-master/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-alb-master/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-amplify/main.tf b/modules/aws-amplify/main.tf new file mode 100644 index 0000000..75185ef --- /dev/null +++ b/modules/aws-amplify/main.tf @@ -0,0 +1,62 @@ +resource "aws_amplify_app" "this" { + name = var.name + repository = var.repository + + build_spec = var.build_spec + + environment_variables = var.environment_variables + + auto_branch_creation_patterns = var.auto_branch_creation_patterns + + enable_auto_branch_creation = try(var.enable_auto_branch_creation, false) + enable_basic_auth = try(var.enable_basic_auth, false) + enable_branch_auto_build = try(var.enable_branch_auto_build, false) + enable_branch_auto_deletion = try(var.enable_branch_auto_deletion, false) + + iam_service_role_arn = var.iam_service_role_arn + + platform = var.platform + + dynamic "custom_rule" { + for_each = var.custom_rules + + content { + source = custom_rule.value["source"] + status = custom_rule.value["status"] + target = custom_rule.value["target"] + } + } + + tags = var.tags +} + +resource "aws_amplify_branch" "this" { + app_id = aws_amplify_app.this.id + branch_name = var.branch_name + + framework = var.framework + stage = var.stage + + enable_auto_build = var.enable_auto_build + enable_performance_mode = var.enable_performance_mode + enable_pull_request_preview = var.enable_pull_request_preview + + tags = var.tags +} + +resource "aws_amplify_domain_association" "this" { + app_id = aws_amplify_app.this.id + domain_name = var.domain_name + + enable_auto_sub_domain = var.enable_auto_sub_domain + + dynamic "sub_domain" { + for_each = var.sub_domains + + content { + branch_name = aws_amplify_branch.this.branch_name + prefix = try(sub_domain.value["prefix"], "") + dns_record = try(sub_domain.value["dns_record"], null) + } + } +} diff --git a/modules/aws-amplify/variables.tf b/modules/aws-amplify/variables.tf new file mode 100644 index 0000000..a0b914a --- /dev/null +++ b/modules/aws-amplify/variables.tf @@ -0,0 +1,111 @@ +variable "name" { + description = "Application repository address connect to" + type = string +} + +variable "repository" { + description = "Application repository address connect to" + type = string +} + +variable "build_spec" { + description = "Application repository address connect to" + type = string +} + +variable "environment_variables" { + type = any + default = {} +} + +variable "enable_auto_branch_creation" { + description = "Application repository address connect to" + type = bool + default = false +} + +variable "enable_basic_auth" { + description = "Application repository address connect to" + type = bool + default = false +} + +variable "enable_branch_auto_build" { + description = "Application repository address connect to" + type = bool + default = false +} + +variable "enable_branch_auto_deletion" { + description = "Application repository address connect to" + type = bool + default = false +} + +variable "enable_auto_build" { + description = "Application repository address connect to" + type = bool + default = true +} + +variable "enable_performance_mode" { + description = "Application repository address connect to" + type = bool + default = false +} + +variable "platform" { + description = "Application repository address connect to" + type = string + default = "WEB_COMPUTE" +} + +variable "auto_branch_creation_patterns" { + description = "Application repository address connect to" + type = list(any) + default = [] +} + +variable "iam_service_role_arn" { + description = "Application repository address connect to" + type = string +} + +variable "custom_rules" { + type = list(any) + default = [] +} + +variable "tags" { + type = any + default = [] +} + +variable "branch_name" { + type = string + default = "main" +} + +variable "framework" { + type = string +} +variable "domain_name" { + type = string +} +variable "sub_domains" { + type = list(any) +} + +variable "stage" { + type = string +} + +variable "enable_pull_request_preview" { + type = bool + default = false +} + +variable "enable_auto_sub_domain" { + type = bool + default = false +} diff --git a/modules/aws-api-gateway/README.md b/modules/aws-api-gateway/README.md new file mode 100644 index 0000000..da0ed8c --- /dev/null +++ b/modules/aws-api-gateway/README.md @@ -0,0 +1,279 @@ + ** DO NOT EDIT THIS FILE + ** + ** This file was automatically generated by the `build-harness`. + ** 1) Make all changes to `README.yaml` + ** 2) Run `make init` (you only need to do this once) + ** 3) Run`make readme` to rebuild this file. + ** + ** (We maintain HUNDREDS of open source projects. This is how we maintain our sanity.) + ** + + + + + +--> + +Terraform module to provision API Gatway resources. + +The root module creates an API Gateway [REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) +along with configuring tracing, logging, and metrics. + +We literally have [*hundreds of terraform modules*][terraform_modules] that are Open Source and well-maintained. Check them out! + + + + + +## Introduction + +A set of modules for configuring an API Gateway + + +## Security & Compliance + + +## Usage + + +**IMPORTANT:** We do not pin modules to versions in our examples because of the +difficulty of keeping the versions in the documentation in sync with the latest released versions. +We highly recommend that in your code you pin the version to the exact version you are +using so that your infrastructure remains stable, and update versions in a +systematic way so that they do not catch you by surprise. + +Also, because of a bug in the Terraform registry ([hashicorp/terraform#21417](https://github.com/hashicorp/terraform/issues/21417)), +the registry shows many of our inputs as required when in fact they are optional. +The table below correctly indicates which inputs are required. + + + +Setup the account-level settings for logging and metrics for API Gateway: + +```hcl +module "api_gateway_account_settgings" { + source = "cloudposse/api-gateway/aws//modules/account-settings" + # version = "x.x.x" + + context = module.this.context +} +``` + + + + +## Examples + +Review the [examples](examples) folder to see how to use the API Gateway modules. + + + + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cloudwatch\_log\_group](#module\_cloudwatch\_log\_group) | cloudposse/cloudwatch-logs/aws | 0.6.5 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_api_gateway_deployment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_deployment) | resource | +| [aws_api_gateway_method_settings.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings) | resource | +| [aws_api_gateway_rest_api.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api) | resource | +| [aws_api_gateway_rest_api_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api_policy) | resource | +| [aws_api_gateway_stage.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage) | resource | +| [aws_api_gateway_vpc_link.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_vpc_link) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_log\_format](#input\_access\_log\_format) | The format of the access log file. | `string` | `" {\n\t\"requestTime\": \"$context.requestTime\",\n\t\"requestId\": \"$context.requestId\",\n\t\"httpMethod\": \"$context.httpMethod\",\n\t\"path\": \"$context.path\",\n\t\"resourcePath\": \"$context.resourcePath\",\n\t\"status\": $context.status,\n\t\"responseLatency\": $context.responseLatency,\n \"xrayTraceId\": \"$context.xrayTraceId\",\n \"integrationRequestId\": \"$context.integration.requestId\",\n\t\"functionResponseStatus\": \"$context.integration.status\",\n \"integrationLatency\": \"$context.integration.latency\",\n\t\"integrationServiceStatus\": \"$context.integration.integrationStatus\",\n \"authorizeResultStatus\": \"$context.authorize.status\",\n\t\"authorizerServiceStatus\": \"$context.authorizer.status\",\n\t\"authorizerLatency\": \"$context.authorizer.latency\",\n\t\"authorizerRequestId\": \"$context.authorizer.requestId\",\n \"ip\": \"$context.identity.sourceIp\",\n\t\"userAgent\": \"$context.identity.userAgent\",\n\t\"principalId\": \"$context.authorizer.principalId\",\n\t\"cognitoUser\": \"$context.identity.cognitoIdentityId\",\n \"user\": \"$context.identity.user\"\n}\n"` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [endpoint\_type](#input\_endpoint\_type) | The type of the endpoint. One of - PUBLIC, PRIVATE, REGIONAL | `string` | `"REGIONAL"` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [iam\_tags\_enabled](#input\_iam\_tags\_enabled) | Enable/disable tags on IAM roles and policies | `string` | `true` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [logging\_level](#input\_logging\_level) | The logging level of the API. One of - OFF, INFO, ERROR | `string` | `"INFO"` | no | +| [metrics\_enabled](#input\_metrics\_enabled) | A flag to indicate whether to enable metrics collection. | `bool` | `false` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | yes | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [openapi\_config](#input\_openapi\_config) | The OpenAPI specification for the API | `any` | `{}` | yes | +| [permissions\_boundary](#input\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `""` | no | +| [private\_link\_target\_arns](#input\_private\_link\_target\_arns) | A list of target ARNs for VPC Private Link | `list(string)` | `[]` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [rest\_api\_policy](#input\_rest\_api\_policy) | The IAM policy document for the API. | `string` | `null` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [stage\_name](#input\_stage\_name) | The name of the stage | `string` | `""` | yes | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [xray\_tracing\_enabled](#input\_xray\_tracing\_enabled) | A flag to indicate whether to enable X-Ray tracing. | `bool` | `false` | no | +| [domain\_name](#domain\_name) | Give a domain to associate with api gateway. | `string` | `""` | yes | +| [domain\_name\_certificate\_arn](#input\_domain\_name\_certificate\_arn) | Give acm certificate arn to associate with api gateway domain | `string` | `""` | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the REST API | +| [created\_date](#output\_created\_date) | The date the REST API was created | +| [execution\_arn](#output\_execution\_arn) | The execution ARN part to be used in lambda\_permission's source\_arn when allowing API Gateway to invoke a Lambda
function, e.g., arn:aws:execute-api:eu-west-2:123456789012:z4675bid1j, which can be concatenated with allowed stage,
method and resource path.The ARN of the Lambda function that will be executed. | +| [id](#output\_id) | The ID of the REST API | +| [invoke\_url](#output\_invoke\_url) | The URL to invoke the REST API | +| [root\_resource\_id](#output\_root\_resource\_id) | The resource ID of the REST API's root | +| [stage\_arn](#output\_stage\_arn) | The ARN of the gateway stage | + + + + +## Share the Love + +Like this project? Please give it a ★ on [our GitHub](https://github.com/cloudposse/terraform-aws-api-gateway)! (it helps us **a lot**) + +Are you using this project or any of our other projects? Consider [leaving a testimonial][testimonial]. =) + + + +## Related Projects + +Check out these related projects. + +- [terraform-yaml-config](https://github.com/cloudposse/terraform-yaml-config) - Terraform module to convert local and remote YAML configuration templates into Terraform lists and maps + + +## References + +For additional context, refer to some of these links. + +- [API Gateway CloudWatch Logging](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html) - To enable CloudWatch Logs, you must grant API Gateway permission to read and write logs to CloudWatch for your account. +- [Create a monitor](https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor) - Create datadog monitors +- [Terraform Datadog role resources](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/role) - Provides a Datadog role resource. Used to create and manage Datadog roles +- [Datadog permissions](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/data-sources/permissions) - Use this data source to retrieve the list of Datadog permissions by name and their corresponding ID, for use in the role resource +- [Role Based Access Control](https://docs.datadoghq.com/account_management/rbac) - Roles categorize users and define what account permissions those users have, such as what data they can read or what account assets they can modify +- [Managing Multiple-Organization Accounts](https://docs.datadoghq.com/account_management/multi_organization) - It is possible to manage multiple child-organizations from one parent-organization account. This is typically used by Managed Service Providers that have customers which should not have access to each others' data + + +## Help + + +## DevOps Accelerator for Startups + + +We are a [**DevOps Accelerator**][commercial_support]. We'll help you build your cloud infrastructure from the ground up so you can own it. Then we'll show you how to operate it and stick around for as long as you need us. + +Work directly with our team of DevOps experts via email, slack, and video conferencing. + +We deliver 10x the value for a fraction of the cost of a full-time engineer. Our track record is not even funny. If you want things done right and you need it done FAST, then we're your best bet. + +- **Reference Architecture.** You'll get everything you need from the ground up built using 100% infrastructure as code. +- **Release Engineering.** You'll have end-to-end CI/CD with unlimited staging environments. +- **Site Reliability Engineering.** You'll have total visibility into your apps and microservices. +- **Security Baseline.** You'll have built-in governance with accountability and audit logs for all changes. +- **GitOps.** You'll be able to operate your infrastructure via Pull Requests. +- **Training.** You'll receive hands-on training so your team can operate what we build. +- **Questions.** You'll have a direct line of communication between our teams via a Shared Slack channel. +- **Troubleshooting.** You'll get help to triage when things aren't working. +- **Code Reviews.** You'll receive constructive feedback on Pull Requests. +- **Bug Fixes.** We'll rapidly work with you to fix any bugs in our projects. + +## Slack Community + +Join our [Open Source Community][slack] on Slack. It's **FREE** for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totally *sweet* infrastructure. + +## Discourse Forums + +Participate in our [Discourse Forums][discourse]. Here you'll find answers to commonly asked questions. Most questions will be related to the enormous number of projects we support on our GitHub. Come here to collaborate on answers, find solutions, and get ideas about the products and services we value. It only takes a minute to get started! Just sign in with SSO using your GitHub account. + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-api-gateway/issues) to report any bugs or file feature requests. + +### Developing + +If you are interested in being a contributor and want to get involved in developing this project or [help out](https://cpco.io/help-out) with our other projects, we would love to hear from you! Shoot us an [email][email]. + +In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull Request** so that we can review your changes + +**NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request! + +## License + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +See [LICENSE](LICENSE) for full details. + +```text +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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 + + https://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. +``` + + + + + + + + + +## Trademarks + +All other trademarks referenced herein are the property of their respective owners. \ No newline at end of file diff --git a/modules/aws-api-gateway/context.tf b/modules/aws-api-gateway/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-api-gateway/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-api-gateway/docs/targets.md b/modules/aws-api-gateway/docs/targets.md new file mode 100644 index 0000000..3dce8b3 --- /dev/null +++ b/modules/aws-api-gateway/docs/targets.md @@ -0,0 +1,12 @@ + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + diff --git a/modules/aws-api-gateway/docs/terraform.md b/modules/aws-api-gateway/docs/terraform.md new file mode 100644 index 0000000..d878910 --- /dev/null +++ b/modules/aws-api-gateway/docs/terraform.md @@ -0,0 +1,78 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cloudwatch\_log\_group](#module\_cloudwatch\_log\_group) | cloudposse/cloudwatch-logs/aws | 0.6.5 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_api_gateway_deployment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_deployment) | resource | +| [aws_api_gateway_method_settings.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings) | resource | +| [aws_api_gateway_rest_api.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api) | resource | +| [aws_api_gateway_rest_api_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api_policy) | resource | +| [aws_api_gateway_stage.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage) | resource | +| [aws_api_gateway_vpc_link.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_vpc_link) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_log\_format](#input\_access\_log\_format) | The format of the access log file. | `string` | `" {\n\t\"requestTime\": \"$context.requestTime\",\n\t\"requestId\": \"$context.requestId\",\n\t\"httpMethod\": \"$context.httpMethod\",\n\t\"path\": \"$context.path\",\n\t\"resourcePath\": \"$context.resourcePath\",\n\t\"status\": $context.status,\n\t\"responseLatency\": $context.responseLatency,\n \"xrayTraceId\": \"$context.xrayTraceId\",\n \"integrationRequestId\": \"$context.integration.requestId\",\n\t\"functionResponseStatus\": \"$context.integration.status\",\n \"integrationLatency\": \"$context.integration.latency\",\n\t\"integrationServiceStatus\": \"$context.integration.integrationStatus\",\n \"authorizeResultStatus\": \"$context.authorize.status\",\n\t\"authorizerServiceStatus\": \"$context.authorizer.status\",\n\t\"authorizerLatency\": \"$context.authorizer.latency\",\n\t\"authorizerRequestId\": \"$context.authorizer.requestId\",\n \"ip\": \"$context.identity.sourceIp\",\n\t\"userAgent\": \"$context.identity.userAgent\",\n\t\"principalId\": \"$context.authorizer.principalId\",\n\t\"cognitoUser\": \"$context.identity.cognitoIdentityId\",\n \"user\": \"$context.identity.user\"\n}\n"` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [endpoint\_type](#input\_endpoint\_type) | The type of the endpoint. One of - PUBLIC, PRIVATE, REGIONAL | `string` | `"REGIONAL"` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [iam\_tags\_enabled](#input\_iam\_tags\_enabled) | Enable/disable tags on IAM roles and policies | `string` | `true` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [logging\_level](#input\_logging\_level) | The logging level of the API. One of - OFF, INFO, ERROR | `string` | `"INFO"` | no | +| [metrics\_enabled](#input\_metrics\_enabled) | A flag to indicate whether to enable metrics collection. | `bool` | `false` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [openapi\_config](#input\_openapi\_config) | The OpenAPI specification for the API | `any` | `{}` | no | +| [permissions\_boundary](#input\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `""` | no | +| [private\_link\_target\_arns](#input\_private\_link\_target\_arns) | A list of target ARNs for VPC Private Link | `list(string)` | `[]` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [rest\_api\_policy](#input\_rest\_api\_policy) | The IAM policy document for the API. | `string` | `null` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [stage\_name](#input\_stage\_name) | The name of the stage | `string` | `""` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [xray\_tracing\_enabled](#input\_xray\_tracing\_enabled) | A flag to indicate whether to enable X-Ray tracing. | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the REST API | +| [created\_date](#output\_created\_date) | The date the REST API was created | +| [execution\_arn](#output\_execution\_arn) | The execution ARN part to be used in lambda\_permission's source\_arn when allowing API Gateway to invoke a Lambda
function, e.g., arn:aws:execute-api:eu-west-2:123456789012:z4675bid1j, which can be concatenated with allowed stage,
method and resource path.The ARN of the Lambda function that will be executed. | +| [id](#output\_id) | The ID of the REST API | +| [invoke\_url](#output\_invoke\_url) | The URL to invoke the REST API | +| [root\_resource\_id](#output\_root\_resource\_id) | The resource ID of the REST API's root | +| [stage\_arn](#output\_stage\_arn) | The ARN of the gateway stage | + diff --git a/modules/aws-api-gateway/main.tf b/modules/aws-api-gateway/main.tf new file mode 100644 index 0000000..1f0081e --- /dev/null +++ b/modules/aws-api-gateway/main.tf @@ -0,0 +1,112 @@ +locals { + enabled = module.this.enabled + create_rest_api_policy = local.enabled && var.rest_api_policy != null + create_log_group = local.enabled && var.logging_level != "OFF" + log_group_arn = local.create_log_group ? module.cloudwatch_log_group.log_group_arn : null + vpc_link_enabled = local.enabled && length(var.private_link_target_arns) > 0 +} + +resource "aws_api_gateway_rest_api" "this" { + count = local.enabled ? 1 : 0 + + name = module.this.id + body = jsonencode(var.openapi_config) + tags = module.this.tags + + endpoint_configuration { + types = [var.endpoint_type] + } +} + +resource "aws_api_gateway_rest_api_policy" "this" { + count = local.create_rest_api_policy ? 1 : 0 + rest_api_id = aws_api_gateway_rest_api.this[0].id + + policy = var.rest_api_policy +} + +module "cloudwatch_log_group" { + source = "cloudposse/cloudwatch-logs/aws" + version = "0.6.5" + + enabled = local.create_log_group + iam_tags_enabled = var.iam_tags_enabled + permissions_boundary = var.permissions_boundary + + context = module.this.context +} + +resource "aws_api_gateway_deployment" "this" { + count = local.enabled ? 1 : 0 + rest_api_id = aws_api_gateway_rest_api.this[0].id + + triggers = { + redeployment = sha1(jsonencode(aws_api_gateway_rest_api.this[0].body)) + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_api_gateway_stage" "this" { + count = local.enabled ? 1 : 0 + deployment_id = aws_api_gateway_deployment.this[0].id + rest_api_id = aws_api_gateway_rest_api.this[0].id + stage_name = var.stage_name != "" ? var.stage_name : module.this.stage + xray_tracing_enabled = var.xray_tracing_enabled + tags = module.this.tags + + variables = { + vpc_link_id = local.vpc_link_enabled ? aws_api_gateway_vpc_link.this[0].id : null + } + + dynamic "access_log_settings" { + for_each = local.create_log_group ? [1] : [] + + content { + destination_arn = local.log_group_arn + format = replace(var.access_log_format, "\n", "") + } + } +} + +# Set the logging, metrics and tracing levels for all methods +resource "aws_api_gateway_method_settings" "all" { + count = local.enabled ? 1 : 0 + rest_api_id = aws_api_gateway_rest_api.this[0].id + stage_name = aws_api_gateway_stage.this[0].stage_name + method_path = "*/*" + + settings { + metrics_enabled = var.metrics_enabled + logging_level = var.logging_level + } +} + +# Optionally create a VPC Link to allow the API Gateway to communicate with private resources (e.g. ALB) +resource "aws_api_gateway_vpc_link" "this" { + count = local.vpc_link_enabled ? 1 : 0 + name = module.this.id + description = "VPC Link for ${module.this.id}" + target_arns = var.private_link_target_arns +} + +resource "aws_api_gateway_domain_name" "this" { + count = local.enabled ? 1 : 0 + + regional_certificate_arn = var.domain_name_certificate_arn + domain_name = var.domain_name + endpoint_configuration { + types = ["REGIONAL"] + } +} + + +resource "aws_api_gateway_base_path_mapping" "this" { + count = local.enabled ? 1 : 0 + + api_id = aws_api_gateway_rest_api.this[0].id + domain_name = aws_api_gateway_domain_name.this[0].id + stage_name = aws_api_gateway_stage.this[0].stage_name +} \ No newline at end of file diff --git a/modules/aws-api-gateway/modules/account-settings/README.md b/modules/aws-api-gateway/modules/account-settings/README.md new file mode 100644 index 0000000..0c986eb --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/README.md @@ -0,0 +1,24 @@ +# API Gateway Account Settings Module + +This module allows you set the global, regional settings required to allow API Gateway to write to CloudWatch logs. + +Every AWS region you want to deploy an API Gateway to must be configured with an IAM Role that gives API Gateway permissions to create and write to CloudWatch logs. Without this configuration, API Gateway will not be able to send logs to CloudWatch. This configuration is done once per region regardless of the number of API Gateways deployed in that region. This module creates an IAM role, assigns it the necessary permissions to write logs and sets it as the "CloudWatch log role ARN" in the API Gateway configuration. + +## Usage + +**IMPORTANT:** The `main` branch is used in `source` just as an example. In your code, do not pin to `main` because there may be breaking changes between releases. +Instead pin to the release tag (e.g. `?ref=tags/x.y.z`) of one of our [latest releases](https://github.com/cloudposse/terraform-aws-config/releases). + +For a complete example, see [examples/account-settings](../../examples/account-settings). + +For automated tests of the complete example using [bats](https://github.com/bats-core/bats-core) and [Terratest](https://github.com/gruntwork-io/terratest)(which tests and deploys the example on AWS), see [test](../../test). + +```hcl +module "account_settings" { + source = "cloudposse/api-gateway/aws//modules/account-settings" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + + context = module.this.context +} +``` diff --git a/modules/aws-api-gateway/modules/account-settings/context.tf b/modules/aws-api-gateway/modules/account-settings/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-api-gateway/modules/account-settings/main.tf b/modules/aws-api-gateway/modules/account-settings/main.tf new file mode 100644 index 0000000..af39802 --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/main.tf @@ -0,0 +1,53 @@ + +locals { + create_iam_role = module.this.enabled && var.iam_role_arn == null + role_arn = module.this.enabled ? var.iam_role_arn == null ? module.role.arn : var.iam_role_arn : null +} + +resource "aws_api_gateway_account" "this" { + count = module.this.enabled ? 1 : 0 + cloudwatch_role_arn = local.role_arn +} + +data "aws_iam_policy_document" "api_gateway_permissions" { + statement { + sid = "AllowAPIGatwayToCloudwatch" + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ] + resources = ["*"] + } +} + +module "role" { + source = "cloudposse/iam-role/aws" + version = "0.16.1" + + enabled = local.create_iam_role + #name = module.iam_role_label.id + use_fullname = true + attributes = ["api", "gateway", "cloudwatch"] + + principals = { + "Service" : ["apigateway.amazonaws.com"] + } + + policy_documents = [ + data.aws_iam_policy_document.api_gateway_permissions.json, + ] + + policy_document_count = 1 + policy_description = "Allow API Gateway to send logs to CloudWatch IAM policy" + role_description = "Allow API Gateway to send logs to CloudWatch" + permissions_boundary = var.permissions_boundary + tags_enabled = var.iam_tags_enabled + + context = module.this.context +} diff --git a/modules/aws-api-gateway/modules/account-settings/outputs.tf b/modules/aws-api-gateway/modules/account-settings/outputs.tf new file mode 100644 index 0000000..cc25c94 --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/outputs.tf @@ -0,0 +1,4 @@ +output "role_arn" { + description = "Role ARN of the API Gateway logging role" + value = local.role_arn +} diff --git a/modules/aws-api-gateway/modules/account-settings/variables.tf b/modules/aws-api-gateway/modules/account-settings/variables.tf new file mode 100644 index 0000000..b17ea3f --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/variables.tf @@ -0,0 +1,17 @@ +variable "iam_role_arn" { + type = string + description = "ARN of the IAM role for API Gateway to use. If not specified, a new role will be created." + default = null +} + +variable "iam_tags_enabled" { + type = string + description = "Enable/disable tags on IAM roles" + default = true +} + +variable "permissions_boundary" { + type = string + default = "" + description = "ARN of the policy that is used to set the permissions boundary for the role" +} \ No newline at end of file diff --git a/modules/aws-api-gateway/modules/account-settings/versions.tf b/modules/aws-api-gateway/modules/account-settings/versions.tf new file mode 100644 index 0000000..4569d16 --- /dev/null +++ b/modules/aws-api-gateway/modules/account-settings/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} \ No newline at end of file diff --git a/modules/aws-api-gateway/outputs.tf b/modules/aws-api-gateway/outputs.tf new file mode 100644 index 0000000..a3e1bfd --- /dev/null +++ b/modules/aws-api-gateway/outputs.tf @@ -0,0 +1,38 @@ +output "id" { + description = "The ID of the REST API" + value = module.this.enabled ? aws_api_gateway_rest_api.this[0].id : null +} + +output "root_resource_id" { + description = "The resource ID of the REST API's root" + value = module.this.enabled ? aws_api_gateway_rest_api.this[0].root_resource_id : null +} + +output "created_date" { + description = "The date the REST API was created" + value = module.this.enabled ? aws_api_gateway_rest_api.this[0].created_date : null +} + +output "execution_arn" { + description = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_apigatewayv2_api.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_api) | resource | +| [aws_apigatewayv2_api_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_api_mapping) | resource | +| [aws_apigatewayv2_authorizer.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_authorizer) | resource | +| [aws_apigatewayv2_domain_name.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_domain_name) | resource | +| [aws_apigatewayv2_integration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_integration) | resource | +| [aws_apigatewayv2_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_route) | resource | +| [aws_apigatewayv2_stage.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_stage) | resource | +| [aws_apigatewayv2_vpc_link.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_vpc_link) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [api\_key\_selection\_expression](#input\_api\_key\_selection\_expression) | An API key selection expression. Valid values: $context.authorizer.usageIdentifierKey, $request.header.x-api-key. | `string` | `"$request.header.x-api-key"` | no | +| [api\_version](#input\_api\_version) | A version identifier for the API | `string` | `null` | no | +| [authorizers](#input\_authorizers) | Map of API gateway authorizers | `map(any)` | `{}` | no | +| [body](#input\_body) | An OpenAPI specification that defines the set of routes and integrations to create as part of the HTTP APIs. Supported only for HTTP APIs. | `string` | `null` | no | +| [cors\_configuration](#input\_cors\_configuration) | The cross-origin resource sharing (CORS) configuration. Applicable for HTTP APIs. | `any` | `{}` | no | +| [create](#input\_create) | Controls if API Gateway resources should be created | `bool` | `true` | no | +| [create\_api\_domain\_name](#input\_create\_api\_domain\_name) | Whether to create API domain name resource | `bool` | `true` | no | +| [create\_api\_gateway](#input\_create\_api\_gateway) | Whether to create API Gateway | `bool` | `true` | no | +| [create\_default\_stage](#input\_create\_default\_stage) | Whether to create default stage | `bool` | `true` | no | +| [create\_default\_stage\_api\_mapping](#input\_create\_default\_stage\_api\_mapping) | Whether to create default stage API mapping | `bool` | `true` | no | +| [create\_routes\_and\_integrations](#input\_create\_routes\_and\_integrations) | Whether to create routes and integrations resources | `bool` | `true` | no | +| [create\_vpc\_link](#input\_create\_vpc\_link) | Whether to create VPC links | `bool` | `true` | no | +| [credentials\_arn](#input\_credentials\_arn) | Part of quick create. Specifies any credentials required for the integration. Applicable for HTTP APIs. | `string` | `null` | no | +| [default\_route\_settings](#input\_default\_route\_settings) | Settings for default route | `map(string)` | `{}` | no | +| [default\_stage\_access\_log\_destination\_arn](#input\_default\_stage\_access\_log\_destination\_arn) | Default stage's ARN of the CloudWatch Logs log group to receive access logs. Any trailing :* is trimmed from the ARN. | `string` | `null` | no | +| [default\_stage\_access\_log\_format](#input\_default\_stage\_access\_log\_format) | Default stage's single line format of the access logs of data, as specified by selected $context variables. | `string` | `null` | no | +| [default\_stage\_tags](#input\_default\_stage\_tags) | A mapping of tags to assign to the default stage resource. | `map(string)` | `{}` | no | +| [description](#input\_description) | The description of the API. | `string` | `null` | no | +| [disable\_execute\_api\_endpoint](#input\_disable\_execute\_api\_endpoint) | Whether clients can invoke the API by using the default execute-api endpoint. To require that clients use a custom domain name to invoke the API, disable the default endpoint | `string` | `false` | no | +| [domain\_name](#input\_domain\_name) | The domain name to use for API gateway | `string` | `null` | no | +| [domain\_name\_certificate\_arn](#input\_domain\_name\_certificate\_arn) | The ARN of an AWS-managed certificate that will be used by the endpoint for the domain name | `string` | `null` | no | +| [domain\_name\_ownership\_verification\_certificate\_arn](#input\_domain\_name\_ownership\_verification\_certificate\_arn) | ARN of the AWS-issued certificate used to validate custom domain ownership (when certificate\_arn is issued via an ACM Private CA or mutual\_tls\_authentication is configured with an ACM-imported certificate.) | `string` | `null` | no | +| [domain\_name\_tags](#input\_domain\_name\_tags) | A mapping of tags to assign to API domain name resource. | `map(string)` | `{}` | no | +| [integrations](#input\_integrations) | Map of API gateway routes with integrations | `map(any)` | `{}` | no | +| [mutual\_tls\_authentication](#input\_mutual\_tls\_authentication) | An Amazon S3 URL that specifies the truststore for mutual TLS authentication as well as version, keyed at uri and version | `map(string)` | `{}` | no | +| [name](#input\_name) | The name of the API | `string` | `""` | no | +| [protocol\_type](#input\_protocol\_type) | The API protocol. Valid values: HTTP, WEBSOCKET | `string` | `"HTTP"` | no | +| [route\_key](#input\_route\_key) | Part of quick create. Specifies any route key. Applicable for HTTP APIs. | `string` | `null` | no | +| [route\_selection\_expression](#input\_route\_selection\_expression) | The route selection expression for the API. | `string` | `"$request.method $request.path"` | no | +| [tags](#input\_tags) | A mapping of tags to assign to API gateway resources. | `map(string)` | `{}` | no | +| [target](#input\_target) | Part of quick create. Quick create produces an API with an integration, a default catch-all route, and a default stage which is configured to automatically deploy changes. For HTTP integrations, specify a fully qualified URL. For Lambda integrations, specify a function ARN. The type of the integration will be HTTP\_PROXY or AWS\_PROXY, respectively. Applicable for HTTP APIs. | `string` | `null` | no | +| [vpc\_link\_tags](#input\_vpc\_link\_tags) | A map of tags to add to the VPC Link | `map(string)` | `{}` | no | +| [vpc\_links](#input\_vpc\_links) | Map of VPC Links details to create | `map(any)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [apigatewayv2\_api\_api\_endpoint](#output\_apigatewayv2\_api\_api\_endpoint) | The URI of the API | +| [apigatewayv2\_api\_arn](#output\_apigatewayv2\_api\_arn) | The ARN of the API | +| [apigatewayv2\_api\_execution\_arn](#output\_apigatewayv2\_api\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | +| [apigatewayv2\_api\_id](#output\_apigatewayv2\_api\_id) | The API identifier | +| [apigatewayv2\_api\_mapping\_id](#output\_apigatewayv2\_api\_mapping\_id) | The API mapping identifier. | +| [apigatewayv2\_authorizer\_id](#output\_apigatewayv2\_authorizer\_id) | The map of API Gateway Authorizer identifiers | +| [apigatewayv2\_domain\_name\_api\_mapping\_selection\_expression](#output\_apigatewayv2\_domain\_name\_api\_mapping\_selection\_expression) | The API mapping selection expression for the domain name | +| [apigatewayv2\_domain\_name\_arn](#output\_apigatewayv2\_domain\_name\_arn) | The ARN of the domain name | +| [apigatewayv2\_domain\_name\_configuration](#output\_apigatewayv2\_domain\_name\_configuration) | The domain name configuration | +| [apigatewayv2\_domain\_name\_hosted\_zone\_id](#output\_apigatewayv2\_domain\_name\_hosted\_zone\_id) | The Amazon Route 53 Hosted Zone ID of the endpoint | +| [apigatewayv2\_domain\_name\_id](#output\_apigatewayv2\_domain\_name\_id) | The domain name identifier | +| [apigatewayv2\_domain\_name\_target\_domain\_name](#output\_apigatewayv2\_domain\_name\_target\_domain\_name) | The target domain name | +| [apigatewayv2\_vpc\_link\_arn](#output\_apigatewayv2\_vpc\_link\_arn) | The map of VPC Link ARNs | +| [apigatewayv2\_vpc\_link\_id](#output\_apigatewayv2\_vpc\_link\_id) | The map of VPC Link identifiers | +| [default\_apigatewayv2\_stage\_arn](#output\_default\_apigatewayv2\_stage\_arn) | The default stage ARN | +| [default\_apigatewayv2\_stage\_domain\_name](#output\_default\_apigatewayv2\_stage\_domain\_name) | Domain name of the stage (useful for CloudFront distribution) | +| [default\_apigatewayv2\_stage\_execution\_arn](#output\_default\_apigatewayv2\_stage\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | +| [default\_apigatewayv2\_stage\_id](#output\_default\_apigatewayv2\_stage\_id) | The default stage identifier | +| [default\_apigatewayv2\_stage\_invoke\_url](#output\_default\_apigatewayv2\_stage\_invoke\_url) | The URL to invoke the API pointing to the stage | + + +## Authors + +Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. + +Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2/tree/master/LICENSE) for full details. diff --git a/modules/aws-apigateway-v2-master/examples/complete-http/README.md b/modules/aws-apigateway-v2-master/examples/complete-http/README.md new file mode 100644 index 0000000..5b4549f --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/complete-http/README.md @@ -0,0 +1,98 @@ +# Complete AWS API Gateway (HTTP) examples + +Configuration in this directory creates AWS API Gateway with Domain Name, ACM Certificate, and integrates it with Lambda and Step Function and shows the variety of supported features. + + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.0 | +| [null](#requirement\_null) | >= 2.0 | +| [random](#requirement\_random) | >= 2.0 | +| [tls](#requirement\_tls) | >= 3.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | +| [null](#provider\_null) | >= 2.0 | +| [random](#provider\_random) | >= 2.0 | +| [tls](#provider\_tls) | >= 3.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 3.0 | +| [api\_gateway](#module\_api\_gateway) | ../../ | n/a | +| [lambda\_function](#module\_lambda\_function) | terraform-aws-modules/lambda/aws | ~> 3.0 | +| [step\_function](#module\_step\_function) | terraform-aws-modules/step-functions/aws | ~> 2.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_apigatewayv2_authorizer.some_authorizer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_authorizer) | resource | +| [aws_cloudwatch_log_group.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cognito_user_pool.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool) | resource | +| [aws_route53_record.api](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_s3_bucket.truststore](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_object.truststore](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | +| [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [tls_private_key.private_key](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [tls_self_signed_cert.example](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [api\_endpoint](#output\_api\_endpoint) | FQDN of an API endpoint | +| [api\_fqdn](#output\_api\_fqdn) | List of Route53 records | +| [apigatewayv2\_api\_api\_endpoint](#output\_apigatewayv2\_api\_api\_endpoint) | The URI of the API | +| [apigatewayv2\_authorizer\_id](#output\_apigatewayv2\_authorizer\_id) | The map of API Gateway Authorizer identifiers | +| [apigatewayv2\_domain\_name\_configuration](#output\_apigatewayv2\_domain\_name\_configuration) | The domain name configuration | +| [apigatewayv2\_domain\_name\_id](#output\_apigatewayv2\_domain\_name\_id) | The domain name identifier | +| [apigatewayv2\_hosted\_zone\_id](#output\_apigatewayv2\_hosted\_zone\_id) | The Amazon Route 53 Hosted Zone ID of the endpoint | +| [apigatewayv2\_target\_domain\_name](#output\_apigatewayv2\_target\_domain\_name) | The target domain name | +| [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The ARN of the Cloudwatch Log Group | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | +| [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | +| [lambda\_function\_kms\_key\_arn](#output\_lambda\_function\_kms\_key\_arn) | The ARN for the KMS encryption key of Lambda Function | +| [lambda\_function\_last\_modified](#output\_lambda\_function\_last\_modified) | The date Lambda Function resource was last modified | +| [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | +| [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | +| [lambda\_function\_source\_code\_hash](#output\_lambda\_function\_source\_code\_hash) | Base64-encoded representation of raw SHA-256 sum of the zip file | +| [lambda\_function\_source\_code\_size](#output\_lambda\_function\_source\_code\_size) | The size in bytes of the function .zip file | +| [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [lambda\_layer\_version](#output\_lambda\_layer\_version) | The Lambda Layer version | +| [lambda\_role\_arn](#output\_lambda\_role\_arn) | The ARN of the IAM role created for the Lambda Function | +| [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | +| [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | +| [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | + diff --git a/modules/aws-apigateway-v2-master/examples/complete-http/api.yaml b/modules/aws-apigateway-v2-master/examples/complete-http/api.yaml new file mode 100644 index 0000000..91c7590 --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/complete-http/api.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.1" +info: + version: 0.0.1 + title: My awesome API + description: My awesome API +paths: + /debug: + get: + operationId: getDebug + responses: + default: + description: "Default response for GET /debug" + x-amazon-apigateway-integration: + $ref: '#/components/x-amazon-apigateway-integrations/debug' + +components: + x-amazon-apigateway-integrations: + debug: + type: AWS_PROXY + httpMethod: GET + uri: "${example_function_arn}" + payloadFormatVersion: "2.0" + connectionType: INTERNET diff --git a/modules/aws-apigateway-v2-master/examples/complete-http/main.tf b/modules/aws-apigateway-v2-master/examples/complete-http/main.tf new file mode 100644 index 0000000..e1c922d --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/complete-http/main.tf @@ -0,0 +1,326 @@ +provider "aws" { + region = "eu-west-1" + + # Make it faster by skipping something + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + + # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn + skip_requesting_account_id = false +} + +locals { + domain_name = "terraform-aws-modules.modules.tf" # trimsuffix(data.aws_route53_zone.this.name, ".") + subdomain = "complete-http" +} + +################### +# HTTP API Gateway +################### + +module "api_gateway" { + source = "../../" + + name = "${random_pet.this.id}-http" + description = "My awesome HTTP API Gateway" + protocol_type = "HTTP" + + cors_configuration = { + allow_headers = ["content-type", "x-amz-date", "authorization", "x-api-key", "x-amz-security-token", "x-amz-user-agent"] + allow_methods = ["*"] + allow_origins = ["*"] + } + + mutual_tls_authentication = { + truststore_uri = "s3://${aws_s3_bucket.truststore.bucket}/${aws_s3_object.truststore.id}" + truststore_version = aws_s3_object.truststore.version_id + } + + domain_name = local.domain_name + domain_name_certificate_arn = module.acm.acm_certificate_arn + + default_stage_access_log_destination_arn = aws_cloudwatch_log_group.logs.arn + default_stage_access_log_format = "$context.identity.sourceIp - - [$context.requestTime] \"$context.httpMethod $context.routeKey $context.protocol\" $context.status $context.responseLength $context.requestId $context.integrationErrorMessage" + + default_route_settings = { + detailed_metrics_enabled = true + throttling_burst_limit = 100 + throttling_rate_limit = 100 + } + + authorizers = { + "cognito" = { + authorizer_type = "JWT" + identity_sources = "$request.header.Authorization" + name = "cognito" + audience = ["d6a38afd-45d6-4874-d1aa-3c5c558aqcc2"] + issuer = "https://${aws_cognito_user_pool.this.endpoint}" + } + } + + integrations = { + + "ANY /" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + timeout_milliseconds = 12000 + } + + "GET /some-route" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + authorization_type = "JWT" + authorizer_id = aws_apigatewayv2_authorizer.some_authorizer.id + throttling_rate_limit = 80 + throttling_burst_limit = 40 + detailed_metrics_enabled = true + } + + "GET /some-route-with-authorizer" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + authorizer_key = "cognito" + } + + "GET /some-route-with-authorizer-and-scope" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + authorization_type = "JWT" + authorizer_key = "cognito" + authorization_scopes = "tf/something.relevant.read,tf/something.relevant.write" # Should comply with the resource server configuration part of the cognito user pool + } + + "GET /some-route-with-authorizer-and-different-scope" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + authorization_type = "JWT" + authorizer_key = "cognito" + authorization_scopes = "tf/something.relevant.write" # Should comply with the resource server configuration part of the cognito user pool + } + + "POST /start-step-function" = { + integration_type = "AWS_PROXY" + integration_subtype = "StepFunctions-StartExecution" + credentials_arn = module.step_function.role_arn + + # Note: jsonencode is used to pass argument as a string + request_parameters = jsonencode({ + StateMachineArn = module.step_function.state_machine_arn + }) + + payload_format_version = "1.0" + timeout_milliseconds = 12000 + } + + "$default" = { + lambda_arn = module.lambda_function.lambda_function_arn + tls_config = jsonencode({ + server_name_to_verify = local.domain_name + }) + + response_parameters = jsonencode([ + { + status_code = 500 + mappings = { + "append:header.header1" = "$context.requestId" + "overwrite:statuscode" = "403" + } + }, + { + status_code = 404 + mappings = { + "append:header.error" = "$stageVariables.environmentId" + } + } + ]) + } + + } + + body = templatefile("api.yaml", { + example_function_arn = module.lambda_function.lambda_function_arn + }) + + tags = { + Name = "dev-api-new" + } +} + +###### +# ACM +###### + +data "aws_route53_zone" "this" { + name = local.domain_name +} + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 3.0" + + domain_name = local.domain_name + zone_id = data.aws_route53_zone.this.id + subject_alternative_names = ["${local.subdomain}.${local.domain_name}"] +} + +########## +# Route53 +########## + +resource "aws_route53_record" "api" { + zone_id = data.aws_route53_zone.this.zone_id + name = local.subdomain + type = "A" + + alias { + name = module.api_gateway.apigatewayv2_domain_name_configuration[0].target_domain_name + zone_id = module.api_gateway.apigatewayv2_domain_name_configuration[0].hosted_zone_id + evaluate_target_health = false + } +} + +############################# +# AWS API Gateway Authorizer +############################# + +resource "aws_apigatewayv2_authorizer" "some_authorizer" { + api_id = module.api_gateway.apigatewayv2_api_id + authorizer_type = "JWT" + identity_sources = ["$request.header.Authorization"] + name = random_pet.this.id + + jwt_configuration { + audience = ["example"] + issuer = "https://${aws_cognito_user_pool.this.endpoint}" + } +} + +######################## +# AWS Cognito User Pool +######################## + +resource "aws_cognito_user_pool" "this" { + name = "user-pool-${random_pet.this.id}" +} + +#################### +# AWS Step Function +#################### + +module "step_function" { + source = "terraform-aws-modules/step-functions/aws" + version = "~> 2.0" + + name = random_pet.this.id + + definition = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.0 | +| [null](#requirement\_null) | >= 2.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [null](#provider\_null) | >= 2.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 6.0 | +| [alb\_security\_group](#module\_alb\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [api\_gateway](#module\_api\_gateway) | ../../ | n/a | +| [api\_gateway\_security\_group](#module\_api\_gateway\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [lambda\_function](#module\_lambda\_function) | terraform-aws-modules/lambda/aws | ~> 2.0 | +| [lambda\_security\_group](#module\_lambda\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [apigatewayv2\_api\_endpoint](#output\_apigatewayv2\_api\_endpoint) | The URI of the API | +| [apigatewayv2\_vpc\_link\_arn](#output\_apigatewayv2\_vpc\_link\_arn) | The ARN of the VPC Link | +| [apigatewayv2\_vpc\_link\_id](#output\_apigatewayv2\_vpc\_link\_id) | The identifier of the VPC Link | + diff --git a/modules/aws-apigateway-v2-master/examples/vpc-link-http/main.tf b/modules/aws-apigateway-v2-master/examples/vpc-link-http/main.tf new file mode 100644 index 0000000..bb28856 --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/vpc-link-http/main.tf @@ -0,0 +1,216 @@ +locals { + region = "eu-west-1" +} + +provider "aws" { + region = local.region + + # Make it faster by skipping something + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + + # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn + skip_requesting_account_id = false +} + +################################ +# Supporting resources +################################ + +resource "random_pet" "this" { + length = 2 +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = " ~> 3.0" + + name = "vpc-link-http" + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + 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 + single_nat_gateway = true +} + +################### +# HTTP API Gateway +################### + +module "api_gateway" { + source = "../../" + + name = "${random_pet.this.id}-http-vpc-links" + description = "HTTP API Gateway with VPC links" + protocol_type = "HTTP" + + cors_configuration = { + allow_headers = ["content-type", "x-amz-date", "authorization", "x-api-key", "x-amz-security-token", "x-amz-user-agent"] + allow_methods = ["*"] + allow_origins = ["*"] + } + + create_api_domain_name = false + + integrations = { + "ANY /" = { + lambda_arn = module.lambda_function.lambda_function_arn + payload_format_version = "2.0" + timeout_milliseconds = 12000 + } + + "GET /alb-internal-route" = { + connection_type = "VPC_LINK" + vpc_link = "my-vpc" + integration_uri = module.alb.http_tcp_listener_arns[0] + integration_type = "HTTP_PROXY" + integration_method = "ANY" + } + + "$default" = { + lambda_arn = module.lambda_function.lambda_function_arn + } + } + + vpc_links = { + my-vpc = { + name = "example" + security_group_ids = [module.api_gateway_security_group.security_group_id] + subnet_ids = module.vpc.public_subnets + } + } + + tags = { + Name = "private-api" + } +} + +module "api_gateway_security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "api-gateway-sg-${random_pet.this.id}" + description = "API Gateway group for example usage" + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["http-80-tcp"] + + egress_rules = ["all-all"] +} + + +############################ +# Application Load Balancer +############################ + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 6.0" + + name = "alb-${random_pet.this.id}" + + vpc_id = module.vpc.vpc_id + security_groups = [module.alb_security_group.security_group_id] + subnets = module.vpc.public_subnets + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + action_type = "forward" + } + ] + + target_groups = [ + { + name_prefix = "l1-" + target_type = "lambda" + } + ] +} + +module "alb_security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "alb-sg-${random_pet.this.id}" + description = "ALB for example usage" + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["http-80-tcp"] + + egress_rules = ["all-all"] +} + + +############################################# +# Using packaged function from Lambda module +############################################# + +locals { + package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip" + downloaded = "downloaded_package_${md5(local.package_url)}.zip" +} + +resource "null_resource" "download_package" { + triggers = { + downloaded = local.downloaded + } + + provisioner "local-exec" { + command = "curl -L -o ${local.downloaded} ${local.package_url}" + } +} + +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 2.0" + + function_name = "${random_pet.this.id}-lambda" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + publish = true + + create_package = false + local_existing_package = local.downloaded + + attach_network_policy = true + vpc_subnet_ids = module.vpc.private_subnets + vpc_security_group_ids = [module.lambda_security_group.security_group_id] + + allowed_triggers = { + AllowExecutionFromAPIGateway = { + service = "apigateway" + source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*/*" + } + } +} + +module "lambda_security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "lambda-sg-${random_pet.this.id}" + description = "Lambda security group for example usage" + vpc_id = module.vpc.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.api_gateway_security_group.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] +} diff --git a/modules/aws-apigateway-v2-master/examples/vpc-link-http/outputs.tf b/modules/aws-apigateway-v2-master/examples/vpc-link-http/outputs.tf new file mode 100644 index 0000000..79505ab --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/vpc-link-http/outputs.tf @@ -0,0 +1,15 @@ +# API Gateway +output "apigatewayv2_api_endpoint" { + description = "The URI of the API" + value = module.api_gateway.apigatewayv2_api_api_endpoint +} + +output "apigatewayv2_vpc_link_arn" { + description = "The ARN of the VPC Link" + value = module.api_gateway.apigatewayv2_vpc_link_arn +} + +output "apigatewayv2_vpc_link_id" { + description = "The identifier of the VPC Link" + value = module.api_gateway.apigatewayv2_vpc_link_id +} diff --git a/modules/aws-apigateway-v2-master/examples/vpc-link-http/variables.tf b/modules/aws-apigateway-v2-master/examples/vpc-link-http/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-apigateway-v2-master/examples/vpc-link-http/versions.tf b/modules/aws-apigateway-v2-master/examples/vpc-link-http/versions.tf new file mode 100644 index 0000000..9bc3a3c --- /dev/null +++ b/modules/aws-apigateway-v2-master/examples/vpc-link-http/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-apigateway-v2-master/main.tf b/modules/aws-apigateway-v2-master/main.tf new file mode 100644 index 0000000..cecdfe2 --- /dev/null +++ b/modules/aws-apigateway-v2-master/main.tf @@ -0,0 +1,220 @@ +# API Gateway +resource "aws_apigatewayv2_api" "this" { + count = var.create && var.create_api_gateway ? 1 : 0 + + name = var.name + description = var.description + protocol_type = var.protocol_type + version = var.api_version + body = var.body + + route_selection_expression = var.route_selection_expression + api_key_selection_expression = var.api_key_selection_expression + disable_execute_api_endpoint = var.disable_execute_api_endpoint + + /* Start of quick create */ + route_key = var.route_key + credentials_arn = var.credentials_arn + target = var.target + /* End of quick create */ + + dynamic "cors_configuration" { + for_each = length(keys(var.cors_configuration)) == 0 ? [] : [var.cors_configuration] + + content { + allow_credentials = try(cors_configuration.value.allow_credentials, null) + allow_headers = try(cors_configuration.value.allow_headers, null) + allow_methods = try(cors_configuration.value.allow_methods, null) + allow_origins = try(cors_configuration.value.allow_origins, null) + expose_headers = try(cors_configuration.value.expose_headers, null) + max_age = try(cors_configuration.value.max_age, null) + } + } + + tags = var.tags +} + +# Domain name +resource "aws_apigatewayv2_domain_name" "this" { + count = var.create && var.create_api_domain_name ? 1 : 0 + + domain_name = var.domain_name + + domain_name_configuration { + certificate_arn = var.domain_name_certificate_arn + ownership_verification_certificate_arn = var.domain_name_ownership_verification_certificate_arn + endpoint_type = "REGIONAL" + security_policy = "TLS_1_2" + } + + dynamic "mutual_tls_authentication" { + for_each = length(keys(var.mutual_tls_authentication)) == 0 ? [] : [var.mutual_tls_authentication] + + content { + truststore_uri = mutual_tls_authentication.value.truststore_uri + truststore_version = try(mutual_tls_authentication.value.truststore_version, null) + } + } + + tags = merge(var.domain_name_tags, var.tags) +} + +# Default stage +resource "aws_apigatewayv2_stage" "default" { + count = var.create && var.create_default_stage ? 1 : 0 + + api_id = aws_apigatewayv2_api.this[0].id + name = "$default" + auto_deploy = true + + dynamic "access_log_settings" { + for_each = var.default_stage_access_log_destination_arn != null && var.default_stage_access_log_format != null ? [true] : [] + + content { + destination_arn = var.default_stage_access_log_destination_arn + format = var.default_stage_access_log_format + } + } + + dynamic "default_route_settings" { + for_each = length(keys(var.default_route_settings)) == 0 ? [] : [var.default_route_settings] + + content { + data_trace_enabled = try(default_route_settings.value.data_trace_enabled, false) # supported in Websocket APIGateway only + logging_level = try(default_route_settings.value.logging_level, null) # supported in Websocket APIGateway only + + detailed_metrics_enabled = try(default_route_settings.value.detailed_metrics_enabled, false) + throttling_burst_limit = try(default_route_settings.value.throttling_burst_limit, null) + throttling_rate_limit = try(default_route_settings.value.throttling_rate_limit, null) + } + } + + dynamic "route_settings" { + for_each = { for k, v in var.integrations : k => v if var.create_routes_and_integrations && length(setintersection(["data_trace_enabled", "detailed_metrics_enabled", "logging_level", "throttling_burst_limit", "throttling_rate_limit"], keys(v))) > 0 } + + content { + route_key = route_settings.key + data_trace_enabled = try(route_settings.value.data_trace_enabled, var.default_route_settings["data_trace_enabled"], false) # supported in Websocket APIGateway only + logging_level = try(route_settings.value.logging_level, var.default_route_settings["logging_level"], null) # supported in Websocket APIGateway only + + detailed_metrics_enabled = try(route_settings.value.detailed_metrics_enabled, var.default_route_settings["detailed_metrics_enabled"], false) + throttling_burst_limit = try(route_settings.value.throttling_burst_limit, var.default_route_settings["throttling_burst_limit"], null) + throttling_rate_limit = try(route_settings.value.throttling_rate_limit, var.default_route_settings["throttling_rate_limit"], null) + } + } + + tags = merge(var.default_stage_tags, var.tags) + + # Bug in terraform-aws-provider with perpetual diff + lifecycle { + ignore_changes = [deployment_id] + } +} + +# Default API mapping +resource "aws_apigatewayv2_api_mapping" "this" { + count = var.create && var.create_api_domain_name && var.create_default_stage && var.create_default_stage_api_mapping ? 1 : 0 + + api_id = aws_apigatewayv2_api.this[0].id + domain_name = aws_apigatewayv2_domain_name.this[0].id + stage = aws_apigatewayv2_stage.default[0].id +} + +# Routes and integrations +resource "aws_apigatewayv2_route" "this" { + for_each = var.create && var.create_routes_and_integrations ? var.integrations : {} + + api_id = aws_apigatewayv2_api.this[0].id + route_key = each.key + + api_key_required = try(each.value.api_key_required, null) + authorization_scopes = try(split(",", each.value.authorization_scopes), null) + authorization_type = try(each.value.authorization_type, "NONE") + authorizer_id = try(aws_apigatewayv2_authorizer.this[each.value.authorizer_key].id, each.value.authorizer_id, null) + model_selection_expression = try(each.value.model_selection_expression, null) + operation_name = try(each.value.operation_name, null) + route_response_selection_expression = try(each.value.route_response_selection_expression, null) + target = "integrations/${aws_apigatewayv2_integration.this[each.key].id}" + + # Have been added to the docs. But is WEBSOCKET only(not yet supported) + # request_models = try(each.value.request_models, null) +} + +resource "aws_apigatewayv2_integration" "this" { + for_each = var.create && var.create_routes_and_integrations ? var.integrations : {} + + api_id = aws_apigatewayv2_api.this[0].id + description = try(each.value.description, null) + + integration_type = try(each.value.integration_type, try(each.value.lambda_arn, "") != "" ? "AWS_PROXY" : "MOCK") + integration_subtype = try(each.value.integration_subtype, null) + integration_method = try(each.value.integration_method, try(each.value.integration_subtype, null) == null ? "POST" : null) + integration_uri = try(each.value.lambda_arn, try(each.value.integration_uri, null)) + + connection_type = try(each.value.connection_type, "INTERNET") + connection_id = try(aws_apigatewayv2_vpc_link.this[each.value["vpc_link"]].id, try(each.value.connection_id, null)) + + payload_format_version = try(each.value.payload_format_version, null) + timeout_milliseconds = try(each.value.timeout_milliseconds, null) + passthrough_behavior = try(each.value.passthrough_behavior, null) + content_handling_strategy = try(each.value.content_handling_strategy, null) + credentials_arn = try(each.value.credentials_arn, null) + request_parameters = try(jsondecode(each.value["request_parameters"]), each.value["request_parameters"], null) + + dynamic "tls_config" { + for_each = flatten([try(jsondecode(each.value["tls_config"]), each.value["tls_config"], [])]) + + content { + server_name_to_verify = tls_config.value["server_name_to_verify"] + } + } + + dynamic "response_parameters" { + for_each = flatten([try(jsondecode(each.value["response_parameters"]), each.value["response_parameters"], [])]) + + content { + status_code = response_parameters.value["status_code"] + mappings = response_parameters.value["mappings"] + } + } + + lifecycle { + create_before_destroy = true + } +} + +# Authorizers +resource "aws_apigatewayv2_authorizer" "this" { + for_each = var.create && var.create_routes_and_integrations ? var.authorizers : {} + + api_id = aws_apigatewayv2_api.this[0].id + + authorizer_type = try(each.value.authorizer_type, null) + identity_sources = try(flatten([each.value.identity_sources]), null) + name = try(each.value.name, null) + authorizer_uri = try(each.value.authorizer_uri, null) + authorizer_payload_format_version = try(each.value.authorizer_payload_format_version, null) + authorizer_result_ttl_in_seconds = try(each.value.authorizer_result_ttl_in_seconds, null) + authorizer_credentials_arn = try(each.value.authorizer_credentials_arn, null) + enable_simple_responses = try(each.value.enable_simple_responses, null) + + dynamic "jwt_configuration" { + for_each = length(try(each.value.audience, [each.value.issuer], [])) > 0 ? [true] : [] + + content { + audience = try(each.value.audience, null) + issuer = try(each.value.issuer, null) + } + } +} + +# VPC Link (Private API) +resource "aws_apigatewayv2_vpc_link" "this" { + for_each = var.create && var.create_vpc_link ? var.vpc_links : {} + + name = try(each.value.name, each.key) + security_group_ids = each.value["security_group_ids"] + subnet_ids = each.value["subnet_ids"] + + tags = merge(var.tags, var.vpc_link_tags, try(each.value.tags, {})) +} diff --git a/modules/aws-apigateway-v2-master/outputs.tf b/modules/aws-apigateway-v2-master/outputs.tf new file mode 100644 index 0000000..4bb3a97 --- /dev/null +++ b/modules/aws-apigateway-v2-master/outputs.tf @@ -0,0 +1,104 @@ +output "apigatewayv2_api_id" { + description = "The API identifier" + value = try(aws_apigatewayv2_api.this[0].id, "") +} + +output "apigatewayv2_api_api_endpoint" { + description = "The URI of the API" + value = try(aws_apigatewayv2_api.this[0].api_endpoint, "") +} + +output "apigatewayv2_api_arn" { + description = "The ARN of the API" + value = try(aws_apigatewayv2_api.this[0].arn, "") +} + +output "apigatewayv2_api_execution_arn" { + description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." + value = try(aws_apigatewayv2_api.this[0].execution_arn, "") +} + +# default stage +output "default_apigatewayv2_stage_id" { + description = "The default stage identifier" + value = try(aws_apigatewayv2_stage.default[0].id, "") +} + +output "default_apigatewayv2_stage_arn" { + description = "The default stage ARN" + value = try(aws_apigatewayv2_stage.default[0].arn, "") +} + +output "default_apigatewayv2_stage_execution_arn" { + description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." + value = try(aws_apigatewayv2_stage.default[0].execution_arn, "") +} + +output "default_apigatewayv2_stage_invoke_url" { + description = "The URL to invoke the API pointing to the stage" + value = try(aws_apigatewayv2_stage.default[0].invoke_url, "") +} + +output "default_apigatewayv2_stage_domain_name" { + description = "Domain name of the stage (useful for CloudFront distribution)" + value = replace(try(aws_apigatewayv2_stage.default[0].invoke_url, ""), "/^https?://([^/]*).*/", "$1") +} + +# domain name +output "apigatewayv2_domain_name_id" { + description = "The domain name identifier" + value = try(aws_apigatewayv2_domain_name.this[0].id, "") +} + +output "apigatewayv2_domain_name_arn" { + description = "The ARN of the domain name" + value = try(aws_apigatewayv2_domain_name.this[0].arn, "") +} + +output "apigatewayv2_domain_name_api_mapping_selection_expression" { + description = "The API mapping selection expression for the domain name" + value = try(aws_apigatewayv2_domain_name.this[0].api_mapping_selection_expression, "") +} + +output "apigatewayv2_domain_name_configuration" { + description = "The domain name configuration" + value = try(aws_apigatewayv2_domain_name.this[0].domain_name_configuration, "") +} + +output "apigatewayv2_domain_name_target_domain_name" { + description = "The target domain name" + value = try(aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name, "") +} + +output "apigatewayv2_domain_name_hosted_zone_id" { + description = "The Amazon Route 53 Hosted Zone ID of the endpoint" + value = try(aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id, "") +} + +# api mapping +output "apigatewayv2_api_mapping_id" { + description = "The API mapping identifier." + value = try(aws_apigatewayv2_api_mapping.this[0].id, "") +} + +# route +# output "apigatewayv2_route_id" { +# description = "The default route identifier." +# value = try(aws_apigatewayv2_route.this[0].id, "") +# } + +# VPC link +output "apigatewayv2_vpc_link_id" { + description = "The map of VPC Link identifiers" + value = { for k, v in aws_apigatewayv2_vpc_link.this : k => v.id } +} + +output "apigatewayv2_vpc_link_arn" { + description = "The map of VPC Link ARNs" + value = { for k, v in aws_apigatewayv2_vpc_link.this : k => v.arn } +} + +output "apigatewayv2_authorizer_id" { + description = "The map of API Gateway Authorizer identifiers" + value = { for k, v in aws_apigatewayv2_authorizer.this : k => v.id } +} diff --git a/modules/aws-apigateway-v2-master/variables.tf b/modules/aws-apigateway-v2-master/variables.tf new file mode 100644 index 0000000..2efb2cb --- /dev/null +++ b/modules/aws-apigateway-v2-master/variables.tf @@ -0,0 +1,221 @@ +variable "create" { + description = "Controls if API Gateway resources should be created" + type = bool + default = true +} + +variable "create_api_gateway" { + description = "Whether to create API Gateway" + type = bool + default = true +} + +variable "create_default_stage" { + description = "Whether to create default stage" + type = bool + default = true +} + +variable "create_default_stage_api_mapping" { + description = "Whether to create default stage API mapping" + type = bool + default = true +} + +# variable "create_stage" { +# description = "Whether to create custom stage" +# type = bool +# default = false +# } +# +# variable "create_stage_api_mapping" { +# description = "Whether to create stage API mapping" +# type = bool +# default = false +# } + +variable "create_api_domain_name" { + description = "Whether to create API domain name resource" + type = bool + default = true +} + +variable "create_routes_and_integrations" { + description = "Whether to create routes and integrations resources" + type = bool + default = true +} + +variable "create_vpc_link" { + description = "Whether to create VPC links" + type = bool + default = true +} + +# API Gateway +variable "name" { + description = "The name of the API" + type = string + default = "" +} + +variable "description" { + description = "The description of the API." + type = string + default = null +} + +variable "default_route_settings" { + description = "Settings for default route" + type = map(string) + default = {} +} + +variable "disable_execute_api_endpoint" { + description = "Whether clients can invoke the API by using the default execute-api endpoint. To require that clients use a custom domain name to invoke the API, disable the default endpoint" + type = string + default = false +} + +variable "protocol_type" { + description = "The API protocol. Valid values: HTTP, WEBSOCKET" + type = string + default = "HTTP" +} + +variable "api_key_selection_expression" { + description = "An API key selection expression. Valid values: $context.authorizer.usageIdentifierKey, $request.header.x-api-key." + type = string + default = "$request.header.x-api-key" +} + +variable "route_key" { + description = "Part of quick create. Specifies any route key. Applicable for HTTP APIs." + type = string + default = null +} + +variable "route_selection_expression" { + description = "The route selection expression for the API." + type = string + default = "$request.method $request.path" +} + +variable "cors_configuration" { + description = "The cross-origin resource sharing (CORS) configuration. Applicable for HTTP APIs." + type = any + default = {} +} + +variable "credentials_arn" { + description = "Part of quick create. Specifies any credentials required for the integration. Applicable for HTTP APIs." + type = string + default = null +} + +variable "target" { + description = "Part of quick create. Quick create produces an API with an integration, a default catch-all route, and a default stage which is configured to automatically deploy changes. For HTTP integrations, specify a fully qualified URL. For Lambda integrations, specify a function ARN. The type of the integration will be HTTP_PROXY or AWS_PROXY, respectively. Applicable for HTTP APIs." + type = string + default = null +} + +variable "body" { + description = "An OpenAPI specification that defines the set of routes and integrations to create as part of the HTTP APIs. Supported only for HTTP APIs." + type = string + default = null +} + +variable "api_version" { + description = "A version identifier for the API" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to API gateway resources." + type = map(string) + default = {} +} + +##### +# default stage +variable "default_stage_access_log_destination_arn" { + description = "Default stage's ARN of the CloudWatch Logs log group to receive access logs. Any trailing :* is trimmed from the ARN." + type = string + default = null +} + +variable "default_stage_access_log_format" { + description = "Default stage's single line format of the access logs of data, as specified by selected $context variables." + type = string + default = null +} + +variable "default_stage_tags" { + description = "A mapping of tags to assign to the default stage resource." + type = map(string) + default = {} +} + +##### +# default stage API mapping + +#### +# domain name +variable "domain_name" { + description = "The domain name to use for API gateway" + type = string + default = null +} + +variable "domain_name_certificate_arn" { + description = "The ARN of an AWS-managed certificate that will be used by the endpoint for the domain name" + type = string + default = null +} + +variable "domain_name_ownership_verification_certificate_arn" { + description = "ARN of the AWS-issued certificate used to validate custom domain ownership (when certificate_arn is issued via an ACM Private CA or mutual_tls_authentication is configured with an ACM-imported certificate.)" + type = string + default = null +} + +variable "domain_name_tags" { + description = "A mapping of tags to assign to API domain name resource." + type = map(string) + default = {} +} + +variable "mutual_tls_authentication" { + description = "An Amazon S3 URL that specifies the truststore for mutual TLS authentication as well as version, keyed at uri and version" + type = map(string) + default = {} +} + +#### +# routes and integrations +variable "integrations" { + description = "Map of API gateway routes with integrations" + type = map(any) + default = {} +} + +# authorrizers +variable "authorizers" { + description = "Map of API gateway authorizers" + type = map(any) + default = {} +} + +# vpc link +variable "vpc_links" { + description = "Map of VPC Links details to create" + type = map(any) + default = {} +} + +variable "vpc_link_tags" { + description = "A map of tags to add to the VPC Link" + type = map(string) + default = {} +} diff --git a/modules/aws-apigateway-v2-master/versions.tf b/modules/aws-apigateway-v2-master/versions.tf new file mode 100644 index 0000000..3752560 --- /dev/null +++ b/modules/aws-apigateway-v2-master/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-centralized-logging/README.md b/modules/aws-centralized-logging/README.md new file mode 100644 index 0000000..0954da1 --- /dev/null +++ b/modules/aws-centralized-logging/README.md @@ -0,0 +1,234 @@ + + +# terraform-aws-centralized-logging module + + + +Terraform module to provision a centralized logging service + + + +## Introduction + +This module will create: +- Elasticsearch(Opensearch) cluster with the specified node count in the provided subnets in a VPC +- Elasticsearch domain policy that accepts a list of IAM role ARNs from which to permit management traffic to the cluster +- Security Group to control access to the Elasticsearch domain (inputs to the Security Group are other Security Groups or CIDRs blocks to be allowed to connect to the cluster) +- DNS hostname record for Elasticsearch cluster (if DNS Zone ID is provided) +- DNS hostname record for Kibana (if DNS Zone ID is provided) +- Kinesis stream for streaming +- Kinesis delivery stream for sending logs to Elasticsearch +- Cloudwatch log group for failure logs of delivery stream +- S3 bucket for backup of delivery stream +- IAM role and policy for delivery stream to put logs to Elasticsearch, Cloudwatch etc. +- + +__NOTE:__ To enable [zone awareness](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-managedomains.html#es-managedomains-zoneawareness) to deploy Elasticsearch nodes into two different Availability Zones, you need to set `zone_awareness_enabled` to `true` and provide two different subnets in `subnet_ids`. +If you enable zone awareness for your domain, Amazon ES places an endpoint into two subnets. +The subnets must be in different Availability Zones in the same region. +If you don't enable zone awareness, Amazon ES places an endpoint into only one subnet. You also need to set `availability_zone_count` to `1`. + + + +## Usage + + + + +```hcl +module "elasticsearch" { + + domain_name = "es" + security_groups = ["sg-XXXXXXXXX", "sg-YYYYYYYY"] + vpc_id = "vpc-XXXXXXXXX" + subnet_ids = ["subnet-XXXXXXXXX", "subnet-YYYYYYYY"] + zone_awareness_enabled = "true" + elasticsearch_version = "6.5" + instance_type = "t2.small.elasticsearch" + instance_count = 4 + ebs_volume_size = 10 + iam_role_arns = ["arn:aws:iam::XXXXXXXXX:role/ops", "arn:aws:iam::XXXXXXXXX:role/dev"] + iam_actions = ["es:ESHttpGet", "es:ESHttpPut", "es:ESHttpPost"] + encrypt_at_rest_enabled = true + kibana_subdomain_name = "kibana-es" + + advanced_options = { + "rest.action.multi.allow_explicit_index" = "true" + } + domain_endpoint_options_enforce_https = false + + # Kinesis stream + + kinesis_stream_name = "kinesis stream" + # kinesis_shard_count = 1 # needed to be specified if the stream mode is PROVISIONED + kinesis_stream_retention_period = 24 + kinesis_stream_mode = "ON_DEMAND" + + + # Kinesis Firehose + firehose_role_name = "firehose-role" + firehose_bucket_name = "firehose-bucket" + firehose_policy_name = "firehose-policy" + firehose_name = "logging-delivery-stream" + firehose_es_index_name = "logging" + firehose_index_rotation_period = "OneWeek" + firehose_cw_log_group_name = "/aws/kinesisfirehose/firehose-lg" + firehose_cw_log_stream_name = "DestinationDelivery" + firehose_cw_log_group_retention_in_days = 30 +} +``` + + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3.35.0 | +| [null](#requirement\_null) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.35.0 | + + +## Resources + +| Name | Type | +|------|------| +| [aws_elasticsearch_domain.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain) | resource | +| [aws_elasticsearch_domain_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain_policy) | resource | +| [aws_iam_role.elasticsearch_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_service_linked_role.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_service_linked_role) | resource | +| [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_security_groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_role.firehose](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.firehose-elasticsearch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_s3_bucket.firehose_backup_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_kinesis_stream.stream](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_stream) | resource | +| [aws_kinesis_firehose_delivery_stream.firehose_stream](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream) | resource | +| [aws_cloudwatch_log_group.firehose_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_stream.firehose_log_stream](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_stream) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [advanced\_options](#input\_advanced\_options) | Key-value string pairs to specify advanced configuration options | `map(string)` | `{}` | no | +| [advanced\_security\_options\_enabled](#input\_advanced\_security\_options\_enabled) | AWS Elasticsearch Kibana enchanced security plugin enabling (forces new resource) | `bool` | `false` | no | +| [advanced\_security\_options\_internal\_user\_database\_enabled](#input\_advanced\_security\_options\_internal\_user\_database\_enabled) | Whether to enable or not internal Kibana user database for ELK OpenDistro security plugin | `bool` | `false` | no | +| [advanced\_security\_options\_master\_user\_arn](#input\_advanced\_security\_options\_master\_user\_arn) | ARN of IAM user who is to be mapped to be Kibana master user (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to false) | `string` | `""` | no | +| [advanced\_security\_options\_master\_user\_name](#input\_advanced\_security\_options\_master\_user\_name) | Master user username (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to true) | `string` | `""` | no | +| [advanced\_security\_options\_master\_user\_password](#input\_advanced\_security\_options\_master\_user\_password) | Master user password (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to true) | `string` | `""` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of CIDR blocks to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [automated\_snapshot\_start\_hour](#input\_automated\_snapshot\_start\_hour) | Hour at which automated snapshots are taken, in UTC | `number` | `0` | no | +| [availability\_zone\_count](#input\_availability\_zone\_count) | Number of Availability Zones for the domain to use. | `number` | `2` | no | +| [aws\_ec2\_service\_name](#input\_aws\_ec2\_service\_name) | AWS EC2 Service Name | `list(string)` |
[
"ec2.amazonaws.com"
]
| no | +| [cognito\_authentication\_enabled](#input\_cognito\_authentication\_enabled) | Whether to enable Amazon Cognito authentication with Kibana | `bool` | `false` | no | +| [cognito\_iam\_role\_arn](#input\_cognito\_iam\_role\_arn) | ARN of the IAM role that has the AmazonESCognitoAccess policy attached | `string` | `""` | no | +| [cognito\_identity\_pool\_id](#input\_cognito\_identity\_pool\_id) | The ID of the Cognito Identity Pool to use | `string` | `""` | no | +| [cognito\_user\_pool\_id](#input\_cognito\_user\_pool\_id) | The ID of the Cognito User Pool to use | `string` | `""` | no | +| [create\_iam\_service\_linked\_role](#input\_create\_iam\_service\_linked\_role) | Whether to create `AWSServiceRoleForAmazonElasticsearchService` service-linked role. Set it to `false` if you already have an ElasticSearch cluster created in the AWS account and AWSServiceRoleForAmazonElasticsearchService already exists. See https://github.com/terraform-providers/terraform-provider-aws/issues/5218 for more info | `bool` | `true` | no | +| [custom\_endpoint](#input\_custom\_endpoint) | Fully qualified domain for custom endpoint. | `string` | `""` | no | +| [custom\_endpoint\_certificate\_arn](#input\_custom\_endpoint\_certificate\_arn) | ACM certificate ARN for custom endpoint. | `string` | `""` | no | +| [custom\_endpoint\_enabled](#input\_custom\_endpoint\_enabled) | Whether to enable custom endpoint for the Elasticsearch domain. | `bool` | `false` | no | +| [dedicated\_master\_count](#input\_dedicated\_master\_count) | Number of dedicated master nodes in the cluster | `number` | `0` | no | +| [dedicated\_master\_enabled](#input\_dedicated\_master\_enabled) | Indicates whether dedicated master nodes are enabled for the cluster | `bool` | `false` | no | +| [dedicated\_master\_type](#input\_dedicated\_master\_type) | Instance type of the dedicated master nodes in the cluster | `string` | `"t2.small.elasticsearch"` | no | +| [dns\_zone\_id](#input\_dns\_zone\_id) | Route53 DNS Zone ID to add hostname records for Elasticsearch domain and Kibana | `string` | `""` | no | +| [domain\_endpoint\_options\_enforce\_https](#input\_domain\_endpoint\_options\_enforce\_https) | Whether or not to require HTTPS | `bool` | `true` | no | +| [domain\_endpoint\_options\_tls\_security\_policy](#input\_domain\_endpoint\_options\_tls\_security\_policy) | The name of the TLS security policy that needs to be applied to the HTTPS endpoint | `string` | `"Policy-Min-TLS-1-0-2019-07"` | no | +| [domain\_hostname\_enabled](#input\_domain\_hostname\_enabled) | Explicit flag to enable creating a DNS hostname for ES. If `true`, then `var.dns_zone_id` is required. | `bool` | `false` | no | +| [ebs\_iops](#input\_ebs\_iops) | The baseline input/output (I/O) performance of EBS volumes attached to data nodes. Applicable only for the Provisioned IOPS EBS volume type | `number` | `0` | no | +| [ebs\_volume\_size](#input\_ebs\_volume\_size) | EBS volumes for data storage in GB | `number` | `0` | no | +| [ebs\_volume\_type](#input\_ebs\_volume\_type) | Storage type of EBS volumes | `string` | `"gp2"` | no | +| [elasticsearch\_subdomain\_name](#input\_elasticsearch\_subdomain\_name) | The name of the subdomain for Elasticsearch in the DNS zone (\_e.g.\_ `elasticsearch`, `ui`, `ui-es`, `search-ui`) | `string` | `""` | no | +| [elasticsearch\_version](#input\_elasticsearch\_version) | Version of Elasticsearch to deploy (\_e.g.\_ `7.4`, `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5` | `string` | `"7.4"` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [encrypt\_at\_rest\_enabled](#input\_encrypt\_at\_rest\_enabled) | Whether to enable encryption at rest | `bool` | `true` | no | +| [encrypt\_at\_rest\_kms\_key\_id](#input\_encrypt\_at\_rest\_kms\_key\_id) | The KMS key ID to encrypt the Elasticsearch domain with. If not specified, then it defaults to using the AWS/Elasticsearch service KMS key | `string` | `""` | no | +| [iam\_actions](#input\_iam\_actions) | List of actions to allow for the IAM roles, _e.g._ `es:ESHttpGet`, `es:ESHttpPut`, `es:ESHttpPost` | `list(string)` | `[]` | no | +| [iam\_authorizing\_role\_arns](#input\_iam\_authorizing\_role\_arns) | List of IAM role ARNs to permit to assume the Elasticsearch user role | `list(string)` | `[]` | no | +| [iam\_role\_arns](#input\_iam\_role\_arns) | List of IAM role ARNs to permit access to the Elasticsearch domain | `list(string)` | `[]` | no | +| [iam\_role\_max\_session\_duration](#input\_iam\_role\_max\_session\_duration) | The maximum session duration (in seconds) for the user role. Can have a value from 1 hour to 12 hours | `number` | `3600` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | The ARN of the permissions boundary policy which will be attached to the Elasticsearch user role | `string` | `null` | no | +| [ingress\_port\_range\_end](#input\_ingress\_port\_range\_end) | End number for allowed port range. (e.g. `443`) | `number` | `65535` | no | +| [ingress\_port\_range\_start](#input\_ingress\_port\_range\_start) | Start number for allowed port range. (e.g. `443`) | `number` | `0` | no | +| [instance\_count](#input\_instance\_count) | Number of data nodes in the cluster | `number` | `4` | no | +| [instance\_type](#input\_instance\_type) | Elasticsearch instance type for data nodes in the cluster | `string` | `"t2.small.elasticsearch"` | no | +| [kibana\_hostname\_enabled](#input\_kibana\_hostname\_enabled) | Explicit flag to enable creating a DNS hostname for Kibana. If `true`, then `var.dns_zone_id` is required. | `bool` | `false` | no | +| [kibana\_subdomain\_name](#input\_kibana\_subdomain\_name) | The name of the subdomain for Kibana in the DNS zone (\_e.g.\_ `kibana`, `ui`, `ui-es`, `search-ui`, `kibana.elasticsearch`) | `string` | `""` | no | +| [log\_publishing\_application\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_application\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for ES\_APPLICATION\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_application\_enabled](#input\_log\_publishing\_application\_enabled) | Specifies whether log publishing option for ES\_APPLICATION\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_audit\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_audit\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for AUDIT\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_audit\_enabled](#input\_log\_publishing\_audit\_enabled) | Specifies whether log publishing option for AUDIT\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_index\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_index\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for INDEX\_SLOW\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_index\_enabled](#input\_log\_publishing\_index\_enabled) | Specifies whether log publishing option for INDEX\_SLOW\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_search\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_search\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for SEARCH\_SLOW\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_search\_enabled](#input\_log\_publishing\_search\_enabled) | Specifies whether log publishing option for SEARCH\_SLOW\_LOGS is enabled or not | `bool` | `false` | no | +| [node\_to\_node\_encryption\_enabled](#input\_node\_to\_node\_encryption\_enabled) | Whether to enable node-to-node encryption | `bool` | `false` | no | +| [security\_groups](#input\_security\_groups) | List of security group IDs to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [subnet\_ids](#input\_subnet\_ids) | VPC Subnet IDs | `list(string)` | `[]` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [vpc\_enabled](#input\_vpc\_enabled) | Set to false if ES should be deployed outside of VPC. | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | VPC ID | `string` | `null` | no | +| [warm\_count](#input\_warm\_count) | Number of UltraWarm nodes | `number` | `2` | no | +| [warm\_enabled](#input\_warm\_enabled) | Whether AWS UltraWarm is enabled | `bool` | `false` | no | +| [warm\_type](#input\_warm\_type) | Type of UltraWarm nodes | `string` | `"ultrawarm1.medium.elasticsearch"` | no | +| [zone\_awareness\_enabled](#input\_zone\_awareness\_enabled) | Enable zone awareness for Elasticsearch cluster | `bool` | `true` | no | +| [kinesis\_stream\_name](#input\_kinesis\_stream\_name) | Name of the Kinesis Stream | `string` | `""` | yes | +| [kinesis\_shard\_count](#input\_kinesis\_shard\_count) | Shard count of the Kinesis Stream \needed if the stream mode is provisioned! | `number` | `1` | no | +| [kinesis\_stream\_retention\_period](#input\_kinesis\_stream\_retention\_period) | Kinesis stream retention period | `number` | `1` | yes | +| [kinesis\_stream\_mode](#input\_kinesis\_stream\_mode) | Kinesis stream mode ONDEMAND/PROVISIONED | `bool` | `true` | yes | +| [firehose\_role\_name](#input\_firehose\_role\_name) | Firehose role name to for related services | `bool` | `true` | yes | +| [firehose\_bucket\_name](#input\_firehose\_bucket\_name) | Firehose bucket name for operations that it does | `bool` | `true` | yes | +| [firehose\_policy\_name](#input\_firehose\_policy\_name) | Firehose policy name | `bool` | `true` | yes | +| [firehose\_name](#input\_firehose\_name) | Firehose name | `bool` | `true` | yes | +| [firehose\_es\_index\_name](#input\_firehose\_es\_index\_name) | Firehose es index name | `bool` | `true` | yes | +| [firehose\_index\_rotation\_period](#input\_firehose\_index\_rotation\_period) | Firehose index rotation period | `bool` | `true` | yes | +| [firehose\_cw\_log\_group\_name](#input\_firehose\_cw\_log\_group\_name) | Firehose Cloudwatch log group name | `bool` | `true` | yes | +| [firehose\_cw\_log\_stream\_name](#input\_firehose\_cw\_log\_stream\_name) | Firehose Cloudwatch log group name | `bool` | `true` | yes | +| [firehose\_cw\_log\_group\_retention\_in\_days](#input\_firehose\_cw\_log\_group\_retention\_in\_days) | Retention time | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [domain\_arn](#output\_domain\_arn) | ARN of the Elasticsearch domain | +| [domain\_endpoint](#output\_domain\_endpoint) | Domain-specific endpoint used to submit index, search, and data upload requests | +| [domain\_hostname](#output\_domain\_hostname) | Elasticsearch domain hostname to submit index, search, and data upload requests | +| [domain\_id](#output\_domain\_id) | Unique identifier for the Elasticsearch domain | +| [domain\_name](#output\_domain\_name) | Name of the Elasticsearch domain | +| [elasticsearch\_user\_iam\_role\_arn](#output\_elasticsearch\_user\_iam\_role\_arn) | The ARN of the IAM role to allow access to Elasticsearch cluster | +| [elasticsearch\_user\_iam\_role\_name](#output\_elasticsearch\_user\_iam\_role\_name) | The name of the IAM role to allow access to Elasticsearch cluster | +| [kibana\_endpoint](#output\_kibana\_endpoint) | Domain-specific endpoint for Kibana without https scheme | +| [kibana\_hostname](#output\_kibana\_hostname) | Kibana hostname | +| [security\_group\_id](#output\_security\_group\_id) | Security Group ID to control access to the Elasticsearch domain | + + + + + + +## References + +For additional context, refer to some of these links. + +- [What is Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/what-is-amazon-elasticsearch-service.html) - Complete description of Amazon Elasticsearch Service +- [Amazon Elasticsearch Service Access Control](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) - Describes several ways of controlling access to Elasticsearch domains +- [VPC Support for Amazon Elasticsearch Service Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html) - Describes Elasticsearch Service VPC Support and VPC architectures with and without zone awareness +- [Creating and Configuring Amazon Elasticsearch Service Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html) - Provides a complete description on how to create and configure Amazon Elasticsearch Service (Amazon ES) domains +- [Kibana and Logstash](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-kibana.html) - Describes some considerations for using Kibana and Logstash with Amazon Elasticsearch Service +- [Amazon Cognito Authentication for Kibana](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-cognito-auth.html) - Amazon Elasticsearch Service uses Amazon Cognito to offer user name and password protection for Kibana +- [Control Access to Amazon Elasticsearch Service Domain](https://aws.amazon.com/blogs/security/how-to-control-access-to-your-amazon-elasticsearch-service-domain/) - Describes how to Control Access to Amazon Elasticsearch Service Domain +- [elasticsearch_domain](https://www.terraform.io/docs/providers/aws/r/elasticsearch_domain.html) - Terraform reference documentation for the `elasticsearch_domain` resource +- [elasticsearch_domain_policy](https://www.terraform.io/docs/providers/aws/r/elasticsearch_domain_policy.html) - Terraform reference documentation for the `elasticsearch_domain_policy` resource +- [centralized_logging_architecture](https://docs.aws.amazon.com/solutions/latest/centralized-logging-on-aws/overview.html) - AWS reference documentation for the architecture \ No newline at end of file diff --git a/modules/aws-centralized-logging/main.tf b/modules/aws-centralized-logging/main.tf new file mode 100644 index 0000000..53d39d1 --- /dev/null +++ b/modules/aws-centralized-logging/main.tf @@ -0,0 +1,411 @@ +# https://github.com/terraform-providers/terraform-provider-aws/issues/5218 +# resource "aws_iam_service_linked_role" "default" { +# count = var.enabled && var.create_iam_service_linked_role ? 1 : 0 +# aws_service_name = "es.amazonaws.com" +# description = "AWSServiceRoleForAmazonElasticsearchService Service-Linked Role" +# custom_suffix = "OpenSearchLogging" +# } + +# Role that pods can assume for access to elasticsearch and kibana +resource "aws_iam_role" "elasticsearch_user" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = "${var.domain_name}-es-user-role" + assume_role_policy = join("", data.aws_iam_policy_document.assume_role.*.json) + description = "IAM Role to assume to access the Elasticsearch ${var.domain_name} cluster" + tags = var.tags + + max_session_duration = var.iam_role_max_session_duration + + permissions_boundary = var.iam_role_permissions_boundary +} + +data "aws_iam_policy_document" "assume_role" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + + statement { + actions = [ + "sts:AssumeRole" + ] + + principals { + type = "Service" + identifiers = var.aws_ec2_service_name + } + + principals { + type = "AWS" + identifiers = compact(concat(var.iam_authorizing_role_arns, var.iam_role_arns)) + } + + effect = "Allow" + } +} + +resource "aws_elasticsearch_domain" "default" { + count = var.enabled ? 1 : 0 + domain_name = var.domain_name + elasticsearch_version = var.elasticsearch_version + + advanced_options = var.advanced_options + + advanced_security_options { + enabled = var.advanced_security_options_enabled + internal_user_database_enabled = var.advanced_security_options_internal_user_database_enabled + master_user_options { + master_user_arn = var.advanced_security_options_master_user_arn + master_user_name = var.advanced_security_options_master_user_name + master_user_password = var.advanced_security_options_master_user_password + } + } + + + + ebs_options { + ebs_enabled = var.ebs_volume_size > 0 ? true : false + volume_size = var.ebs_volume_size + volume_type = var.ebs_volume_type + iops = var.ebs_iops + } + + encrypt_at_rest { + enabled = var.encrypt_at_rest_enabled + kms_key_id = var.encrypt_at_rest_kms_key_id + } + + + domain_endpoint_options { + enforce_https = var.domain_endpoint_options_enforce_https + tls_security_policy = var.domain_endpoint_options_tls_security_policy + custom_endpoint_enabled = var.custom_endpoint_enabled + custom_endpoint = var.custom_endpoint_enabled ? var.custom_endpoint : null + custom_endpoint_certificate_arn = var.custom_endpoint_enabled ? var.custom_endpoint_certificate_arn : null + } + + cluster_config { + instance_count = var.instance_count + instance_type = var.instance_type + dedicated_master_enabled = var.dedicated_master_enabled + dedicated_master_count = var.dedicated_master_count + dedicated_master_type = var.dedicated_master_type + zone_awareness_enabled = var.zone_awareness_enabled + warm_enabled = var.warm_enabled + warm_count = var.warm_enabled ? var.warm_count : null + warm_type = var.warm_enabled ? var.warm_type : null + + dynamic "zone_awareness_config" { + for_each = var.availability_zone_count > 1 && var.zone_awareness_enabled ? [true] : [] + content { + availability_zone_count = var.availability_zone_count + } + } + } + + node_to_node_encryption { + enabled = var.node_to_node_encryption_enabled + } + + dynamic "vpc_options" { + for_each = var.vpc_enabled ? [true] : [] + + content { + security_group_ids = var.security_groups + subnet_ids = var.subnet_ids + } + } + + snapshot_options { + automated_snapshot_start_hour = var.automated_snapshot_start_hour + } + + dynamic "cognito_options" { + for_each = var.cognito_authentication_enabled ? [true] : [] + content { + enabled = true + user_pool_id = var.cognito_user_pool_id + identity_pool_id = var.cognito_identity_pool_id + role_arn = var.cognito_iam_role_arn + } + } + + + log_publishing_options { + enabled = var.log_publishing_index_enabled + log_type = "INDEX_SLOW_LOGS" + cloudwatch_log_group_arn = var.log_publishing_index_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_search_enabled + log_type = "SEARCH_SLOW_LOGS" + cloudwatch_log_group_arn = var.log_publishing_search_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_audit_enabled + log_type = "AUDIT_LOGS" + cloudwatch_log_group_arn = var.log_publishing_audit_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_application_enabled + log_type = "ES_APPLICATION_LOGS" + cloudwatch_log_group_arn = var.log_publishing_application_cloudwatch_log_group_arn + } + + + + tags = var.tags + + # depends_on = [aws_iam_service_linked_role.default] +} + + + + +data "aws_iam_policy_document" "default" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + + statement { + effect = "Allow" + + actions = distinct(compact(var.iam_actions)) + + resources = [ + join("", aws_elasticsearch_domain.default.*.arn), + "${join("", aws_elasticsearch_domain.default.*.arn)}/*" + ] + + principals { + type = "AWS" + identifiers = distinct(compact(concat(var.iam_role_arns, aws_iam_role.elasticsearch_user.*.arn))) + } + } + + # This statement is for non VPC ES to allow anonymous access from whitelisted IP ranges without requests signing + # https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html#es-ac-types-ip + # https://aws.amazon.com/premiumsupport/knowledge-center/anonymous-not-authorized-elasticsearch/ + dynamic "statement" { + for_each = length(var.allowed_cidr_blocks) > 0 && !var.vpc_enabled ? [true] : [] + content { + effect = "Allow" + + actions = distinct(compact(var.iam_actions)) + + resources = [ + join("", aws_elasticsearch_domain.default.*.arn), + "${join("", aws_elasticsearch_domain.default.*.arn)}/*" + ] + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "IpAddress" + values = var.allowed_cidr_blocks + variable = "aws:SourceIp" + } + } + } +} + +resource "aws_elasticsearch_domain_policy" "default" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + domain_name = var.domain_name + access_policies = join("", data.aws_iam_policy_document.default.*.json) +} + + +resource "aws_kinesis_stream" "stream" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = var.kinesis_stream_name + # shard_count = var.kinesis_shard_count # can be defined when PROVISIONED mode enabled + retention_period = var.kinesis_stream_retention_period + + shard_level_metrics = [ + "IncomingBytes", + "OutgoingBytes", + ] + + stream_mode_details { + stream_mode = var.kinesis_stream_mode + } + +} + + +resource "aws_iam_role" "firehose" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = var.firehose_role_name + + assume_role_policy = < 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = var.firehose_policy_name + role = aws_iam_role.firehose[count.index].id + policy = < 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + bucket = var.firehose_bucket_name +} + +resource "aws_kinesis_firehose_delivery_stream" "firehose_stream" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + depends_on = [aws_iam_role_policy.firehose-elasticsearch] + + name = var.firehose_name + destination = "elasticsearch" + s3_configuration { + role_arn = aws_iam_role.firehose[count.index].arn + bucket_arn = aws_s3_bucket.firehose_backup_bucket[count.index].arn + } + + kinesis_source_configuration { + kinesis_stream_arn = aws_kinesis_stream.stream[count.index].arn + role_arn = aws_iam_role.firehose[count.index].arn + } + + elasticsearch_configuration { + domain_arn = aws_elasticsearch_domain.default[count.index].arn + role_arn = aws_iam_role.firehose[count.index].arn + index_name = var.firehose_es_index_name + index_rotation_period = var.firehose_index_rotation_period + # type_name = var.firehose_es_type_name # it is deprecated in Elasticsearch version 7.10 + + vpc_config { + subnet_ids = var.subnet_ids + security_group_ids = var.security_groups # security_group_ids = [aws_security_group.first.id] + role_arn = aws_iam_role.firehose[count.index].arn + } + cloudwatch_logging_options { + enabled = var.cloudwatch_logging_options_enabled + log_group_name = aws_cloudwatch_log_group.firehose_log_group[count.index].name + log_stream_name = aws_cloudwatch_log_stream.firehose_log_stream[count.index].name + } + } + + +} + +resource "aws_cloudwatch_log_group" "firehose_log_group" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = var.firehose_cw_log_group_name + retention_in_days = var.firehose_cw_log_group_retention_in_days + + +} + + +resource "aws_cloudwatch_log_stream" "firehose_log_stream" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = var.firehose_cw_log_stream_name + log_group_name = aws_cloudwatch_log_group.firehose_log_group[0].name +} + + diff --git a/modules/aws-centralized-logging/outputs.tf b/modules/aws-centralized-logging/outputs.tf new file mode 100644 index 0000000..8b39761 --- /dev/null +++ b/modules/aws-centralized-logging/outputs.tf @@ -0,0 +1,46 @@ +output "security_group_ids" { + value = var.security_groups + description = "Security Group ID to control access to the Elasticsearch domain" +} + +output "domain_arn" { + value = join("", aws_elasticsearch_domain.default.*.arn) + description = "ARN of the Elasticsearch domain" +} + +output "domain_id" { + value = join("", aws_elasticsearch_domain.default.*.domain_id) + description = "Unique identifier for the Elasticsearch domain" +} + +output "domain_name" { + value = join("", aws_elasticsearch_domain.default.*.domain_name) + description = "Name of the Elasticsearch domain" +} + +output "domain_endpoint" { + value = join("", aws_elasticsearch_domain.default.*.endpoint) + description = "Domain-specific endpoint used to submit index, search, and data upload requests" +} + +output "kibana_endpoint" { + value = join("", aws_elasticsearch_domain.default.*.kibana_endpoint) + description = "Domain-specific endpoint for Kibana without https scheme" +} + + +output "elasticsearch_user_iam_role_name" { + value = join(",", aws_iam_role.elasticsearch_user.*.name) + description = "The name of the IAM role to allow access to Elasticsearch cluster" +} + +output "elasticsearch_user_iam_role_arn" { + value = join(",", aws_iam_role.elasticsearch_user.*.arn) + description = "The ARN of the IAM role to allow access to Elasticsearch cluster" +} + + +output "kinesis_stream_name" { + value = join(",", aws_kinesis_stream.stream.*.name) + description = "The name of the Kinesis stream" +} \ No newline at end of file diff --git a/modules/aws-centralized-logging/variables.tf b/modules/aws-centralized-logging/variables.tf new file mode 100644 index 0000000..dc9e80b --- /dev/null +++ b/modules/aws-centralized-logging/variables.tf @@ -0,0 +1,487 @@ +variable "domain_name" { + description = "Elastic Search domain name" + default = null + type = string +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "security_groups" { + type = list(string) + default = [] + description = "List of security group IDs to be allowed to connect to the cluster" +} + +variable "ingress_port_range_start" { + type = number + default = 0 + description = "Start number for allowed port range. (e.g. `443`)" +} + +variable "ingress_port_range_end" { + type = number + default = 65535 + description = "End number for allowed port range. (e.g. `443`)" +} + +variable "allowed_cidr_blocks" { + type = list(string) + default = [] + description = "List of CIDR blocks to be allowed to connect to the cluster" +} + +variable "vpc_enabled" { + type = bool + description = "Set to false if ES should be deployed outside of VPC." + default = true +} + +variable "vpc_id" { + type = string + description = "VPC ID" + default = null +} + +variable "subnet_ids" { + type = list(string) + description = "VPC Subnet IDs" + default = [] +} + +variable "dns_zone_id" { + type = string + default = "" + description = "Route53 DNS Zone ID to add hostname records for Elasticsearch domain and Kibana" +} + +variable "elasticsearch_version" { + type = string + default = "7.4" + description = "Version of Elasticsearch to deploy (_e.g._ `7.4`, `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5`" +} + +variable "instance_type" { + type = string + default = "t2.small.elasticsearch" + description = "Elasticsearch instance type for data nodes in the cluster" +} + +variable "instance_count" { + type = number + description = "Number of data nodes in the cluster" + default = 4 +} + +variable "warm_enabled" { + type = bool + default = false + description = "Whether AWS UltraWarm is enabled" +} + +variable "warm_count" { + type = number + default = 2 + description = "Number of UltraWarm nodes" +} + +variable "warm_type" { + type = string + default = "ultrawarm1.medium.elasticsearch" + description = "Type of UltraWarm nodes" +} + +variable "iam_role_arns" { + type = list(string) + default = [] + description = "List of IAM role ARNs to permit access to the Elasticsearch domain" +} + +variable "iam_role_permissions_boundary" { + type = string + default = null + description = "The ARN of the permissions boundary policy which will be attached to the Elasticsearch user role" +} + +variable "iam_authorizing_role_arns" { + type = list(string) + default = [] + description = "List of IAM role ARNs to permit to assume the Elasticsearch user role" +} + +variable "iam_actions" { + type = list(string) + default = [] + description = "List of actions to allow for the IAM roles, _e.g._ `es:ESHttpGet`, `es:ESHttpPut`, `es:ESHttpPost`" +} + +variable "zone_awareness_enabled" { + type = bool + default = true + description = "Enable zone awareness for Elasticsearch cluster" +} + +variable "availability_zone_count" { + type = number + default = 2 + description = "Number of Availability Zones for the domain to use." + + validation { + condition = contains([2, 3], var.availability_zone_count) + error_message = "The availibility zone count must be 2 or 3." + } +} + +variable "ebs_volume_size" { + type = number + description = "EBS volumes for data storage in GB" + default = 0 +} + +variable "ebs_volume_type" { + type = string + default = "gp2" + description = "Storage type of EBS volumes" +} + +variable "ebs_iops" { + type = number + default = 0 + description = "The baseline input/output (I/O) performance of EBS volumes attached to data nodes. Applicable only for the Provisioned IOPS EBS volume type" +} + +variable "encrypt_at_rest_enabled" { + type = bool + default = true + description = "Whether to enable encryption at rest" +} + +variable "encrypt_at_rest_kms_key_id" { + type = string + default = "" + description = "The KMS key ID to encrypt the Elasticsearch domain with. If not specified, then it defaults to using the AWS/Elasticsearch service KMS key" +} + +variable "domain_endpoint_options_enforce_https" { + type = bool + default = true + description = "Whether or not to require HTTPS" +} + +variable "domain_endpoint_options_tls_security_policy" { + type = string + default = "Policy-Min-TLS-1-0-2019-07" + description = "The name of the TLS security policy that needs to be applied to the HTTPS endpoint" +} + + +variable "log_publishing_index_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for INDEX_SLOW_LOGS is enabled or not" +} + +variable "log_publishing_search_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for SEARCH_SLOW_LOGS is enabled or not" +} + +variable "log_publishing_audit_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for AUDIT_LOGS is enabled or not" +} + +variable "log_publishing_application_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for ES_APPLICATION_LOGS is enabled or not" +} + +variable "log_publishing_index_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for INDEX_SLOW_LOGS needs to be published" +} + +variable "log_publishing_search_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for SEARCH_SLOW_LOGS needs to be published" +} + +variable "log_publishing_audit_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for AUDIT_LOGS needs to be published" +} + +variable "log_publishing_application_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for ES_APPLICATION_LOGS needs to be published" +} + +variable "automated_snapshot_start_hour" { + type = number + description = "Hour at which automated snapshots are taken, in UTC" + default = 0 +} + +variable "dedicated_master_enabled" { + type = bool + default = false + description = "Indicates whether dedicated master nodes are enabled for the cluster" +} + +variable "dedicated_master_count" { + type = number + description = "Number of dedicated master nodes in the cluster" + default = 0 +} + +variable "dedicated_master_type" { + type = string + default = "t2.small.elasticsearch" + description = "Instance type of the dedicated master nodes in the cluster" +} + +variable "advanced_options" { + type = map(string) + default = {} + description = "Key-value string pairs to specify advanced configuration options" +} + +variable "elasticsearch_subdomain_name" { + type = string + default = "" + description = "The name of the subdomain for Elasticsearch in the DNS zone (_e.g._ `elasticsearch`, `ui`, `ui-es`, `search-ui`)" +} + +variable "kibana_subdomain_name" { + type = string + default = "" + description = "The name of the subdomain for Kibana in the DNS zone (_e.g._ `kibana`, `ui`, `ui-es`, `search-ui`, `kibana.elasticsearch`)" +} + +variable "create_iam_service_linked_role" { + type = bool + default = true + description = "Whether to create `AWSServiceRoleForAmazonElasticsearchService` service-linked role. Set it to `false` if you already have an ElasticSearch cluster created in the AWS account and AWSServiceRoleForAmazonElasticsearchService already exists. See https://github.com/terraform-providers/terraform-provider-aws/issues/5218 for more info" +} + +variable "node_to_node_encryption_enabled" { + type = bool + default = false + description = "Whether to enable node-to-node encryption" +} + +variable "iam_role_max_session_duration" { + type = number + default = 3600 + description = "The maximum session duration (in seconds) for the user role. Can have a value from 1 hour to 12 hours" +} + +variable "cognito_authentication_enabled" { + type = bool + default = false + description = "Whether to enable Amazon Cognito authentication with Kibana" +} + +variable "cognito_user_pool_id" { + type = string + default = "" + description = "The ID of the Cognito User Pool to use" +} + +variable "cognito_identity_pool_id" { + type = string + default = "" + description = "The ID of the Cognito Identity Pool to use" +} + +variable "cognito_iam_role_arn" { + type = string + default = "" + description = "ARN of the IAM role that has the AmazonESCognitoAccess policy attached" +} + +variable "aws_ec2_service_name" { + type = list(string) + default = ["ec2.amazonaws.com"] + description = "AWS EC2 Service Name" +} + +variable "domain_hostname_enabled" { + type = bool + description = "Explicit flag to enable creating a DNS hostname for ES. If `true`, then `var.dns_zone_id` is required." + default = false +} + +variable "kibana_hostname_enabled" { + type = bool + description = "Explicit flag to enable creating a DNS hostname for Kibana. If `true`, then `var.dns_zone_id` is required." + default = false +} + +variable "advanced_security_options_enabled" { + type = bool + default = false + description = "AWS Elasticsearch Kibana enchanced security plugin enabling (forces new resource)" +} + +variable "advanced_security_options_internal_user_database_enabled" { + type = bool + default = false + description = "Whether to enable or not internal Kibana user database for ELK OpenDistro security plugin" +} + +variable "advanced_security_options_master_user_arn" { + type = string + default = "" + description = "ARN of IAM user who is to be mapped to be Kibana master user (applicable if advanced_security_options_internal_user_database_enabled set to false)" +} + +variable "advanced_security_options_master_user_name" { + type = string + default = "" + description = "Master user username (applicable if advanced_security_options_internal_user_database_enabled set to true)" +} + +variable "advanced_security_options_master_user_password" { + type = string + default = "" + description = "Master user password (applicable if advanced_security_options_internal_user_database_enabled set to true)" +} + +variable "custom_endpoint_enabled" { + type = bool + description = "Whether to enable custom endpoint for the Elasticsearch domain." + default = false +} + +variable "custom_endpoint" { + type = string + description = "Fully qualified domain for custom endpoint." + default = "" +} + +variable "custom_endpoint_certificate_arn" { + type = string + description = "ACM certificate ARN for custom endpoint." + default = "" +} + + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + + +# Kinesis Data Stream variables + +variable "kinesis_stream_name" { + type = string + description = "ACM certificate ARN for custom endpoint." + default = "" +} + +variable "kinesis_shard_count" { + type = number + description = "Will be used when stream mode set to PROVISIONED" + default = 1 +} + +variable "kinesis_stream_retention_period" { + type = number + description = "Retention period" + default = 24 +} + +variable "kinesis_stream_mode" { + type = string + description = "Streaming mode of kinesis data stream (it could be PROVISIONED(you need to set shard count for provisioned mode, and ON_DEMAND))" + default = "ON_DEMAND" +} + + +# Kinesis Firehose system + +variable "firehose_role_name" { + type = string + description = "Firehose role name" + default = "" +} + +variable "firehose_bucket_name" { + type = string + description = "Firehose backup,log bucket name" + default = "" +} + +variable "firehose_policy_name" { + type = string + description = "Firehose policy name" + default = "" +} + +variable "firehose_name" { + type = string + description = "Firehose name" + default = "" +} + +variable "firehose_es_index_name" { + type = string + description = "Firehose index name" + default = "" +} + +variable "firehose_index_rotation_period" { + type = string + description = "Firehose index rotation" + default = "" +} + +variable "firehose_cw_log_group_name" { + type = string + description = "Firehose CloudWatch log group name" + default = "" +} + +variable "firehose_cw_log_stream_name" { + type = string + description = "Firehose CloudWatch log stream name" + default = "" +} + +variable "index_field_enabled" { + type = bool + description = "Create logging index at elasticsearch" + default = true +} + + +variable "cloudwatch_logging_options_enabled" { + type = bool + description = "Create logging index at elasticsearch" + default = true +} + + + + +variable "firehose_cw_log_group_retention_in_days" { + type = number + description = "Log group retention in days" + default = 30 +} \ No newline at end of file diff --git a/modules/aws-centralized-logging/versions.tf b/modules/aws-centralized-logging/versions.tf new file mode 100644 index 0000000..588d1c7 --- /dev/null +++ b/modules/aws-centralized-logging/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.35.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-client-vpn/main.tf b/modules/aws-client-vpn/main.tf new file mode 100644 index 0000000..edf6107 --- /dev/null +++ b/modules/aws-client-vpn/main.tf @@ -0,0 +1,81 @@ +locals { + enabled = var.enabled + mutual_enabled = local.enabled && var.authentication_type == "certificate-authentication" + logging_enabled = local.enabled && var.logging_enabled + root_certificate_chain_arn = local.mutual_enabled ? var.root_cert_arn : null + cloudwatch_log_group = local.logging_enabled ? var.log_group_name : null + additional_routes_for_all_subnets = length(var.additional_routes_for_all) > 0 ? flatten([ + for route in var.additional_routes_for_all : [ + for subnet in var.associated_subnets : + { + "destination_cidr_block" = route.destination_cidr_block + "description" = route.description + "target_vpc_subnet_id" = subnet + } + ] + ]) : [] + additional_routes = concat(var.additional_routes, local.additional_routes_for_all_subnets) +} + +resource "aws_ec2_client_vpn_endpoint" "default" { + count = local.enabled ? 1 : 0 + + server_certificate_arn = var.server_cert_arn + client_cidr_block = var.client_cidr + transport_protocol = var.transport_protocol + + authentication_options { + type = var.authentication_type + saml_provider_arn = var.saml_provider_arn + root_certificate_chain_arn = local.root_certificate_chain_arn + } + + connection_log_options { + enabled = var.logging_enabled + cloudwatch_log_group = local.cloudwatch_log_group + } + + dns_servers = var.dns_servers + split_tunnel = var.split_tunnel + + session_timeout_hours = var.session_timeout_hours + + security_group_ids = [var.security_group_id] + vpc_id = var.vpc_id + tags = var.tags +} + +resource "aws_ec2_client_vpn_network_association" "default" { + count = local.enabled ? length(var.associated_subnets) : 0 + + client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id) + subnet_id = var.associated_subnets[count.index] +} + +resource "aws_ec2_client_vpn_authorization_rule" "default" { + count = local.enabled ? length(var.authorization_rules) : 0 + + access_group_id = lookup(var.authorization_rules[count.index], "access_group_id", null) + authorize_all_groups = lookup(var.authorization_rules[count.index], "authorize_all_groups", null) + client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id) + description = var.authorization_rules[count.index].description + target_network_cidr = var.authorization_rules[count.index].target_network_cidr +} + +resource "aws_ec2_client_vpn_route" "default" { + count = local.enabled ? length(local.additional_routes) : 0 + + description = try(local.additional_routes[count.index].description, null) + destination_cidr_block = local.additional_routes[count.index].destination_cidr_block + client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id) + target_vpc_subnet_id = local.additional_routes[count.index].target_vpc_subnet_id + + depends_on = [ + aws_ec2_client_vpn_network_association.default + ] + + timeouts { + create = "5m" + delete = "5m" + } +} diff --git a/modules/aws-client-vpn/outputs.tf b/modules/aws-client-vpn/outputs.tf new file mode 100644 index 0000000..dacc5e0 --- /dev/null +++ b/modules/aws-client-vpn/outputs.tf @@ -0,0 +1,14 @@ +output "vpn_endpoint_arn" { + value = local.enabled ? join("", aws_ec2_client_vpn_endpoint.default[*].arn) : null + description = "The ARN of the Client VPN Endpoint Connection." +} + +output "vpn_endpoint_id" { + value = local.enabled ? join("", aws_ec2_client_vpn_endpoint.default[*].id) : null + description = "The ID of the Client VPN Endpoint Connection." +} + +output "vpn_endpoint_dns_name" { + value = local.enabled ? join("", aws_ec2_client_vpn_endpoint.default[*].dns_name) : null + description = "The DNS Name of the Client VPN Endpoint Connection." +} \ No newline at end of file diff --git a/modules/aws-client-vpn/variables.tf b/modules/aws-client-vpn/variables.tf new file mode 100644 index 0000000..f95e38a --- /dev/null +++ b/modules/aws-client-vpn/variables.tf @@ -0,0 +1,175 @@ +variable "enabled" { + type = bool + default = true + description = "Enables this vpn module" +} + +variable "client_cidr" { + type = string + description = "Network CIDR to use for clients" +} + +variable "logging_enabled" { + type = bool + default = false + description = "Enables or disables Client VPN Cloudwatch logging." +} + +variable "authentication_type" { + type = string + default = "certificate-authentication" + description = <<-EOT + One of `certificate-authentication` or `federated-authentication` + EOT + validation { + condition = contains(["certificate-authentication", "federated-authentication"], var.authentication_type) + error_message = "VPN client authentication type must one be one of: certificate-authentication, federated-authentication." + } +} + +variable "saml_provider_arn" { + default = null + description = "Optional SAML provider ARN. Must include this or `saml_metadata_document`" + type = string + + validation { + error_message = "Invalid SAML provider ARN." + + condition = ( + var.saml_provider_arn == null || + try(length(regexall( + "^arn:[^:]+:iam::(?P\\d{12}):saml-provider\\/(?P[\\w+=,\\.@-]+)$", + var.saml_provider_arn + )) > 0, + false + )) + } +} + +variable "associated_subnets" { + type = list(string) + description = "List of subnets to associate with the VPN endpoint" +} + +variable "authorization_rules" { + # type = list(object({ + # name = string + # access_group_id = string + # authorize_all_groups = bool + # description = string + # target_network_cidr = string + # })) + type = list(map(any)) + description = "List of objects describing the authorization rules for the client vpn" + default = [] +} + +variable "vpc_id" { + type = string + description = "ID of VPC to attach VPN to" +} + +variable "dns_servers" { + default = [] + type = list(string) + validation { + condition = can( + [ + for server_ip in var.dns_servers : regex( + "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", + server_ip + ) + ] + ) + error_message = "IPv4 addresses must match the appropriate format xxx.xxx.xxx.xxx." + } + description = "Information about the DNS servers to be used for DNS resolution. A Client VPN endpoint can have up to two DNS servers. If no DNS server is specified, the DNS address of the connecting device is used." +} + +variable "split_tunnel" { + default = false + type = bool + description = "Indicates whether split-tunnel is enabled on VPN endpoint. Default value is false." +} + +variable "self_service_portal_enabled" { + description = "Specify whether to enable the self-service portal for the Client VPN endpoint" + type = bool + default = false +} + +variable "self_service_saml_provider_arn" { + description = "The ARN of the IAM SAML identity provider for the self service portal if type is federated-authentication." + type = string + default = null +} + +variable "session_timeout_hours" { + description = "The maximum session duration is a trigger by which end-users are required to re-authenticate prior to establishing a VPN session. Default value is 24. Valid values: 8 | 10 | 12 | 24" + type = string + default = "24" + + validation { + condition = contains(["8", "10", "12", "24"], var.session_timeout_hours) + error_message = "The maximum session duration must one be one of: 8, 10, 12, 24." + } +} + +variable "transport_protocol" { + description = "Transport protocol used by the TLS sessions." + type = string + default = "udp" + validation { + condition = contains(["udp", "tcp"], var.transport_protocol) + error_message = "Invalid protocol type must be one of: udp, tcp." + } +} + +variable "server_cert_arn" { + description = "Server certificate arn" + type = string +} + +variable "root_cert_arn" { + description = "Root certificate arn" + type = string + default = "null" +} + +variable "security_group_id" { + description = "Security group id for VPN" + type = string +} + +variable "log_group_name" { + description = "Log group name" + type = string + default = "" +} + +variable "tags" { + description = "Tags" + type = map(string) + default = {} +} + +variable "additional_routes" { + default = [] + description = "A list of additional routes that should be attached to the Client VPN endpoint" + + type = list(object({ + destination_cidr_block = string + description = string + target_vpc_subnet_id = string + })) +} + +variable "additional_routes_for_all" { + default = [] + description = "A list of additional routes that should be attached to the Client VPN endpoint" + + type = list(object({ + destination_cidr_block = string + description = string + })) +} diff --git a/modules/aws-client-vpn/versions.tf b/modules/aws-client-vpn/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-client-vpn/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudfront/README.md b/modules/aws-cloudfront/README.md new file mode 100644 index 0000000..b534033 --- /dev/null +++ b/modules/aws-cloudfront/README.md @@ -0,0 +1,160 @@ +# AWS CloudFront Terraform module + +Terraform module which creates AWS CloudFront resources with all (or almost all) features provided by Terraform AWS provider. + +## Usage + +### CloudFront distribution with versioning enabled + +```hcl +module "cdn" { + source = "terraform-aws-modules/cloudfront/aws" + + aliases = ["cdn.example.com"] + + comment = "My awesome CloudFront" + enabled = true + is_ipv6_enabled = true + price_class = "PriceClass_All" + retain_on_delete = false + wait_for_deployment = false + + create_origin_access_identity = true + origin_access_identities = { + s3_bucket_one = "My awesome CloudFront can access" + } + + logging_config = { + bucket = "logs-my-cdn.s3.amazonaws.com" + } + + origin = { + something = { + domain_name = "something.example.com" + custom_origin_config = { + http_port = 80 + https_port = 443 + origin_protocol_policy = "match-viewer" + origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] + } + } + + s3_one = { + domain_name = "my-s3-bycket.s3.amazonaws.com" + s3_origin_config = { + origin_access_identity = "s3_bucket_one" + } + } + } + + default_cache_behavior = { + target_origin_id = "something" + viewer_protocol_policy = "allow-all" + + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + compress = true + query_string = true + } + + ordered_cache_behavior = [ + { + path_pattern = "/static/*" + target_origin_id = "s3_one" + viewer_protocol_policy = "redirect-to-https" + + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + compress = true + query_string = true + } + ] + + viewer_certificate = { + acm_certificate_arn = "arn:aws:acm:us-east-1:135367859851:certificate/1032b155-22da-4ae0-9f69-e206f825458b" + ssl_support_method = "sni-only" + } +} +``` + +## Examples: + +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples/complete) - Complete example which creates AWS CloudFront distribution and integrates it with other [terraform-aws-modules](https://github.com/terraform-aws-modules) to create additional resources: S3 buckets, Lambda Functions, CloudFront Functions, ACM Certificate, Route53 Records. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.29 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.29 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudfront_distribution.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | +| [aws_cloudfront_monitoring_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_monitoring_subscription) | resource | +| [aws_cloudfront_origin_access_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aliases](#input\_aliases) | Extra CNAMEs (alternate domain names), if any, for this distribution. | `list(string)` | `null` | no | +| [comment](#input\_comment) | Any comments you want to include about the distribution. | `string` | `null` | no | +| [create\_distribution](#input\_create\_distribution) | Controls if CloudFront distribution should be created | `bool` | `true` | no | +| [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created. | `bool` | `false` | no | +| [create\_origin\_access\_identity](#input\_create\_origin\_access\_identity) | Controls if CloudFront origin access identity should be created | `bool` | `false` | no | +| [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements | `any` | `{}` | no | +| [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | `any` | `null` | no | +| [default\_root\_object](#input\_default\_root\_object) | The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL. | `string` | `null` | no | +| [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content. | `bool` | `true` | no | +| [geo\_restriction](#input\_geo\_restriction) | The restriction configuration for this distribution (geo\_restrictions) | `any` | `{}` | no | +| [http\_version](#input\_http\_version) | The maximum HTTP version to support on the distribution. Allowed values are http1.1 and http2. The default is http2. | `string` | `"http2"` | no | +| [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution. | `bool` | `null` | no | +| [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one). | `any` | `{}` | no | +| [ordered\_cache\_behavior](#input\_ordered\_cache\_behavior) | An ordered list of cache behaviors resource for this distribution. List from top to bottom in order of precedence. The topmost cache behavior will have precedence 0. | `any` | `[]` | no | +| [origin](#input\_origin) | One or more origins for this distribution (multiples allowed). | `any` | `null` | no | +| [origin\_access\_identities](#input\_origin\_access\_identities) | Map of CloudFront origin access identities (value as a comment) | `map(string)` | `{}` | no | +| [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed). | `any` | `{}` | no | +| [price\_class](#input\_price\_class) | The price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100 | `string` | `null` | no | +| [realtime\_metrics\_subscription\_status](#input\_realtime\_metrics\_subscription\_status) | A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`. | `string` | `"Enabled"` | no | +| [retain\_on\_delete](#input\_retain\_on\_delete) | Disables the distribution instead of deleting it when destroying the resource through Terraform. If this is set, the distribution needs to be deleted manually afterwards. | `bool` | `false` | no | +| [tags](#input\_tags) | A map of tags to assign to the resource. | `map(string)` | `null` | no | +| [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution | `any` |
{
"cloudfront_default_certificate": true,
"minimum_protocol_version": "TLSv1"
}
| no | +| [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this tofalse will skip the process. | `bool` | `true` | no | +| [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudfront\_distribution\_arn](#output\_cloudfront\_distribution\_arn) | The ARN (Amazon Resource Name) for the distribution. | +| [cloudfront\_distribution\_caller\_reference](#output\_cloudfront\_distribution\_caller\_reference) | Internal value used by CloudFront to allow future updates to the distribution configuration. | +| [cloudfront\_distribution\_domain\_name](#output\_cloudfront\_distribution\_domain\_name) | The domain name corresponding to the distribution. | +| [cloudfront\_distribution\_etag](#output\_cloudfront\_distribution\_etag) | The current version of the distribution's information. | +| [cloudfront\_distribution\_hosted\_zone\_id](#output\_cloudfront\_distribution\_hosted\_zone\_id) | The CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to. | +| [cloudfront\_distribution\_id](#output\_cloudfront\_distribution\_id) | The identifier for the distribution. | +| [cloudfront\_distribution\_in\_progress\_validation\_batches](#output\_cloudfront\_distribution\_in\_progress\_validation\_batches) | The number of invalidation batches currently in progress. | +| [cloudfront\_distribution\_last\_modified\_time](#output\_cloudfront\_distribution\_last\_modified\_time) | The date and time the distribution was last modified. | +| [cloudfront\_distribution\_status](#output\_cloudfront\_distribution\_status) | The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system. | +| [cloudfront\_distribution\_tags](#output\_cloudfront\_distribution\_tags) | Tags of the distribution's | +| [cloudfront\_distribution\_trusted\_signers](#output\_cloudfront\_distribution\_trusted\_signers) | List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs | +| [cloudfront\_monitoring\_subscription\_id](#output\_cloudfront\_monitoring\_subscription\_id) | The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`. | +| [cloudfront\_origin\_access\_identities](#output\_cloudfront\_origin\_access\_identities) | The origin access identities created | +| [cloudfront\_origin\_access\_identity\_iam\_arns](#output\_cloudfront\_origin\_access\_identity\_iam\_arns) | The IAM arns of the origin access identities created | +| [cloudfront\_origin\_access\_identity\_ids](#output\_cloudfront\_origin\_access\_identity\_ids) | The IDS of the origin access identities created | +| [s3\_bucket\_name](#s3\_bucket\_name) | S3 origin bucket name for Cloudfront | +| [zone\_id](#zone\_id) | Zone id of the route53 record| + \ No newline at end of file diff --git a/modules/aws-cloudfront/examples/complete/.gitignore b/modules/aws-cloudfront/examples/complete/.gitignore new file mode 100644 index 0000000..c4c4ffc --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/modules/aws-cloudfront/examples/complete/README.md b/modules/aws-cloudfront/examples/complete/README.md new file mode 100644 index 0000000..6572fc1 --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/README.md @@ -0,0 +1,88 @@ +# Complete CloudFront distribution with most of supported features enabled + +Configuration in this directory creates CloudFront distribution which demos such capabilities: +- access logging +- origins and origin groups +- caching behaviours +- Origin Access Identities (with S3 bucket policy) +- Lambda@Edge +- ACM certificate +- Route53 record + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.29 | +| [external](#requirement\_external) | >= 1.0 | +| [local](#requirement\_local) | >= 1.0 | +| [null](#requirement\_null) | >= 2.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.29 | +| [null](#provider\_null) | >= 2.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | +| [cloudfront](#module\_cloudfront) | ../../ | n/a | +| [lambda\_function](#module\_lambda\_function) | terraform-aws-modules/lambda/aws | ~> 4.0 | +| [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [records](#module\_records) | terraform-aws-modules/route53/aws//modules/records | ~> 2.0 | +| [s3\_one](#module\_s3\_one) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudfront_function.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource | +| [aws_s3_bucket_policy.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | +| [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudfront\_distribution\_arn](#output\_cloudfront\_distribution\_arn) | The ARN (Amazon Resource Name) for the distribution. | +| [cloudfront\_distribution\_caller\_reference](#output\_cloudfront\_distribution\_caller\_reference) | Internal value used by CloudFront to allow future updates to the distribution configuration. | +| [cloudfront\_distribution\_domain\_name](#output\_cloudfront\_distribution\_domain\_name) | The domain name corresponding to the distribution. | +| [cloudfront\_distribution\_etag](#output\_cloudfront\_distribution\_etag) | The current version of the distribution's information. | +| [cloudfront\_distribution\_hosted\_zone\_id](#output\_cloudfront\_distribution\_hosted\_zone\_id) | The CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to. | +| [cloudfront\_distribution\_id](#output\_cloudfront\_distribution\_id) | The identifier for the distribution. | +| [cloudfront\_distribution\_in\_progress\_validation\_batches](#output\_cloudfront\_distribution\_in\_progress\_validation\_batches) | The number of invalidation batches currently in progress. | +| [cloudfront\_distribution\_last\_modified\_time](#output\_cloudfront\_distribution\_last\_modified\_time) | The date and time the distribution was last modified. | +| [cloudfront\_distribution\_status](#output\_cloudfront\_distribution\_status) | The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system. | +| [cloudfront\_distribution\_trusted\_signers](#output\_cloudfront\_distribution\_trusted\_signers) | List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs | +| [cloudfront\_origin\_access\_identities](#output\_cloudfront\_origin\_access\_identities) | The origin access identities created | +| [cloudfront\_origin\_access\_identity\_iam\_arns](#output\_cloudfront\_origin\_access\_identity\_iam\_arns) | The IAM arns of the origin access identities created | +| [cloudfront\_origin\_access\_identity\_ids](#output\_cloudfront\_origin\_access\_identity\_ids) | The IDS of the origin access identities created | + diff --git a/modules/aws-cloudfront/examples/complete/example-function.js b/modules/aws-cloudfront/examples/complete/example-function.js new file mode 100644 index 0000000..8983e5f --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/example-function.js @@ -0,0 +1,13 @@ +function handler(event) { + // NOTE: This example function is for a viewer request event trigger. + // Choose viewer request for event trigger when you associate this function with a distribution. + var response = { + statusCode: 302, + statusDescription: 'Found', + headers: { + 'cloudfront-functions': { value: 'generated-by-CloudFront-Functions' }, + 'location': { value: 'https://aws.amazon.com/cloudfront/' } + } + }; + return response; +} diff --git a/modules/aws-cloudfront/examples/complete/main.tf b/modules/aws-cloudfront/examples/complete/main.tf new file mode 100644 index 0000000..ddbec93 --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/main.tf @@ -0,0 +1,299 @@ +provider "aws" { + region = "us-east-1" # CloudFront expects ACM resources in us-east-1 region only + + # Make it faster by skipping something + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + + # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn + skip_requesting_account_id = false +} + +locals { + domain_name = "terraform-aws-modules.modules.tf" # trimsuffix(data.aws_route53_zone.this.name, ".") + subdomain = "cdn" +} + +module "cloudfront" { + source = "../../" + + aliases = ["${local.subdomain}.${local.domain_name}"] + + comment = "My awesome CloudFront" + enabled = true + is_ipv6_enabled = true + price_class = "PriceClass_All" + retain_on_delete = false + wait_for_deployment = false + + # When you enable additional metrics for a distribution, CloudFront sends up to 8 metrics to CloudWatch in the US East (N. Virginia) Region. + # This rate is charged only once per month, per metric (up to 8 metrics per distribution). + create_monitoring_subscription = true + + create_origin_access_identity = true + origin_access_identities = { + s3_bucket_one = "My awesome CloudFront can access" + } + + logging_config = { + bucket = module.log_bucket.s3_bucket_bucket_domain_name + prefix = "cloudfront" + } + + origin = { + appsync = { + domain_name = "appsync.${local.domain_name}" + custom_origin_config = { + http_port = 80 + https_port = 443 + origin_protocol_policy = "match-viewer" + origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] + } + + custom_header = [ + { + name = "X-Forwarded-Scheme" + value = "https" + }, + { + name = "X-Frame-Options" + value = "SAMEORIGIN" + } + ] + + origin_shield = { + enabled = true + origin_shield_region = "us-east-1" + } + } + + s3_one = { + domain_name = module.s3_one.s3_bucket_bucket_regional_domain_name + s3_origin_config = { + origin_access_identity = "s3_bucket_one" # key in `origin_access_identities` + # cloudfront_access_identity_path = "origin-access-identity/cloudfront/E5IGQAA1QO48Z" # external OAI resource + } + } + } + + origin_group = { + group_one = { + failover_status_codes = [403, 404, 500, 502] + primary_member_origin_id = "appsync" + secondary_member_origin_id = "s3_one" + } + } + + default_cache_behavior = { + target_origin_id = "appsync" + viewer_protocol_policy = "allow-all" + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + compress = true + query_string = true + + # This is id for SecurityHeadersPolicy copied from https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html + response_headers_policy_id = "67f7725c-6f97-4210-82d7-5512b31e9d03" + + lambda_function_association = { + + # Valid keys: viewer-request, origin-request, viewer-response, origin-response + viewer-request = { + lambda_arn = module.lambda_function.lambda_function_qualified_arn + include_body = true + } + + origin-request = { + lambda_arn = module.lambda_function.lambda_function_qualified_arn + } + } + } + + ordered_cache_behavior = [ + { + path_pattern = "/static/*" + target_origin_id = "s3_one" + viewer_protocol_policy = "redirect-to-https" + + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + compress = true + query_string = true + + function_association = { + # Valid keys: viewer-request, viewer-response + viewer-request = { + function_arn = aws_cloudfront_function.example.arn + } + + viewer-response = { + function_arn = aws_cloudfront_function.example.arn + } + } + } + ] + + viewer_certificate = { + acm_certificate_arn = module.acm.acm_certificate_arn + ssl_support_method = "sni-only" + } + + geo_restriction = { + restriction_type = "whitelist" + locations = ["NO", "UA", "US", "GB"] + } + +} + +###### +# ACM +###### + +data "aws_route53_zone" "this" { + name = local.domain_name +} + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 4.0" + + domain_name = local.domain_name + zone_id = data.aws_route53_zone.this.id + subject_alternative_names = ["${local.subdomain}.${local.domain_name}"] +} + +############# +# S3 buckets +############# + +data "aws_canonical_user_id" "current" {} + +module "s3_one" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket = "s3-one-${random_pet.this.id}" + force_destroy = true +} + +module "log_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket = "logs-${random_pet.this.id}" + acl = null + grant = [{ + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_canonical_user_id.current.id + }, { + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0" + # Ref. https://github.com/terraform-providers/terraform-provider-aws/issues/12512 + # Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html + }] + force_destroy = true +} + +############################################# +# Using packaged function from Lambda module +############################################# + +locals { + package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip" + downloaded = "downloaded_package_${md5(local.package_url)}.zip" +} + +resource "null_resource" "download_package" { + triggers = { + downloaded = local.downloaded + } + + provisioner "local-exec" { + command = "curl -L -o ${local.downloaded} ${local.package_url}" + } +} + +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 4.0" + + function_name = "${random_pet.this.id}-lambda" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + publish = true + lambda_at_edge = true + + create_package = false + local_existing_package = local.downloaded + + # @todo: Missing CloudFront as allowed_triggers? + + # allowed_triggers = { + # AllowExecutionFromAPIGateway = { + # service = "apigateway" + # arn = module.api_gateway.apigatewayv2_api_execution_arn + # } + # } +} + +########## +# Route53 +########## + +module "records" { + source = "terraform-aws-modules/route53/aws//modules/records" + version = "~> 2.0" + + zone_id = data.aws_route53_zone.this.zone_id + + records = [ + { + name = local.subdomain + type = "A" + alias = { + name = module.cloudfront.cloudfront_distribution_domain_name + zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id + } + }, + ] +} + +########################### +# Origin Access Identities +########################### +data "aws_iam_policy_document" "s3_policy" { + statement { + actions = ["s3:GetObject"] + resources = ["${module.s3_one.s3_bucket_arn}/static/*"] + + principals { + type = "AWS" + identifiers = module.cloudfront.cloudfront_origin_access_identity_iam_arns + } + } +} + +resource "aws_s3_bucket_policy" "bucket_policy" { + bucket = module.s3_one.s3_bucket_id + policy = data.aws_iam_policy_document.s3_policy.json +} + +######## +# Extra +######## + +resource "random_pet" "this" { + length = 2 +} + +resource "aws_cloudfront_function" "example" { + name = "example-${random_pet.this.id}" + runtime = "cloudfront-js-1.0" + code = file("example-function.js") +} diff --git a/modules/aws-cloudfront/examples/complete/outputs.tf b/modules/aws-cloudfront/examples/complete/outputs.tf new file mode 100644 index 0000000..3b775e2 --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/outputs.tf @@ -0,0 +1,64 @@ +output "cloudfront_distribution_id" { + description = "The identifier for the distribution." + value = module.cloudfront.cloudfront_distribution_id +} + +output "cloudfront_distribution_arn" { + description = "The ARN (Amazon Resource Name) for the distribution." + value = module.cloudfront.cloudfront_distribution_arn +} + +output "cloudfront_distribution_caller_reference" { + description = "Internal value used by CloudFront to allow future updates to the distribution configuration." + value = module.cloudfront.cloudfront_distribution_caller_reference +} + +output "cloudfront_distribution_status" { + description = "The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system." + value = module.cloudfront.cloudfront_distribution_status +} + +output "cloudfront_distribution_trusted_signers" { + description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs" + value = module.cloudfront.cloudfront_distribution_trusted_signers +} + +output "cloudfront_distribution_domain_name" { + description = "The domain name corresponding to the distribution." + value = module.cloudfront.cloudfront_distribution_domain_name +} + +output "cloudfront_distribution_last_modified_time" { + description = "The date and time the distribution was last modified." + value = module.cloudfront.cloudfront_distribution_last_modified_time +} + +output "cloudfront_distribution_in_progress_validation_batches" { + description = "The number of invalidation batches currently in progress." + value = module.cloudfront.cloudfront_distribution_in_progress_validation_batches +} + +output "cloudfront_distribution_etag" { + description = "The current version of the distribution's information." + value = module.cloudfront.cloudfront_distribution_etag +} + +output "cloudfront_distribution_hosted_zone_id" { + description = "The CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to." + value = module.cloudfront.cloudfront_distribution_hosted_zone_id +} + +output "cloudfront_origin_access_identities" { + description = "The origin access identities created" + value = module.cloudfront.cloudfront_origin_access_identities +} + +output "cloudfront_origin_access_identity_ids" { + description = "The IDS of the origin access identities created" + value = module.cloudfront.cloudfront_origin_access_identity_ids +} + +output "cloudfront_origin_access_identity_iam_arns" { + description = "The IAM arns of the origin access identities created" + value = module.cloudfront.cloudfront_origin_access_identity_iam_arns +} diff --git a/modules/aws-cloudfront/examples/complete/variables.tf b/modules/aws-cloudfront/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-cloudfront/examples/complete/versions.tf b/modules/aws-cloudfront/examples/complete/versions.tf new file mode 100644 index 0000000..55001ba --- /dev/null +++ b/modules/aws-cloudfront/examples/complete/versions.tf @@ -0,0 +1,26 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.29" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + external = { + source = "hashicorp/external" + version = ">= 1.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.0" + } + } +} diff --git a/modules/aws-cloudfront/main.tf b/modules/aws-cloudfront/main.tf new file mode 100644 index 0000000..e5a54c6 --- /dev/null +++ b/modules/aws-cloudfront/main.tf @@ -0,0 +1,461 @@ +locals { + create_origin_access_identity = var.create_origin_access_identity && length(keys(var.origin_access_identities)) > 0 + # create_s3_bucket = var.create_s3_bucket && length(var.s3_bucket_name) > 0 +} + + + +resource "aws_cloudfront_origin_access_identity" "this" { + for_each = local.create_origin_access_identity ? var.origin_access_identities : {} + + comment = each.value + + lifecycle { + create_before_destroy = true + } +} + +data "aws_route53_zone" "selected" { + provider = aws.shared_infra + count = length(var.aliases) > 0 ? 1 : 0 + name = var.zone_name + private_zone = false +} + +data "aws_s3_bucket" "existing_bucket" { + count = var.s3_bucket_name != "" && var.create_s3_bucket == false ? 1 : 0 + bucket = var.s3_bucket_name +} + +resource "aws_route53_record" "this" { + provider = aws.shared_infra + count = length(var.aliases) + zone_id = data.aws_route53_zone.selected[0].id + name = var.aliases[count.index] + type = "A" + alias { + name = aws_cloudfront_distribution.this[0].domain_name + zone_id = aws_cloudfront_distribution.this[0].hosted_zone_id + evaluate_target_health = true + } +} + +resource "aws_s3_bucket" "this" { + count = var.create_origin_access_identity && var.create_s3_bucket ? 1 : 0 + bucket = var.s3_bucket_name + # object_ownership = var.object_ownership +} + +resource "aws_s3_bucket_cors_configuration" "this" { + count = ((length(var.cors_rule) > 0 ? true : false) && var.create_origin_access_identity) ? 1 : 0 + bucket = aws_s3_bucket.this[0].id + #expected_bucket_owner = var.expected_bucket_owner + + dynamic "cors_rule" { + for_each = var.cors_rule + + content { + id = try(cors_rule.value.id, null) + allowed_methods = cors_rule.value.AllowedMethods + allowed_origins = cors_rule.value.AllowedOrigins + allowed_headers = try(cors_rule.value.AllowedHeaders, null) + expose_headers = try(cors_rule.value.ExposeHeaders, null) + max_age_seconds = try(cors_rule.value.MaxAgeSeconds, null) + } + } +} + +resource "aws_s3_bucket_policy" "s3_policy" { + for_each = local.create_origin_access_identity && var.create_s3_bucket ? var.origin_access_identities : {} + bucket = aws_s3_bucket.this[0].id + policy = jsonencode( + { + Version = "2008-10-17", + Id = "PolicyForCloudFrontPrivateContent", + Statement = [ + { + Sid = "1", + Effect = "Allow", + Principal = { + "AWS" : "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${aws_cloudfront_origin_access_identity.this[each.key].id}" + }, + Action = "s3:GetObject", + Resource = var.bucket_policy_resources == null ? ["arn:aws:s3:::${aws_s3_bucket.this[0].id}/*"] : var.bucket_policy_resources # split(",",var.bucket_policy_resources) + + }, + ] + } + ) +} + +resource "aws_s3_bucket_ownership_controls" "this" { + count = var.control_object_ownership ? 1 : 0 + bucket = aws_s3_bucket.this[0].id # local.create_origin_access_identity ? aws_s3_bucket_policy.s3_policy[0].id : aws_s3_bucket.this[0].id + + rule { + object_ownership = var.object_ownership + } + + # This `depends_on` is to prevent "A conflicting conditional operation is currently in progress against this resource." + depends_on = [ + aws_s3_bucket_policy.s3_policy, + aws_s3_bucket.this + ] +} + +resource "aws_s3_bucket_website_configuration" "this" { + count = length(keys(var.website)) > 0 ? 1 : 0 + bucket = aws_s3_bucket.this[0].id + + dynamic "index_document" { + for_each = try([var.website["index_document"]], []) + + content { + suffix = index_document.value + } + } + + dynamic "error_document" { + for_each = try([var.website["error_document"]], []) + + content { + key = error_document.value + } + } + + dynamic "redirect_all_requests_to" { + for_each = try([var.website["redirect_all_requests_to"]], []) + + content { + host_name = redirect_all_requests_to.value.host_name + protocol = try(redirect_all_requests_to.value.protocol, null) + } + } + + dynamic "routing_rule" { + for_each = try(flatten([var.website["routing_rules"]]), []) + + content { + dynamic "condition" { + for_each = [try([routing_rule.value.condition], [])] + + content { + http_error_code_returned_equals = try(routing_rule.value.condition["http_error_code_returned_equals"], null) + key_prefix_equals = try(routing_rule.value.condition["key_prefix_equals"], null) + } + } + + redirect { + host_name = try(routing_rule.value.redirect["host_name"], null) + http_redirect_code = try(routing_rule.value.redirect["http_redirect_code"], null) + protocol = try(routing_rule.value.redirect["protocol"], null) + replace_key_prefix_with = try(routing_rule.value.redirect["replace_key_prefix_with"], null) + replace_key_with = try(routing_rule.value.redirect["replace_key_with"], null) + } + } + } +} + + +resource "aws_cloudfront_distribution" "this" { + depends_on = [ + aws_cloudfront_origin_request_policy.this[0] + ] + count = var.create_distribution ? 1 : 0 + + aliases = var.aliases + comment = var.comment + default_root_object = var.default_root_object + enabled = var.enabled + http_version = var.http_version + is_ipv6_enabled = var.is_ipv6_enabled + price_class = var.price_class + retain_on_delete = var.retain_on_delete + wait_for_deployment = var.wait_for_deployment + web_acl_id = var.web_acl_id + tags = var.tags + + dynamic "logging_config" { + for_each = length(keys(var.logging_config)) == 0 ? [] : [var.logging_config] + + content { + bucket = logging_config.value["bucket"] + prefix = lookup(logging_config.value, "prefix", null) + include_cookies = lookup(logging_config.value, "include_cookies", null) + } + } + + dynamic "origin" { + for_each = var.origin + + content { + domain_name = var.cloudfront_elb ? lookup(origin.value, "domain_name", origin.key) : lookup(origin.value, "domain_name", + var.s3_bucket_name != "" && var.create_s3_bucket == false ? data.aws_s3_bucket.existing_bucket[0].bucket_domain_name : aws_s3_bucket.this[0].bucket_domain_name) + origin_id = lookup(origin.value, "origin_id", origin.key) + origin_path = lookup(origin.value, "origin_path", "") + connection_attempts = lookup(origin.value, "connection_attempts", null) + connection_timeout = lookup(origin.value, "connection_timeout", null) + origin_access_control_id = lookup(origin.value, "origin_access_control_id", null) + + dynamic "s3_origin_config" { + for_each = length(keys(lookup(origin.value, "s3_origin_config", {}))) == 0 ? [] : [lookup(origin.value, "s3_origin_config", {})] + + content { + origin_access_identity = lookup(s3_origin_config.value, "cloudfront_access_identity_path", lookup(lookup(aws_cloudfront_origin_access_identity.this, lookup(s3_origin_config.value, "origin_access_identity", ""), {}), "cloudfront_access_identity_path", null)) + } + } + + dynamic "custom_origin_config" { + for_each = length(lookup(origin.value, "custom_origin_config", "")) == 0 ? [] : [lookup(origin.value, "custom_origin_config", "")] + + content { + http_port = custom_origin_config.value.http_port + https_port = custom_origin_config.value.https_port + origin_protocol_policy = custom_origin_config.value.origin_protocol_policy + origin_ssl_protocols = custom_origin_config.value.origin_ssl_protocols + origin_keepalive_timeout = lookup(custom_origin_config.value, "origin_keepalive_timeout", null) + origin_read_timeout = lookup(custom_origin_config.value, "origin_read_timeout", null) + } + } + + dynamic "custom_header" { + for_each = lookup(origin.value, "custom_header", []) + + content { + name = custom_header.value.name + value = custom_header.value.value + } + } + + dynamic "origin_shield" { + for_each = length(keys(lookup(origin.value, "origin_shield", {}))) == 0 ? [] : [lookup(origin.value, "origin_shield", {})] + + content { + enabled = origin_shield.value.enabled + origin_shield_region = origin_shield.value.origin_shield_region + } + } + } + } + + dynamic "origin_group" { + for_each = var.origin_group + + content { + origin_id = lookup(origin_group.value, "origin_id", origin_group.key) + + failover_criteria { + status_codes = origin_group.value["failover_status_codes"] + } + + member { + origin_id = origin_group.value["primary_member_origin_id"] + } + + member { + origin_id = origin_group.value["secondary_member_origin_id"] + } + } + } + + dynamic "default_cache_behavior" { + for_each = [var.default_cache_behavior] + iterator = i + + content { + target_origin_id = var.cloudfront_elb ? lookup(i.value, "domain_name", "") : (var.s3_bucket_name != "" && var.create_s3_bucket == false ? data.aws_s3_bucket.existing_bucket[0].id : aws_s3_bucket.this[0].id) + viewer_protocol_policy = i.value["viewer_protocol_policy"] + + allowed_methods = lookup(i.value, "allowed_methods", ["GET", "HEAD", "OPTIONS"]) + cached_methods = lookup(i.value, "cached_methods", ["GET", "HEAD"]) + compress = lookup(i.value, "compress", null) + field_level_encryption_id = lookup(i.value, "field_level_encryption_id", null) + smooth_streaming = lookup(i.value, "smooth_streaming", null) + trusted_signers = lookup(i.value, "trusted_signers", null) + trusted_key_groups = lookup(i.value, "trusted_key_groups", null) + + cache_policy_id = var.create_and_attach_origin_request_policy || lookup(i.value, "managed_cache_disabled_policy", false) ? data.aws_cloudfront_cache_policy.managed_cache_disabled.id : lookup(i.value, "cache_policy_id", null) + origin_request_policy_id = var.create_and_attach_origin_request_policy ? aws_cloudfront_origin_request_policy.this[0].id : ( + lookup(i.value, "origin_request_policy_id", null) == "managed_origin_request_policy" ? data.aws_cloudfront_origin_request_policy.managed_origin_request_policy.id : lookup(i.value, "origin_request_policy_id", null) + ) + response_headers_policy_id = lookup(i.value, "response_headers_policy_id", null) + realtime_log_config_arn = lookup(i.value, "realtime_log_config_arn", null) + + min_ttl = lookup(i.value, "min_ttl", null) + default_ttl = lookup(i.value, "default_ttl", null) + max_ttl = lookup(i.value, "max_ttl", null) + + dynamic "forwarded_values" { + for_each = lookup(i.value, "use_forwarded_values", true) ? [true] : [] + + content { + query_string = lookup(i.value, "query_string", false) + query_string_cache_keys = lookup(i.value, "query_string_cache_keys", []) + headers = lookup(i.value, "headers", []) + + cookies { + forward = lookup(i.value, "cookies_forward", "none") + whitelisted_names = lookup(i.value, "cookies_whitelisted_names", null) + } + } + } + + dynamic "lambda_function_association" { + for_each = lookup(i.value, "lambda_function_association", []) + iterator = l + + content { + event_type = l.key + lambda_arn = l.value.lambda_arn + include_body = lookup(l.value, "include_body", null) + } + } + + dynamic "function_association" { + for_each = lookup(i.value, "function_association", []) + iterator = f + + content { + event_type = f.key + function_arn = f.value.function_arn + } + } + } + } + + dynamic "ordered_cache_behavior" { + for_each = var.ordered_cache_behavior + iterator = i + + content { + path_pattern = i.value["path_pattern"] + target_origin_id = var.cloudfront_elb ? lookup(i.value, "domain_name", "") : aws_s3_bucket.this[0].id + viewer_protocol_policy = i.value["viewer_protocol_policy"] + + allowed_methods = lookup(i.value, "allowed_methods", ["GET", "HEAD", "OPTIONS"]) + cached_methods = lookup(i.value, "cached_methods", ["GET", "HEAD"]) + compress = lookup(i.value, "compress", null) + field_level_encryption_id = lookup(i.value, "field_level_encryption_id", null) + smooth_streaming = lookup(i.value, "smooth_streaming", null) + trusted_signers = lookup(i.value, "trusted_signers", null) + trusted_key_groups = lookup(i.value, "trusted_key_groups", null) + + cache_policy_id = lookup(i.value, "cache_policy_id", null) + origin_request_policy_id = lookup(i.value, "origin_request_policy_id", null) + response_headers_policy_id = lookup(i.value, "response_headers_policy_id", null) + realtime_log_config_arn = lookup(i.value, "realtime_log_config_arn", null) + + min_ttl = lookup(i.value, "min_ttl", null) + default_ttl = lookup(i.value, "default_ttl", null) + max_ttl = lookup(i.value, "max_ttl", null) + + dynamic "forwarded_values" { + for_each = lookup(i.value, "use_forwarded_values", true) ? [true] : [] + + content { + query_string = lookup(i.value, "query_string", false) + query_string_cache_keys = lookup(i.value, "query_string_cache_keys", []) + headers = lookup(i.value, "headers", []) + + cookies { + forward = lookup(i.value, "cookies_forward", "none") + whitelisted_names = lookup(i.value, "cookies_whitelisted_names", null) + } + } + } + + dynamic "lambda_function_association" { + for_each = lookup(i.value, "lambda_function_association", []) + iterator = l + + content { + event_type = l.key + lambda_arn = l.value.lambda_arn + include_body = lookup(l.value, "include_body", null) + } + } + + dynamic "function_association" { + for_each = lookup(i.value, "function_association", []) + iterator = f + + content { + event_type = f.key + function_arn = f.value.function_arn + } + } + } + } + + viewer_certificate { + acm_certificate_arn = lookup(var.viewer_certificate, "acm_certificate_arn", null) + cloudfront_default_certificate = lookup(var.viewer_certificate, "cloudfront_default_certificate", null) + iam_certificate_id = lookup(var.viewer_certificate, "iam_certificate_id", null) + + minimum_protocol_version = lookup(var.viewer_certificate, "minimum_protocol_version", "TLSv1") + ssl_support_method = lookup(var.viewer_certificate, "ssl_support_method", null) + } + + dynamic "custom_error_response" { + for_each = var.custom_error_response + + content { + error_code = custom_error_response.value["error_code"] + + response_code = lookup(custom_error_response.value, "response_code", null) + response_page_path = lookup(custom_error_response.value, "response_page_path", null) + error_caching_min_ttl = lookup(custom_error_response.value, "error_caching_min_ttl", null) + } + } + + restrictions { + dynamic "geo_restriction" { + for_each = [var.geo_restriction] + + content { + restriction_type = lookup(geo_restriction.value, "restriction_type", "none") + locations = lookup(geo_restriction.value, "locations", []) + } + } + } +} + +resource "aws_cloudfront_monitoring_subscription" "this" { + count = var.create_distribution && var.create_monitoring_subscription ? 1 : 0 + + distribution_id = aws_cloudfront_distribution.this[0].id + + monitoring_subscription { + realtime_metrics_subscription_config { + realtime_metrics_subscription_status = var.realtime_metrics_subscription_status + } + } +} + +resource "aws_cloudfront_origin_request_policy" "this" { + count = var.create_distribution && var.create_and_attach_origin_request_policy ? 1 : 0 + name = var.origin_request_policy_name + comment = "origin request policy includes the following headers" + cookies_config { + cookie_behavior = "all" + } + headers_config { + header_behavior = "whitelist" + headers { + items = var.origin_request_policy_headers + } + } + query_strings_config { + query_string_behavior = "all" + } + +} + + +data "aws_cloudfront_cache_policy" "managed_cache_disabled" { + name = "Managed-CachingDisabled" +} + +data "aws_cloudfront_origin_request_policy" "managed_origin_request_policy" { + name = "Managed-AllViewerAndCloudFrontHeaders-2022-06" +} diff --git a/modules/aws-cloudfront/outputs.tf b/modules/aws-cloudfront/outputs.tf new file mode 100644 index 0000000..e1fac95 --- /dev/null +++ b/modules/aws-cloudfront/outputs.tf @@ -0,0 +1,88 @@ +output "cloudfront_distribution_id" { + description = "The identifier for the distribution." + value = element(concat(aws_cloudfront_distribution.this.*.id, [""]), 0) +} + +output "cloudfront_distribution_arn" { + description = "The ARN (Amazon Resource Name) for the distribution." + value = element(concat(aws_cloudfront_distribution.this.*.arn, [""]), 0) +} + +output "cloudfront_distribution_caller_reference" { + description = "Internal value used by CloudFront to allow future updates to the distribution configuration." + value = element(concat(aws_cloudfront_distribution.this.*.caller_reference, [""]), 0) +} + +output "cloudfront_distribution_status" { + description = "The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system." + value = element(concat(aws_cloudfront_distribution.this.*.status, [""]), 0) +} + +output "cloudfront_distribution_trusted_signers" { + description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs" + value = element(concat(aws_cloudfront_distribution.this.*.trusted_signers, [""]), 0) +} + +output "cloudfront_distribution_domain_name" { + description = "The domain name corresponding to the distribution." + value = element(concat(aws_cloudfront_distribution.this.*.domain_name, [""]), 0) +} + +output "cloudfront_distribution_last_modified_time" { + description = "The date and time the distribution was last modified." + value = element(concat(aws_cloudfront_distribution.this.*.last_modified_time, [""]), 0) +} + +output "cloudfront_distribution_in_progress_validation_batches" { + description = "The number of invalidation batches currently in progress." + value = element(concat(aws_cloudfront_distribution.this.*.in_progress_validation_batches, [""]), 0) +} + +output "cloudfront_distribution_etag" { + description = "The current version of the distribution's information." + value = element(concat(aws_cloudfront_distribution.this.*.etag, [""]), 0) +} + +output "cloudfront_distribution_hosted_zone_id" { + description = "The CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to." + value = element(concat(aws_cloudfront_distribution.this.*.hosted_zone_id, [""]), 0) +} + +output "cloudfront_origin_access_identities" { + description = "The origin access identities created" + value = local.create_origin_access_identity ? { for k, v in aws_cloudfront_origin_access_identity.this : k => v } : {} +} + +output "cloudfront_origin_access_identity_ids" { + description = "The IDS of the origin access identities created" + value = local.create_origin_access_identity ? [for v in aws_cloudfront_origin_access_identity.this : v.id] : [] +} + +output "cloudfront_origin_access_identity_iam_arns" { + description = "The IAM arns of the origin access identities created" + value = local.create_origin_access_identity ? [for v in aws_cloudfront_origin_access_identity.this : v.iam_arn] : [] +} + +output "cloudfront_monitoring_subscription_id" { + description = " The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`." + value = element(concat(aws_cloudfront_monitoring_subscription.this.*.id, [""]), 0) +} + +output "cloudfront_distribution_tags" { + description = "Tags of the distribution's" + value = element(concat(aws_cloudfront_distribution.this.*.tags_all, [""]), 0) +} + +output "s3_bucket_website_endpoint" { + description = "The website endpoint, if the bucket is configured with a website. If not, this will be an empty string." + value = try(aws_s3_bucket_website_configuration.this[0].website_endpoint, "") +} + +output "s3_bucket_website_domain" { + description = "The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records." + value = try(aws_s3_bucket_website_configuration.this[0].website_domain, "") +} + +output "bucket_name" { + value = var.s3_bucket_name +} \ No newline at end of file diff --git a/modules/aws-cloudfront/variables.tf b/modules/aws-cloudfront/variables.tf new file mode 100644 index 0000000..5817168 --- /dev/null +++ b/modules/aws-cloudfront/variables.tf @@ -0,0 +1,250 @@ +variable "create_distribution" { + description = "Controls if CloudFront distribution should be created" + type = bool + default = true +} + +variable "create_origin_access_identity" { + description = "Controls if CloudFront origin access identity should be created" + type = bool + default = false +} + +variable "origin_access_identities" { + description = "Map of CloudFront origin access identities (value as a comment)" + type = map(string) + default = {} +} + +variable "aliases" { + description = "Extra CNAMEs (alternate domain names), if any, for this distribution." + type = list(string) + default = null +} + +variable "comment" { + description = "Any comments you want to include about the distribution." + type = string + default = null +} + +variable "default_root_object" { + description = "The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL." + type = string + default = null +} + +variable "enabled" { + description = "Whether the distribution is enabled to accept end user requests for content." + type = bool + default = true +} + +variable "http_version" { + description = "The maximum HTTP version to support on the distribution. Allowed values are http1.1 and http2. The default is http2." + type = string + default = "http2" +} + +variable "is_ipv6_enabled" { + description = "Whether the IPv6 is enabled for the distribution." + type = bool + default = null +} + +variable "price_class" { + description = "The price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100" + type = string + default = null +} + +variable "retain_on_delete" { + description = "Disables the distribution instead of deleting it when destroying the resource through Terraform. If this is set, the distribution needs to be deleted manually afterwards." + type = bool + default = false +} + +variable "wait_for_deployment" { + description = "If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this tofalse will skip the process." + type = bool + default = true +} + +variable "web_acl_id" { + description = "If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL." + type = string + default = null +} + +variable "tags" { + description = "A map of tags to assign to the resource." + type = map(string) + default = null +} + +variable "origin" { + description = "One or more origins for this distribution (multiples allowed)." + type = any + default = null +} + +variable "origin_group" { + description = "One or more origin_group for this distribution (multiples allowed)." + type = any + default = {} +} + +variable "viewer_certificate" { + description = "The SSL configuration for this distribution" + type = any + default = { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1" + } +} + +variable "geo_restriction" { + description = "The restriction configuration for this distribution (geo_restrictions)" + type = any + default = {} +} + +variable "logging_config" { + description = "The logging configuration that controls how logs are written to your distribution (maximum one)." + type = any + default = {} +} + +variable "custom_error_response" { + description = "One or more custom error response elements" + type = any + default = {} +} + +variable "default_cache_behavior" { + description = "The default cache behavior for this distribution" + type = any + default = null +} + +variable "ordered_cache_behavior" { + description = "An ordered list of cache behaviors resource for this distribution. List from top to bottom in order of precedence. The topmost cache behavior will have precedence 0." + type = any + default = [] +} + +variable "create_monitoring_subscription" { + description = "If enabled, the resource for monitoring subscription will created." + type = bool + default = false +} + +variable "realtime_metrics_subscription_status" { + description = "A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`." + type = string + default = "Enabled" +} + +variable "s3_bucket_name" { + description = "Name of the bucket" + default = "" +} + +variable "zone_name" { + description = "Zone Name of the record that will be created for alias" + default = "" +} + +variable "create_and_attach_origin_request_policy" { + description = "Controls if CloudFront cache policy should be created" + type = bool + default = true +} + +variable "cloudfront_elb" { + description = "Controls if CloudFront origin will be a Load Balancer" + type = bool + default = false +} + +variable "origin_request_policy_name" { + description = "Policy name of the origin request to make it regional" + type = string + default = "" +} + +variable "cache_policy_name" { + description = "Policy name of the origin request to make it regional" + type = string + default = "" +} + +variable "origin_request_policy_headers" { + description = "List of headers" + type = list(string) + default = [] +} + + +variable "cache_default_ttl" { + description = "Default TTL seconds" + type = number + default = 50 +} +variable "cache_max_ttl" { + description = "Max TTL seconds" + type = number + default = 100 +} +variable "cache_min_ttl" { + description = "Min TTL seconds" + type = number + default = 1 +} + +variable "accept_encoding_brotli" { + description = "Accept encoding Brotli" + type = bool + default = false +} + +variable "accept_encoding_gzip" { + description = "Accept encoding gzip" + type = bool + default = false +} + +variable "bucket_policy_resources" { + description = "This is optional. List of arns. Default is 'all items in bucket'. Usage ex: ['arn:aws:s3::://*'] " + default = null + type = list(any) +} + +variable "cors_rule" { + description = "List of maps containing rules for Cross-Origin Resource Sharing." + type = any + default = [] +} + +variable "control_object_ownership" { + description = "Whether to manage S3 Bucket Ownership Controls on this bucket." + type = bool + default = false +} + +variable "object_ownership" { + description = "Object ownership. Valid values: BucketOwnerEnforced, BucketOwnerPreferred or ObjectWriter. 'BucketOwnerEnforced': ACLs are disabled, and the bucket owner automatically owns and has full control over every object in the bucket. 'BucketOwnerPreferred': Objects uploaded to the bucket change ownership to the bucket owner if the objects are uploaded with the bucket-owner-full-control canned ACL. 'ObjectWriter': The uploading account will own the object if the object is uploaded with the bucket-owner-full-control canned ACL." + type = string + default = "ObjectWriter" +} + +variable "website" { + description = "Map containing static web-site hosting or redirect configuration." + type = any # map(string) + default = {} +} + +variable "create_s3_bucket" { + default = true + type = bool +} diff --git a/modules/aws-cloudfront/versions.tf b/modules/aws-cloudfront/versions.tf new file mode 100644 index 0000000..6c1e911 --- /dev/null +++ b/modules/aws-cloudfront/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.29" + } + } +} diff --git a/modules/aws-cloudfront/wrappers/README.md b/modules/aws-cloudfront/wrappers/README.md new file mode 100644 index 0000000..74012e5 --- /dev/null +++ b/modules/aws-cloudfront/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudfront/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudfront.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudfront/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudfront/wrappers/main.tf b/modules/aws-cloudfront/wrappers/main.tf new file mode 100644 index 0000000..c438f5f --- /dev/null +++ b/modules/aws-cloudfront/wrappers/main.tf @@ -0,0 +1,33 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create_distribution = try(each.value.create_distribution, var.defaults.create_distribution, true) + create_origin_access_identity = try(each.value.create_origin_access_identity, var.defaults.create_origin_access_identity, false) + origin_access_identities = try(each.value.origin_access_identities, var.defaults.origin_access_identities, {}) + aliases = try(each.value.aliases, var.defaults.aliases, null) + comment = try(each.value.comment, var.defaults.comment, null) + default_root_object = try(each.value.default_root_object, var.defaults.default_root_object, null) + enabled = try(each.value.enabled, var.defaults.enabled, true) + http_version = try(each.value.http_version, var.defaults.http_version, "http2") + is_ipv6_enabled = try(each.value.is_ipv6_enabled, var.defaults.is_ipv6_enabled, null) + price_class = try(each.value.price_class, var.defaults.price_class, null) + retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, false) + wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, true) + web_acl_id = try(each.value.web_acl_id, var.defaults.web_acl_id, null) + tags = try(each.value.tags, var.defaults.tags, null) + origin = try(each.value.origin, var.defaults.origin, null) + origin_group = try(each.value.origin_group, var.defaults.origin_group, {}) + viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1" + }) + geo_restriction = try(each.value.geo_restriction, var.defaults.geo_restriction, {}) + logging_config = try(each.value.logging_config, var.defaults.logging_config, {}) + custom_error_response = try(each.value.custom_error_response, var.defaults.custom_error_response, {}) + default_cache_behavior = try(each.value.default_cache_behavior, var.defaults.default_cache_behavior, null) + ordered_cache_behavior = try(each.value.ordered_cache_behavior, var.defaults.ordered_cache_behavior, []) + create_monitoring_subscription = try(each.value.create_monitoring_subscription, var.defaults.create_monitoring_subscription, false) + realtime_metrics_subscription_status = try(each.value.realtime_metrics_subscription_status, var.defaults.realtime_metrics_subscription_status, "Enabled") +} diff --git a/modules/aws-cloudfront/wrappers/outputs.tf b/modules/aws-cloudfront/wrappers/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudfront/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudfront/wrappers/variables.tf b/modules/aws-cloudfront/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudfront/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudfront/wrappers/versions.tf b/modules/aws-cloudfront/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudfront/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudtrail-s3-bucket/.gitignore b/modules/aws-cloudtrail-s3-bucket/.gitignore new file mode 100644 index 0000000..1989e6a --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/.gitignore @@ -0,0 +1,11 @@ +# Compiled files +*.tfstate +*.tfstate.backup + +# Module directory +.terraform/ +.idea +terraform-aws-cloudtrail-s3-bucket.iml + +.build-harness +build-harness diff --git a/modules/aws-cloudtrail-s3-bucket/LICENSE b/modules/aws-cloudtrail-s3-bucket/LICENSE new file mode 100644 index 0000000..177cbde --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/LICENSE @@ -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 2018-2020 Cloud Posse, 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. diff --git a/modules/aws-cloudtrail-s3-bucket/README.md b/modules/aws-cloudtrail-s3-bucket/README.md new file mode 100644 index 0000000..1d5e049 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/README.md @@ -0,0 +1,413 @@ + + +# terraform-aws-cloudtrail-s3-bucket [![Latest Release](https://img.shields.io/github/release/cloudposse/terraform-aws-cloudtrail-s3-bucket.svg)](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket/releases/latest) [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) + + +[![README Header][readme_header_img]][readme_header_link] + +[![Cloud Posse][logo]](https://cpco.io/homepage) + + + +Terraform module to provision an S3 bucket with built in policy to allow [CloudTrail](https://aws.amazon.com/cloudtrail/) [logs](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-management-and-data-events-with-cloudtrail.html). + +This is useful if an organization uses a number of separate AWS accounts to isolate the Audit environment from other environments (production, staging, development). + +In this case, you create CloudTrail in the production environment (Production AWS account), +while the S3 bucket to store the CloudTrail logs is created in the Audit AWS account, restricting access to the logs only to the users/groups from the Audit account. + + +The module supports the following: + +1. Forced [server-side encryption](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html) at rest for the S3 bucket +2. S3 bucket [versioning](https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html) to easily recover from both unintended user actions and application failures +3. S3 bucket is protected from deletion if it's not empty ([force_destroy](https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#force_destroy) set to `false`) + +--- + +This project is part of our comprehensive ["SweetOps"](https://cpco.io/sweetops) approach towards DevOps. +[][share_email] +[][share_googleplus] +[][share_facebook] +[][share_reddit] +[][share_linkedin] +[][share_twitter] + + +[![Terraform Open Source Modules](https://docs.cloudposse.com/images/terraform-open-source-modules.svg)][terraform_modules] + + + +It's 100% Open Source and licensed under the [APACHE2](LICENSE). + + + + + + + +We literally have [*hundreds of terraform modules*][terraform_modules] that are Open Source and well-maintained. Check them out! + + + + + + +## Security & Compliance [](https://bridgecrew.io/) + +Security scanning is graciously provided by Bridgecrew. Bridgecrew is the leading fully hosted, cloud-native solution providing continuous Terraform security and compliance. + +| Benchmark | Description | +|--------|---------------| +| [![Infrastructure Security](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/general)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=INFRASTRUCTURE+SECURITY) | Infrastructure Security Compliance | +| [![CIS KUBERNETES](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/cis_kubernetes)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=CIS+KUBERNETES+V1.5) | Center for Internet Security, KUBERNETES Compliance | +| [![CIS AWS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/cis_aws)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=CIS+AWS+V1.2) | Center for Internet Security, AWS Compliance | +| [![CIS AZURE](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/cis_azure)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=CIS+AZURE+V1.1) | Center for Internet Security, AZURE Compliance | +| [![PCI-DSS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/pci)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=PCI-DSS+V3.2) | Payment Card Industry Data Security Standards Compliance | +| [![NIST-800-53](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/nist)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=NIST-800-53) | National Institute of Standards and Technology Compliance | +| [![ISO27001](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/iso)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=ISO27001) | Information Security Management System, ISO/IEC 27001 Compliance | +| [![SOC2](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/soc2)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=SOC2)| Service Organization Control 2 Compliance | +| [![CIS GCP](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/cis_gcp)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=CIS+GCP+V1.1) | Center for Internet Security, GCP Compliance | +| [![HIPAA](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail-s3-bucket/hipaa)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail-s3-bucket&benchmark=HIPAA) | Health Insurance Portability and Accountability Compliance | + + + +## Usage + + +**IMPORTANT:** We do not pin modules to versions in our examples because of the +difficulty of keeping the versions in the documentation in sync with the latest released versions. +We highly recommend that in your code you pin the version to the exact version you are +using so that your infrastructure remains stable, and update versions in a +systematic way so that they do not catch you by surprise. + +Also, because of a bug in the Terraform registry ([hashicorp/terraform#21417](https://github.com/hashicorp/terraform/issues/21417)), +the registry shows many of our inputs as required when in fact they are optional. +The table below correctly indicates which inputs are required. + + +```hcl +module "s3_bucket" { + source = "cloudposse/cloudtrail-s3-bucket/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "prod" + name = "cluster" +} +``` + + + + + + + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [access\_log\_label](#module\_access\_log\_label) | cloudposse/label/null | 0.25.0 | +| [s3\_access\_log\_bucket](#module\_s3\_access\_log\_bucket) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [s3\_bucket](#module\_s3\_bucket) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [abort\_incomplete\_multipart\_upload\_days](#input\_abort\_incomplete\_multipart\_upload\_days) | Maximum time (in days) that you want to allow multipart uploads to remain in progress | `number` | `5` | no | +| [access\_log\_bucket\_name](#input\_access\_log\_bucket\_name) | Name of the S3 bucket where s3 access log will be sent to | `string` | `""` | no | +| [acl](#input\_acl) | The canned ACL to apply. We recommend log-delivery-write for compatibility with AWS services | `string` | `"log-delivery-write"` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `true` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [block\_public\_acls](#input\_block\_public\_acls) | Set to `false` to disable the blocking of new public access lists on the bucket | `bool` | `true` | no | +| [block\_public\_policy](#input\_block\_public\_policy) | Set to `false` to disable the blocking of new public policies on the bucket | `bool` | `true` | no | +| [bucket\_notifications\_enabled](#input\_bucket\_notifications\_enabled) | Send notifications for the object created events. Used for 3rd-party log collection from a bucket. This does not affect access log bucket created by this module. To enable bucket notifications on the access log bucket, create it separately using the cloudposse/s3-log-storage/aws | `bool` | `false` | no | +| [bucket\_notifications\_prefix](#input\_bucket\_notifications\_prefix) | Prefix filter. Used to manage object notifications | `string` | `""` | no | +| [bucket\_notifications\_type](#input\_bucket\_notifications\_type) | Type of the notification configuration. Only SQS is supported. | `string` | `"SQS"` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_access\_log\_bucket](#input\_create\_access\_log\_bucket) | A flag to indicate if a bucket for s3 access logs should be created | `bool` | `false` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_glacier\_transition](#input\_enable\_glacier\_transition) | Glacier transition might just increase your bill. Set to false to disable lifecycle transitions to AWS Glacier. | `bool` | `false` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [expiration\_days](#input\_expiration\_days) | Number of days after which to expunge the objects | `number` | `90` | no | +| [force\_destroy](#input\_force\_destroy) | (Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | +| [glacier\_transition\_days](#input\_glacier\_transition\_days) | Number of days after which to move the data to the glacier storage tier | `number` | `60` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | +| [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN used for the SSE-KMS encryption. This can only be used when you set the value of sse\_algorithm as aws:kms. The default aws/s3 AWS KMS master key is used if this element is absent while the sse\_algorithm is aws:kms | `string` | `""` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [lifecycle\_prefix](#input\_lifecycle\_prefix) | Prefix filter. Used to manage object lifecycle events | `string` | `""` | no | +| [lifecycle\_rule\_enabled](#input\_lifecycle\_rule\_enabled) | Enable lifecycle events on this bucket | `bool` | `true` | no | +| [lifecycle\_tags](#input\_lifecycle\_tags) | Tags filter. Used to manage object lifecycle events | `map(string)` | `{}` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | Specifies when noncurrent object versions expire | `number` | `90` | no | +| [noncurrent\_version\_transition\_days](#input\_noncurrent\_version\_transition\_days) | Specifies when noncurrent object versions transitions | `number` | `30` | no | +| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no | +| [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are AES256 and aws:kms | `string` | `"AES256"` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [standard\_transition\_days](#input\_standard\_transition\_days) | Number of days to persist in the standard storage tier before moving to the infrequent access tier | `number` | `30` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [versioning\_enabled](#input\_versioning\_enabled) | A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [bucket\_arn](#output\_bucket\_arn) | Bucket ARN | +| [bucket\_domain\_name](#output\_bucket\_domain\_name) | FQDN of bucket | +| [bucket\_id](#output\_bucket\_id) | Bucket ID | +| [bucket\_notifications\_sqs\_queue\_arn](#output\_bucket\_notifications\_sqs\_queue\_arn) | Notifications SQS queue ARN | +| [prefix](#output\_prefix) | Prefix configured for lifecycle rules | + + + + +## Share the Love + +Like this project? Please give it a ★ on [our GitHub](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket)! (it helps us **a lot**) + +Are you using this project or any of our other projects? Consider [leaving a testimonial][testimonial]. =) + + + +## Related Projects + +Check out these related projects. + +- [terraform-aws-cloudtrail](https://github.com/cloudposse/terraform-aws-cloudtrail) - Terraform module to provision an AWS CloudTrail and an encrypted S3 bucket with versioning to store CloudTrail logs +- [terraform-aws-cloudtrail-cloudwatch-alarms](https://github.com/cloudposse/terraform-aws-cloudtrail-cloudwatch-alarms) - Terraform module for creating alarms for tracking important changes and occurances from cloudtrail. +- [terraform-aws-s3-log-storage](https://github.com/cloudposse/terraform-aws-s3-log-storage) - This module creates an S3 bucket suitable for receiving logs from other AWS services such as S3, CloudFront, and CloudTrail +- [terraform-aws-cloudtrail-s3-bucket](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket) - S3 bucket with built in IAM policy to allow CloudTrail logs + +## Help + +**Got a question?** We got answers. + +File a GitHub [issue](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket/issues), send us an [email][email] or join our [Slack Community][slack]. + +[![README Commercial Support][readme_commercial_support_img]][readme_commercial_support_link] + +## DevOps Accelerator for Startups + + +We are a [**DevOps Accelerator**][commercial_support]. We'll help you build your cloud infrastructure from the ground up so you can own it. Then we'll show you how to operate it and stick around for as long as you need us. + +[![Learn More](https://img.shields.io/badge/learn%20more-success.svg?style=for-the-badge)][commercial_support] + +Work directly with our team of DevOps experts via email, slack, and video conferencing. + +We deliver 10x the value for a fraction of the cost of a full-time engineer. Our track record is not even funny. If you want things done right and you need it done FAST, then we're your best bet. + +- **Reference Architecture.** You'll get everything you need from the ground up built using 100% infrastructure as code. +- **Release Engineering.** You'll have end-to-end CI/CD with unlimited staging environments. +- **Site Reliability Engineering.** You'll have total visibility into your apps and microservices. +- **Security Baseline.** You'll have built-in governance with accountability and audit logs for all changes. +- **GitOps.** You'll be able to operate your infrastructure via Pull Requests. +- **Training.** You'll receive hands-on training so your team can operate what we build. +- **Questions.** You'll have a direct line of communication between our teams via a Shared Slack channel. +- **Troubleshooting.** You'll get help to triage when things aren't working. +- **Code Reviews.** You'll receive constructive feedback on Pull Requests. +- **Bug Fixes.** We'll rapidly work with you to fix any bugs in our projects. + +## Slack Community + +Join our [Open Source Community][slack] on Slack. It's **FREE** for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totally *sweet* infrastructure. + +## Discourse Forums + +Participate in our [Discourse Forums][discourse]. Here you'll find answers to commonly asked questions. Most questions will be related to the enormous number of projects we support on our GitHub. Come here to collaborate on answers, find solutions, and get ideas about the products and services we value. It only takes a minute to get started! Just sign in with SSO using your GitHub account. + +## Newsletter + +Sign up for [our newsletter][newsletter] that covers everything on our technology radar. Receive updates on what we're up to on GitHub as well as awesome new projects we discover. + +## Office Hours + +[Join us every Wednesday via Zoom][office_hours] for our weekly "Lunch & Learn" sessions. It's **FREE** for everyone! + +[![zoom](https://img.cloudposse.com/fit-in/200x200/https://cloudposse.com/wp-content/uploads/2019/08/Powered-by-Zoom.png")][office_hours] + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket/issues) to report any bugs or file feature requests. + +### Developing + +If you are interested in being a contributor and want to get involved in developing this project or [help out](https://cpco.io/help-out) with our other projects, we would love to hear from you! Shoot us an [email][email]. + +In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull Request** so that we can review your changes + +**NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request! + + +## Copyright + +Copyright © 2017-2022 [Cloud Posse, LLC](https://cpco.io/copyright) + + + +## License + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +See [LICENSE](LICENSE) for full details. + +```text +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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 + + https://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. +``` + + + + + + + + + +## Trademarks + +All other trademarks referenced herein are the property of their respective owners. + +## About + +This project is maintained and funded by [Cloud Posse, LLC][website]. Like it? Please let us know by [leaving a testimonial][testimonial]! + +[![Cloud Posse][logo]][website] + +We're a [DevOps Professional Services][hire] company based in Los Angeles, CA. We ❤️ [Open Source Software][we_love_open_source]. + +We offer [paid support][commercial_support] on all of our projects. + +Check out [our other projects][github], [follow us on twitter][twitter], [apply for a job][jobs], or [hire us][hire] to help with your cloud strategy and implementation. + + + +### Contributors + + +| [![Andriy Knysh][aknysh_avatar]][aknysh_homepage]
[Andriy Knysh][aknysh_homepage] | +|---| + + + [aknysh_homepage]: https://github.com/aknysh + [aknysh_avatar]: https://img.cloudposse.com/150x150/https://github.com/aknysh.png + +[![README Footer][readme_footer_img]][readme_footer_link] +[![Beacon][beacon]][website] + + [logo]: https://cloudposse.com/logo-300x69.svg + [docs]: https://cpco.io/docs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=docs + [website]: https://cpco.io/homepage?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=website + [github]: https://cpco.io/github?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=github + [jobs]: https://cpco.io/jobs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=jobs + [hire]: https://cpco.io/hire?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=hire + [slack]: https://cpco.io/slack?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=slack + [linkedin]: https://cpco.io/linkedin?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=linkedin + [twitter]: https://cpco.io/twitter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=twitter + [testimonial]: https://cpco.io/leave-testimonial?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=testimonial + [office_hours]: https://cloudposse.com/office-hours?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=office_hours + [newsletter]: https://cpco.io/newsletter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=newsletter + [discourse]: https://ask.sweetops.com/?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=discourse + [email]: https://cpco.io/email?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=email + [commercial_support]: https://cpco.io/commercial-support?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=commercial_support + [we_love_open_source]: https://cpco.io/we-love-open-source?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=we_love_open_source + [terraform_modules]: https://cpco.io/terraform-modules?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=terraform_modules + [readme_header_img]: https://cloudposse.com/readme/header/img + [readme_header_link]: https://cloudposse.com/readme/header/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=readme_header_link + [readme_footer_img]: https://cloudposse.com/readme/footer/img + [readme_footer_link]: https://cloudposse.com/readme/footer/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=readme_footer_link + [readme_commercial_support_img]: https://cloudposse.com/readme/commercial-support/img + [readme_commercial_support_link]: https://cloudposse.com/readme/commercial-support/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail-s3-bucket&utm_content=readme_commercial_support_link + [share_twitter]: https://twitter.com/intent/tweet/?text=terraform-aws-cloudtrail-s3-bucket&url=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [share_linkedin]: https://www.linkedin.com/shareArticle?mini=true&title=terraform-aws-cloudtrail-s3-bucket&url=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [share_reddit]: https://reddit.com/submit/?url=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [share_facebook]: https://facebook.com/sharer/sharer.php?u=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [share_googleplus]: https://plus.google.com/share?url=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [share_email]: mailto:?subject=terraform-aws-cloudtrail-s3-bucket&body=https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket + [beacon]: https://ga-beacon.cloudposse.com/UA-76589703-4/cloudposse/terraform-aws-cloudtrail-s3-bucket?pixel&cs=github&cm=readme&an=terraform-aws-cloudtrail-s3-bucket diff --git a/modules/aws-cloudtrail-s3-bucket/README.yaml b/modules/aws-cloudtrail-s3-bucket/README.yaml new file mode 100644 index 0000000..594c729 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/README.yaml @@ -0,0 +1,78 @@ +--- +# +# This is the canonical configuration for the `README.md` +# Run `make readme` to rebuild the `README.md` +# + +# Name of this project +name: terraform-aws-cloudtrail-s3-bucket + +# Logo for this project +#logo: docs/logo.png + +# License of this project +license: "APACHE2" + +# Canonical GitHub repo +github_repo: cloudposse/terraform-aws-cloudtrail-s3-bucket + +# Badges to display +badges: + - name: "Latest Release" + image: "https://img.shields.io/github/release/cloudposse/terraform-aws-cloudtrail-s3-bucket.svg" + url: "https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket/releases/latest" + - name: "Slack Community" + image: "https://slack.cloudposse.com/badge.svg" + url: "https://slack.cloudposse.com" + +related: + - name: "terraform-aws-cloudtrail" + description: "Terraform module to provision an AWS CloudTrail and an encrypted S3 bucket with versioning to store CloudTrail logs" + url: "https://github.com/cloudposse/terraform-aws-cloudtrail" + - name: "terraform-aws-cloudtrail-cloudwatch-alarms" + description: "Terraform module for creating alarms for tracking important changes and occurances from cloudtrail." + url: "https://github.com/cloudposse/terraform-aws-cloudtrail-cloudwatch-alarms" + - name: "terraform-aws-s3-log-storage" + description: "This module creates an S3 bucket suitable for receiving logs from other AWS services such as S3, CloudFront, and CloudTrail" + url: "https://github.com/cloudposse/terraform-aws-s3-log-storage" + - name: "terraform-aws-cloudtrail-s3-bucket" + description: "S3 bucket with built in IAM policy to allow CloudTrail logs" + url: "https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket" + +# Short description of this project +description: |- + Terraform module to provision an S3 bucket with built in policy to allow [CloudTrail](https://aws.amazon.com/cloudtrail/) [logs](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-management-and-data-events-with-cloudtrail.html). + + This is useful if an organization uses a number of separate AWS accounts to isolate the Audit environment from other environments (production, staging, development). + + In this case, you create CloudTrail in the production environment (Production AWS account), + while the S3 bucket to store the CloudTrail logs is created in the Audit AWS account, restricting access to the logs only to the users/groups from the Audit account. + + + The module supports the following: + + 1. Forced [server-side encryption](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html) at rest for the S3 bucket + 2. S3 bucket [versioning](https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html) to easily recover from both unintended user actions and application failures + 3. S3 bucket is protected from deletion if it's not empty ([force_destroy](https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#force_destroy) set to `false`) + +# How to use this project +usage: |- + ```hcl + module "s3_bucket" { + source = "cloudposse/cloudtrail-s3-bucket/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "prod" + name = "cluster" + } + ``` + +include: + - "docs/targets.md" + - "docs/terraform.md" + +# Contributors to this project +contributors: + - name: "Andriy Knysh" + github: "aknysh" diff --git a/modules/aws-cloudtrail-s3-bucket/context.tf b/modules/aws-cloudtrail-s3-bucket/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-cloudtrail-s3-bucket/docs/targets.md b/modules/aws-cloudtrail-s3-bucket/docs/targets.md new file mode 100644 index 0000000..3dce8b3 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/docs/targets.md @@ -0,0 +1,12 @@ + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + diff --git a/modules/aws-cloudtrail-s3-bucket/docs/terraform.md b/modules/aws-cloudtrail-s3-bucket/docs/terraform.md new file mode 100644 index 0000000..26c0295 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/docs/terraform.md @@ -0,0 +1,89 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [access\_log\_label](#module\_access\_log\_label) | cloudposse/label/null | 0.25.0 | +| [s3\_access\_log\_bucket](#module\_s3\_access\_log\_bucket) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [s3\_bucket](#module\_s3\_bucket) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [abort\_incomplete\_multipart\_upload\_days](#input\_abort\_incomplete\_multipart\_upload\_days) | Maximum time (in days) that you want to allow multipart uploads to remain in progress | `number` | `5` | no | +| [access\_log\_bucket\_name](#input\_access\_log\_bucket\_name) | Name of the S3 bucket where s3 access log will be sent to | `string` | `""` | no | +| [acl](#input\_acl) | The canned ACL to apply. We recommend log-delivery-write for compatibility with AWS services | `string` | `"log-delivery-write"` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `true` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [block\_public\_acls](#input\_block\_public\_acls) | Set to `false` to disable the blocking of new public access lists on the bucket | `bool` | `true` | no | +| [block\_public\_policy](#input\_block\_public\_policy) | Set to `false` to disable the blocking of new public policies on the bucket | `bool` | `true` | no | +| [bucket\_notifications\_enabled](#input\_bucket\_notifications\_enabled) | Send notifications for the object created events. Used for 3rd-party log collection from a bucket. This does not affect access log bucket created by this module. To enable bucket notifications on the access log bucket, create it separately using the cloudposse/s3-log-storage/aws | `bool` | `false` | no | +| [bucket\_notifications\_prefix](#input\_bucket\_notifications\_prefix) | Prefix filter. Used to manage object notifications | `string` | `""` | no | +| [bucket\_notifications\_type](#input\_bucket\_notifications\_type) | Type of the notification configuration. Only SQS is supported. | `string` | `"SQS"` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_access\_log\_bucket](#input\_create\_access\_log\_bucket) | A flag to indicate if a bucket for s3 access logs should be created | `bool` | `false` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_glacier\_transition](#input\_enable\_glacier\_transition) | Glacier transition might just increase your bill. Set to false to disable lifecycle transitions to AWS Glacier. | `bool` | `false` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [expiration\_days](#input\_expiration\_days) | Number of days after which to expunge the objects | `number` | `90` | no | +| [force\_destroy](#input\_force\_destroy) | (Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | +| [glacier\_transition\_days](#input\_glacier\_transition\_days) | Number of days after which to move the data to the glacier storage tier | `number` | `60` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | +| [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN used for the SSE-KMS encryption. This can only be used when you set the value of sse\_algorithm as aws:kms. The default aws/s3 AWS KMS master key is used if this element is absent while the sse\_algorithm is aws:kms | `string` | `""` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [lifecycle\_prefix](#input\_lifecycle\_prefix) | Prefix filter. Used to manage object lifecycle events | `string` | `""` | no | +| [lifecycle\_rule\_enabled](#input\_lifecycle\_rule\_enabled) | Enable lifecycle events on this bucket | `bool` | `true` | no | +| [lifecycle\_tags](#input\_lifecycle\_tags) | Tags filter. Used to manage object lifecycle events | `map(string)` | `{}` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | Specifies when noncurrent object versions expire | `number` | `90` | no | +| [noncurrent\_version\_transition\_days](#input\_noncurrent\_version\_transition\_days) | Specifies when noncurrent object versions transitions | `number` | `30` | no | +| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no | +| [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are AES256 and aws:kms | `string` | `"AES256"` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [standard\_transition\_days](#input\_standard\_transition\_days) | Number of days to persist in the standard storage tier before moving to the infrequent access tier | `number` | `30` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [versioning\_enabled](#input\_versioning\_enabled) | A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [bucket\_arn](#output\_bucket\_arn) | Bucket ARN | +| [bucket\_domain\_name](#output\_bucket\_domain\_name) | FQDN of bucket | +| [bucket\_id](#output\_bucket\_id) | Bucket ID | +| [bucket\_notifications\_sqs\_queue\_arn](#output\_bucket\_notifications\_sqs\_queue\_arn) | Notifications SQS queue ARN | +| [prefix](#output\_prefix) | Prefix configured for lifecycle rules | + diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/context.tf b/modules/aws-cloudtrail-s3-bucket/examples/complete/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/fixtures.us-east-2.tfvars b/modules/aws-cloudtrail-s3-bucket/examples/complete/fixtures.us-east-2.tfvars new file mode 100644 index 0000000..80f06f8 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/fixtures.us-east-2.tfvars @@ -0,0 +1,9 @@ +enabled = true + +region = "us-east-2" + +namespace = "eg" + +stage = "test" + +name = "cloudtrail-s3-bucket" diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/main.tf b/modules/aws-cloudtrail-s3-bucket/examples/complete/main.tf new file mode 100644 index 0000000..c8a3517 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/main.tf @@ -0,0 +1,12 @@ +provider "aws" { + region = var.region +} + +module "cloudtrail_s3_bucket" { + source = "../../" + + force_destroy = true + create_access_log_bucket = true + + context = module.this.context +} diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/outputs.tf b/modules/aws-cloudtrail-s3-bucket/examples/complete/outputs.tf new file mode 100644 index 0000000..cf79af2 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/outputs.tf @@ -0,0 +1,14 @@ +output "bucket_domain_name" { + value = module.cloudtrail_s3_bucket.bucket_domain_name + description = "FQDN of bucket" +} + +output "bucket_id" { + value = module.cloudtrail_s3_bucket.bucket_id + description = "Bucket Name (aka ID)" +} + +output "bucket_arn" { + value = module.cloudtrail_s3_bucket.bucket_arn + description = "Bucket ARN" +} diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/variables.tf b/modules/aws-cloudtrail-s3-bucket/examples/complete/variables.tf new file mode 100644 index 0000000..d2cdd03 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/variables.tf @@ -0,0 +1,3 @@ +variable "region" { + type = string +} diff --git a/modules/aws-cloudtrail-s3-bucket/examples/complete/versions.tf b/modules/aws-cloudtrail-s3-bucket/examples/complete/versions.tf new file mode 100644 index 0000000..28298e5 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/examples/complete/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.2" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-cloudtrail-s3-bucket/main.tf b/modules/aws-cloudtrail-s3-bucket/main.tf new file mode 100644 index 0000000..51d6785 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/main.tf @@ -0,0 +1,130 @@ + +module "access_log_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = "cloudtrail-access-log" + + context = module.this.context +} + +module "s3_bucket" { + source = "cloudposse/s3-log-storage/aws" + version = "0.26.0" + enabled = module.this.enabled + + acl = var.acl + policy = join("", data.aws_iam_policy_document.default.*.json) + force_destroy = var.force_destroy + versioning_enabled = var.versioning_enabled + lifecycle_rule_enabled = var.lifecycle_rule_enabled + lifecycle_prefix = var.lifecycle_prefix + lifecycle_tags = var.lifecycle_tags + noncurrent_version_expiration_days = var.noncurrent_version_expiration_days + noncurrent_version_transition_days = var.noncurrent_version_transition_days + standard_transition_days = var.standard_transition_days + glacier_transition_days = var.glacier_transition_days + enable_glacier_transition = var.enable_glacier_transition + expiration_days = var.expiration_days + abort_incomplete_multipart_upload_days = var.abort_incomplete_multipart_upload_days + sse_algorithm = var.sse_algorithm + kms_master_key_arn = var.kms_master_key_arn + block_public_acls = var.block_public_acls + block_public_policy = var.block_public_policy + ignore_public_acls = var.ignore_public_acls + restrict_public_buckets = var.restrict_public_buckets + access_log_bucket_name = local.access_log_bucket_name + allow_ssl_requests_only = var.allow_ssl_requests_only + bucket_notifications_enabled = var.bucket_notifications_enabled + bucket_notifications_type = var.bucket_notifications_type + bucket_notifications_prefix = var.bucket_notifications_prefix + + context = module.this.context +} + +module "s3_access_log_bucket" { + source = "cloudposse/s3-log-storage/aws" + version = "0.26.0" + enabled = module.this.enabled && var.create_access_log_bucket + + acl = var.acl + policy = "" + force_destroy = var.force_destroy + versioning_enabled = var.versioning_enabled + lifecycle_rule_enabled = var.lifecycle_rule_enabled + lifecycle_prefix = var.lifecycle_prefix + lifecycle_tags = var.lifecycle_tags + noncurrent_version_expiration_days = var.noncurrent_version_expiration_days + noncurrent_version_transition_days = var.noncurrent_version_transition_days + standard_transition_days = var.standard_transition_days + glacier_transition_days = var.glacier_transition_days + enable_glacier_transition = var.enable_glacier_transition + expiration_days = var.expiration_days + abort_incomplete_multipart_upload_days = var.abort_incomplete_multipart_upload_days + sse_algorithm = var.sse_algorithm + kms_master_key_arn = var.kms_master_key_arn + block_public_acls = var.block_public_acls + block_public_policy = var.block_public_policy + ignore_public_acls = var.ignore_public_acls + restrict_public_buckets = var.restrict_public_buckets + access_log_bucket_name = "" + allow_ssl_requests_only = var.allow_ssl_requests_only + + attributes = ["access-logs"] + context = module.this.context +} + +data "aws_iam_policy_document" "default" { + count = module.this.enabled ? 1 : 0 + source_json = var.policy == "" ? null : var.policy + + statement { + sid = "AWSCloudTrailAclCheck" + + principals { + type = "Service" + identifiers = ["cloudtrail.amazonaws.com"] + } + + actions = [ + "s3:GetBucketAcl", + ] + + resources = [ + "${local.arn_format}:s3:::${module.this.id}", + ] + } + + statement { + sid = "AWSCloudTrailWrite" + + principals { + type = "Service" + identifiers = ["cloudtrail.amazonaws.com", "config.amazonaws.com"] + } + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${local.arn_format}:s3:::${module.this.id}/*", + ] + + condition { + test = "StringEquals" + variable = "s3:x-amz-acl" + + values = [ + "bucket-owner-full-control", + ] + } + } +} + +data "aws_partition" "current" {} + +locals { + access_log_bucket_name = var.create_access_log_bucket == true ? module.s3_access_log_bucket.bucket_id : var.access_log_bucket_name + arn_format = "arn:${data.aws_partition.current.partition}" +} diff --git a/modules/aws-cloudtrail-s3-bucket/outputs.tf b/modules/aws-cloudtrail-s3-bucket/outputs.tf new file mode 100644 index 0000000..dad425c --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/outputs.tf @@ -0,0 +1,24 @@ +output "bucket_domain_name" { + value = module.s3_bucket.bucket_domain_name + description = "FQDN of bucket" +} + +output "bucket_id" { + value = module.s3_bucket.bucket_id + description = "Bucket ID" +} + +output "bucket_arn" { + value = module.s3_bucket.bucket_arn + description = "Bucket ARN" +} + +output "prefix" { + value = module.s3_bucket.prefix + description = "Prefix configured for lifecycle rules" +} + +output "bucket_notifications_sqs_queue_arn" { + value = module.s3_bucket.bucket_notifications_sqs_queue_arn + description = "Notifications SQS queue ARN" +} \ No newline at end of file diff --git a/modules/aws-cloudtrail-s3-bucket/variables.tf b/modules/aws-cloudtrail-s3-bucket/variables.tf new file mode 100644 index 0000000..33b45f0 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/variables.tf @@ -0,0 +1,150 @@ +variable "acl" { + type = string + description = "The canned ACL to apply. We recommend log-delivery-write for compatibility with AWS services" + default = "log-delivery-write" +} + +variable "policy" { + type = string + description = "A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" + default = "" +} + +variable "lifecycle_prefix" { + type = string + description = "Prefix filter. Used to manage object lifecycle events" + default = "" +} + +variable "lifecycle_tags" { + type = map(string) + description = "Tags filter. Used to manage object lifecycle events" + default = {} +} + +variable "force_destroy" { + type = bool + description = "(Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable" + default = false +} + +variable "lifecycle_rule_enabled" { + type = bool + description = "Enable lifecycle events on this bucket" + default = true +} + +variable "versioning_enabled" { + type = bool + description = "A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket" + default = false +} + +variable "noncurrent_version_expiration_days" { + description = "Specifies when noncurrent object versions expire" + default = 90 +} + +variable "noncurrent_version_transition_days" { + description = "Specifies when noncurrent object versions transitions" + default = 30 +} + +variable "standard_transition_days" { + description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier" + default = 30 +} + +variable "glacier_transition_days" { + description = "Number of days after which to move the data to the glacier storage tier" + default = 60 +} + +variable "enable_glacier_transition" { + type = bool + default = false + description = "Glacier transition might just increase your bill. Set to false to disable lifecycle transitions to AWS Glacier." +} + +variable "expiration_days" { + description = "Number of days after which to expunge the objects" + default = 90 +} + +variable "abort_incomplete_multipart_upload_days" { + type = number + default = 5 + description = "Maximum time (in days) that you want to allow multipart uploads to remain in progress" +} + +variable "sse_algorithm" { + type = string + description = "The server-side encryption algorithm to use. Valid values are AES256 and aws:kms" + default = "AES256" +} + +variable "kms_master_key_arn" { + type = string + description = "The AWS KMS master key ARN used for the SSE-KMS encryption. This can only be used when you set the value of sse_algorithm as aws:kms. The default aws/s3 AWS KMS master key is used if this element is absent while the sse_algorithm is aws:kms" + default = "" +} + +variable "block_public_acls" { + type = bool + default = true + description = "Set to `false` to disable the blocking of new public access lists on the bucket" +} + +variable "block_public_policy" { + type = bool + default = true + description = "Set to `false` to disable the blocking of new public policies on the bucket" +} + +variable "ignore_public_acls" { + type = bool + default = true + description = "Set to `false` to disable the ignoring of public access lists on the bucket" +} + +variable "restrict_public_buckets" { + type = bool + default = true + description = "Set to `false` to disable the restricting of making the bucket public" +} + +variable "access_log_bucket_name" { + type = string + default = "" + description = "Name of the S3 bucket where s3 access log will be sent to" +} + +variable "create_access_log_bucket" { + type = bool + default = false + description = "A flag to indicate if a bucket for s3 access logs should be created" +} + +variable "allow_ssl_requests_only" { + type = bool + default = true + description = "Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests" +} + +variable "bucket_notifications_enabled" { + type = bool + description = "Send notifications for the object created events. Used for 3rd-party log collection from a bucket. This does not affect access log bucket created by this module. To enable bucket notifications on the access log bucket, create it separately using the cloudposse/s3-log-storage/aws" + default = false +} + +variable "bucket_notifications_type" { + type = string + description = "Type of the notification configuration. Only SQS is supported." + default = "SQS" +} + +variable "bucket_notifications_prefix" { + type = string + description = "Prefix filter. Used to manage object notifications" + default = "" +} \ No newline at end of file diff --git a/modules/aws-cloudtrail-s3-bucket/versions.tf b/modules/aws-cloudtrail-s3-bucket/versions.tf new file mode 100644 index 0000000..85d1d00 --- /dev/null +++ b/modules/aws-cloudtrail-s3-bucket/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} diff --git a/modules/aws-cloudtrail/.gitignore b/modules/aws-cloudtrail/.gitignore new file mode 100644 index 0000000..dbfbc21 --- /dev/null +++ b/modules/aws-cloudtrail/.gitignore @@ -0,0 +1,11 @@ +# Compiled files +*.tfstate +*.tfstate.backup + +# Module directory +**/.terraform/ +.idea +terraform-aws-cloudtrail.iml +.build-harness +build-harness +**/.terraform.lock.hcl \ No newline at end of file diff --git a/modules/aws-cloudtrail/README.md b/modules/aws-cloudtrail/README.md new file mode 100644 index 0000000..aa309b2 --- /dev/null +++ b/modules/aws-cloudtrail/README.md @@ -0,0 +1,435 @@ + + +# terraform-aws-cloudtrail [![Latest Release](https://img.shields.io/github/release/cloudposse/terraform-aws-cloudtrail.svg)](https://travis-ci.org/cloudposse/terraform-aws-cloudtrail/releases) [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) + + +[![README Header][readme_header_img]][readme_header_link] + +[![Cloud Posse][logo]](https://cpco.io/homepage) + + + +Terraform module to provision an AWS [CloudTrail](https://aws.amazon.com/cloudtrail/). + +The module accepts an encrypted S3 bucket with versioning to store CloudTrail logs. + +The bucket could be from the same AWS account or from a different account. + +This is useful if an organization uses a number of separate AWS accounts to isolate the Audit environment from other environments (production, staging, development). + +In this case, you create CloudTrail in the production environment (production AWS account), +while the S3 bucket to store the CloudTrail logs is created in the Audit AWS account, restricting access to the logs only to the users/groups from the Audit account. + +--- + +This project is part of our comprehensive ["SweetOps"](https://cpco.io/sweetops) approach towards DevOps. +[][share_email] +[][share_googleplus] +[][share_facebook] +[][share_reddit] +[][share_linkedin] +[][share_twitter] + + +[![Terraform Open Source Modules](https://docs.cloudposse.com/images/terraform-open-source-modules.svg)][terraform_modules] + + + +It's 100% Open Source and licensed under the [APACHE2](LICENSE). + + + + + + + +We literally have [*hundreds of terraform modules*][terraform_modules] that are Open Source and well-maintained. Check them out! + + + + + + +## Security & Compliance [](https://bridgecrew.io/) + +Security scanning is graciously provided by Bridgecrew. Bridgecrew is the leading fully hosted, cloud-native solution providing continuous Terraform security and compliance. + +| Benchmark | Description | +|--------|---------------| +| [![Infrastructure Security](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/general)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=INFRASTRUCTURE+SECURITY) | Infrastructure Security Compliance | +| [![CIS KUBERNETES](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/cis_kubernetes)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=CIS+KUBERNETES+V1.5) | Center for Internet Security, KUBERNETES Compliance | +| [![CIS AWS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/cis_aws)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=CIS+AWS+V1.2) | Center for Internet Security, AWS Compliance | +| [![CIS AZURE](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/cis_azure)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=CIS+AZURE+V1.1) | Center for Internet Security, AZURE Compliance | +| [![PCI-DSS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/pci)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=PCI-DSS+V3.2) | Payment Card Industry Data Security Standards Compliance | +| [![NIST-800-53](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/nist)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=NIST-800-53) | National Institute of Standards and Technology Compliance | +| [![ISO27001](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/iso)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=ISO27001) | Information Security Management System, ISO/IEC 27001 Compliance | +| [![SOC2](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/soc2)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=SOC2)| Service Organization Control 2 Compliance | +| [![CIS GCP](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/cis_gcp)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=CIS+GCP+V1.1) | Center for Internet Security, GCP Compliance | +| [![HIPAA](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-cloudtrail/hipaa)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-cloudtrail&benchmark=HIPAA) | Health Insurance Portability and Accountability Compliance | + + + +## Usage + + +**IMPORTANT:** We do not pin modules to versions in our examples because of the +difficulty of keeping the versions in the documentation in sync with the latest released versions. +We highly recommend that in your code you pin the version to the exact version you are +using so that your infrastructure remains stable, and update versions in a +systematic way so that they do not catch you by surprise. + +Also, because of a bug in the Terraform registry ([hashicorp/terraform#21417](https://github.com/hashicorp/terraform/issues/21417)), +the registry shows many of our inputs as required when in fact they are optional. +The table below correctly indicates which inputs are required. + + +```hcl +module "cloudtrail" { + source = "cloudposse/cloudtrail/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" + enable_log_file_validation = true + include_global_service_events = true + is_multi_region_trail = false + enable_logging = true + s3_bucket_name = "my-cloudtrail-logs-bucket" +} +``` + +__NOTE:__ To create an S3 bucket for CloudTrail logs, use [terraform-aws-cloudtrail-s3-bucket](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket) module. +It creates an S3 bucket and an IAM policy to allow CloudTrail logs. + + +```hcl +module "cloudtrail" { + source = "cloudposse/cloudtrail/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" + enable_log_file_validation = true + include_global_service_events = true + is_multi_region_trail = false + enable_logging = true + s3_bucket_name = module.cloudtrail_s3_bucket.bucket_id +} + +module "cloudtrail_s3_bucket" { + source = "cloudposse/cloudtrail-s3-bucket/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" +} +``` + +For a complete example, see [examples/complete](examples/complete). + + + + + + + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudtrail.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [cloud\_watch\_logs\_group\_arn](#input\_cloud\_watch\_logs\_group\_arn) | Specifies a log group name using an Amazon Resource Name (ARN), that represents the log group to which CloudTrail logs will be delivered | `string` | `""` | no | +| [cloud\_watch\_logs\_role\_arn](#input\_cloud\_watch\_logs\_role\_arn) | Specifies the role for the CloudWatch Logs endpoint to assume to write to a user’s log group | `string` | `""` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_log\_file\_validation](#input\_enable\_log\_file\_validation) | Specifies whether log file integrity validation is enabled. Creates signed digest for validated contents of logs | `bool` | `true` | no | +| [enable\_logging](#input\_enable\_logging) | Enable logging for the trail | `bool` | `true` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [event\_selector](#input\_event\_selector) | Specifies an event selector for enabling data event logging. See: https://www.terraform.io/docs/providers/aws/r/cloudtrail.html for details on this variable |
list(object({
include_management_events = bool
read_write_type = string

data_resource = list(object({
type = string
values = list(string)
}))
}))
| `[]` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [include\_global\_service\_events](#input\_include\_global\_service\_events) | Specifies whether the trail is publishing events from global services such as IAM to the log files | `bool` | `false` | no | +| [insight\_selector](#input\_insight\_selector) | Specifies an insight selector for type of insights to log on a trail |
list(object({
insight_type = string
}))
| `[]` | no | +| [is\_multi\_region\_trail](#input\_is\_multi\_region\_trail) | Specifies whether the trail is created in the current region or in all regions | `bool` | `true` | no | +| [is\_organization\_trail](#input\_is\_organization\_trail) | The trail is an AWS Organizations trail | `bool` | `false` | no | +| [kms\_key\_arn](#input\_kms\_key\_arn) | Specifies the KMS key ARN to use to encrypt the logs delivered by CloudTrail | `string` | `""` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [s3\_bucket\_name](#input\_s3\_bucket\_name) | S3 bucket name for CloudTrail logs | `string` | n/a | yes | +| [s3\_key\_prefix](#input\_s3\_key\_prefix) | Prefix for S3 bucket used by Cloudtrail to store logs | `string` | `null` | no | +| [sns\_topic\_name](#input\_sns\_topic\_name) | Specifies the name of the Amazon SNS topic defined for notification of log file delivery | `string` | `null` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudtrail\_arn](#output\_cloudtrail\_arn) | The Amazon Resource Name of the trail | +| [cloudtrail\_home\_region](#output\_cloudtrail\_home\_region) | The region in which the trail was created | +| [cloudtrail\_id](#output\_cloudtrail\_id) | The name of the trail | + + + + +## Share the Love + +Like this project? Please give it a ★ on [our GitHub](https://github.com/cloudposse/terraform-aws-cloudtrail)! (it helps us **a lot**) + +Are you using this project or any of our other projects? Consider [leaving a testimonial][testimonial]. =) + + + +## Related Projects + +Check out these related projects. + +- [terraform-aws-cloudtrail-cloudwatch-alarms](https://github.com/cloudposse/terraform-aws-cloudtrail-cloudwatch-alarms) - Terraform module for creating alarms for tracking important changes and occurances from cloudtrail. +- [terraform-aws-cloudtrail-s3-bucket](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket) - S3 bucket with built in IAM policy to allow CloudTrail logs +- [terraform-aws-s3-log-storage](https://github.com/cloudposse/terraform-aws-s3-log-storage) - This module creates an S3 bucket suitable for receiving logs from other AWS services such as S3, CloudFront, and CloudTrail + +## Help + +**Got a question?** We got answers. + +File a GitHub [issue](https://github.com/cloudposse/terraform-aws-cloudtrail/issues), send us an [email][email] or join our [Slack Community][slack]. + +[![README Commercial Support][readme_commercial_support_img]][readme_commercial_support_link] + +## DevOps Accelerator for Startups + + +We are a [**DevOps Accelerator**][commercial_support]. We'll help you build your cloud infrastructure from the ground up so you can own it. Then we'll show you how to operate it and stick around for as long as you need us. + +[![Learn More](https://img.shields.io/badge/learn%20more-success.svg?style=for-the-badge)][commercial_support] + +Work directly with our team of DevOps experts via email, slack, and video conferencing. + +We deliver 10x the value for a fraction of the cost of a full-time engineer. Our track record is not even funny. If you want things done right and you need it done FAST, then we're your best bet. + +- **Reference Architecture.** You'll get everything you need from the ground up built using 100% infrastructure as code. +- **Release Engineering.** You'll have end-to-end CI/CD with unlimited staging environments. +- **Site Reliability Engineering.** You'll have total visibility into your apps and microservices. +- **Security Baseline.** You'll have built-in governance with accountability and audit logs for all changes. +- **GitOps.** You'll be able to operate your infrastructure via Pull Requests. +- **Training.** You'll receive hands-on training so your team can operate what we build. +- **Questions.** You'll have a direct line of communication between our teams via a Shared Slack channel. +- **Troubleshooting.** You'll get help to triage when things aren't working. +- **Code Reviews.** You'll receive constructive feedback on Pull Requests. +- **Bug Fixes.** We'll rapidly work with you to fix any bugs in our projects. + +## Slack Community + +Join our [Open Source Community][slack] on Slack. It's **FREE** for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totally *sweet* infrastructure. + +## Discourse Forums + +Participate in our [Discourse Forums][discourse]. Here you'll find answers to commonly asked questions. Most questions will be related to the enormous number of projects we support on our GitHub. Come here to collaborate on answers, find solutions, and get ideas about the products and services we value. It only takes a minute to get started! Just sign in with SSO using your GitHub account. + +## Newsletter + +Sign up for [our newsletter][newsletter] that covers everything on our technology radar. Receive updates on what we're up to on GitHub as well as awesome new projects we discover. + +## Office Hours + +[Join us every Wednesday via Zoom][office_hours] for our weekly "Lunch & Learn" sessions. It's **FREE** for everyone! + +[![zoom](https://img.cloudposse.com/fit-in/200x200/https://cloudposse.com/wp-content/uploads/2019/08/Powered-by-Zoom.png")][office_hours] + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-cloudtrail/issues) to report any bugs or file feature requests. + +### Developing + +If you are interested in being a contributor and want to get involved in developing this project or [help out](https://cpco.io/help-out) with our other projects, we would love to hear from you! Shoot us an [email][email]. + +In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull Request** so that we can review your changes + +**NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request! + + +## Copyright + +Copyright © 2017-2022 [Cloud Posse, LLC](https://cpco.io/copyright) + + + +## License + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +See [LICENSE](LICENSE) for full details. + +```text +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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 + + https://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. +``` + + + + + + + + + +## Trademarks + +All other trademarks referenced herein are the property of their respective owners. + +## About + +This project is maintained and funded by [Cloud Posse, LLC][website]. Like it? Please let us know by [leaving a testimonial][testimonial]! + +[![Cloud Posse][logo]][website] + +We're a [DevOps Professional Services][hire] company based in Los Angeles, CA. We ❤️ [Open Source Software][we_love_open_source]. + +We offer [paid support][commercial_support] on all of our projects. + +Check out [our other projects][github], [follow us on twitter][twitter], [apply for a job][jobs], or [hire us][hire] to help with your cloud strategy and implementation. + + + +### Contributors + + +| [![Erik Osterman][osterman_avatar]][osterman_homepage]
[Erik Osterman][osterman_homepage] | [![Andriy Knysh][aknysh_avatar]][aknysh_homepage]
[Andriy Knysh][aknysh_homepage] | [![Sergey Vasilyev][s2504s_avatar]][s2504s_homepage]
[Sergey Vasilyev][s2504s_homepage] | [![Valeriy][drama17_avatar]][drama17_homepage]
[Valeriy][drama17_homepage] | [![Jamie Nelson][Jamie-BitFlight_avatar]][Jamie-BitFlight_homepage]
[Jamie Nelson][Jamie-BitFlight_homepage] | +|---|---|---|---|---| + + + [osterman_homepage]: https://github.com/osterman + [osterman_avatar]: https://img.cloudposse.com/150x150/https://github.com/osterman.png + [aknysh_homepage]: https://github.com/aknysh + [aknysh_avatar]: https://img.cloudposse.com/150x150/https://github.com/aknysh.png + [s2504s_homepage]: https://github.com/s2504s + [s2504s_avatar]: https://img.cloudposse.com/150x150/https://github.com/s2504s.png + [drama17_homepage]: https://github.com/drama17 + [drama17_avatar]: https://img.cloudposse.com/150x150/https://github.com/drama17.png + [Jamie-BitFlight_homepage]: https://github.com/Jamie-BitFlight + [Jamie-BitFlight_avatar]: https://img.cloudposse.com/150x150/https://github.com/Jamie-BitFlight.png + +[![README Footer][readme_footer_img]][readme_footer_link] +[![Beacon][beacon]][website] + + [logo]: https://cloudposse.com/logo-300x69.svg + [docs]: https://cpco.io/docs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=docs + [website]: https://cpco.io/homepage?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=website + [github]: https://cpco.io/github?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=github + [jobs]: https://cpco.io/jobs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=jobs + [hire]: https://cpco.io/hire?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=hire + [slack]: https://cpco.io/slack?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=slack + [linkedin]: https://cpco.io/linkedin?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=linkedin + [twitter]: https://cpco.io/twitter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=twitter + [testimonial]: https://cpco.io/leave-testimonial?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=testimonial + [office_hours]: https://cloudposse.com/office-hours?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=office_hours + [newsletter]: https://cpco.io/newsletter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=newsletter + [discourse]: https://ask.sweetops.com/?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=discourse + [email]: https://cpco.io/email?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=email + [commercial_support]: https://cpco.io/commercial-support?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=commercial_support + [we_love_open_source]: https://cpco.io/we-love-open-source?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=we_love_open_source + [terraform_modules]: https://cpco.io/terraform-modules?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=terraform_modules + [readme_header_img]: https://cloudposse.com/readme/header/img + [readme_header_link]: https://cloudposse.com/readme/header/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=readme_header_link + [readme_footer_img]: https://cloudposse.com/readme/footer/img + [readme_footer_link]: https://cloudposse.com/readme/footer/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=readme_footer_link + [readme_commercial_support_img]: https://cloudposse.com/readme/commercial-support/img + [readme_commercial_support_link]: https://cloudposse.com/readme/commercial-support/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-cloudtrail&utm_content=readme_commercial_support_link + [share_twitter]: https://twitter.com/intent/tweet/?text=terraform-aws-cloudtrail&url=https://github.com/cloudposse/terraform-aws-cloudtrail + [share_linkedin]: https://www.linkedin.com/shareArticle?mini=true&title=terraform-aws-cloudtrail&url=https://github.com/cloudposse/terraform-aws-cloudtrail + [share_reddit]: https://reddit.com/submit/?url=https://github.com/cloudposse/terraform-aws-cloudtrail + [share_facebook]: https://facebook.com/sharer/sharer.php?u=https://github.com/cloudposse/terraform-aws-cloudtrail + [share_googleplus]: https://plus.google.com/share?url=https://github.com/cloudposse/terraform-aws-cloudtrail + [share_email]: mailto:?subject=terraform-aws-cloudtrail&body=https://github.com/cloudposse/terraform-aws-cloudtrail + [beacon]: https://ga-beacon.cloudposse.com/UA-76589703-4/cloudposse/terraform-aws-cloudtrail?pixel&cs=github&cm=readme&an=terraform-aws-cloudtrail + diff --git a/modules/aws-cloudtrail/README.yaml b/modules/aws-cloudtrail/README.yaml new file mode 100644 index 0000000..c019af8 --- /dev/null +++ b/modules/aws-cloudtrail/README.yaml @@ -0,0 +1,108 @@ +# +# This is the canonical configuration for the `README.md` +# Run `make readme` to rebuild the `README.md` +# + +# Name of this project +name: terraform-aws-cloudtrail +# Logo for this project +#logo: docs/logo.png + +# License of this project +license: "APACHE2" +# Canonical GitHub repo +github_repo: cloudposse/terraform-aws-cloudtrail +# Badges to display +badges: + - name: "Latest Release" + image: "https://img.shields.io/github/release/cloudposse/terraform-aws-cloudtrail.svg" + url: "https://travis-ci.org/cloudposse/terraform-aws-cloudtrail/releases" + - name: "Slack Community" + image: "https://slack.cloudposse.com/badge.svg" + url: "https://slack.cloudposse.com" +related: + - name: "terraform-aws-cloudtrail-cloudwatch-alarms" + description: "Terraform module for creating alarms for tracking important changes and occurances from cloudtrail." + url: "https://github.com/cloudposse/terraform-aws-cloudtrail-cloudwatch-alarms" + - name: "terraform-aws-cloudtrail-s3-bucket" + description: "S3 bucket with built in IAM policy to allow CloudTrail logs" + url: "https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket" + - name: "terraform-aws-s3-log-storage" + description: "This module creates an S3 bucket suitable for receiving logs from other AWS services such as S3, CloudFront, and CloudTrail" + url: "https://github.com/cloudposse/terraform-aws-s3-log-storage" +# Short description of this project +description: |- + Terraform module to provision an AWS [CloudTrail](https://aws.amazon.com/cloudtrail/). + + The module accepts an encrypted S3 bucket with versioning to store CloudTrail logs. + + The bucket could be from the same AWS account or from a different account. + + This is useful if an organization uses a number of separate AWS accounts to isolate the Audit environment from other environments (production, staging, development). + + In this case, you create CloudTrail in the production environment (production AWS account), + while the S3 bucket to store the CloudTrail logs is created in the Audit AWS account, restricting access to the logs only to the users/groups from the Audit account. +# How to use this project +usage: |- + ```hcl + module "cloudtrail" { + source = "cloudposse/cloudtrail/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" + enable_log_file_validation = true + include_global_service_events = true + is_multi_region_trail = false + enable_logging = true + s3_bucket_name = "my-cloudtrail-logs-bucket" + } + ``` + + __NOTE:__ To create an S3 bucket for CloudTrail logs, use [terraform-aws-cloudtrail-s3-bucket](https://github.com/cloudposse/terraform-aws-cloudtrail-s3-bucket) module. + It creates an S3 bucket and an IAM policy to allow CloudTrail logs. + + + ```hcl + module "cloudtrail" { + source = "cloudposse/cloudtrail/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" + enable_log_file_validation = true + include_global_service_events = true + is_multi_region_trail = false + enable_logging = true + s3_bucket_name = module.cloudtrail_s3_bucket.bucket_id + } + + module "cloudtrail_s3_bucket" { + source = "cloudposse/cloudtrail-s3-bucket/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + namespace = "eg" + stage = "dev" + name = "cluster" + } + ``` + + For a complete example, see [examples/complete](examples/complete). +include: + - "docs/targets.md" + - "docs/terraform.md" + +# Contributors to this project +contributors: + - name: "Erik Osterman" + github: "osterman" + - name: "Andriy Knysh" + github: "aknysh" + - name: "Sergey Vasilyev" + github: "s2504s" + - name: "Valeriy" + github: "drama17" + - name: "Jamie Nelson" + github: "Jamie-BitFlight" diff --git a/modules/aws-cloudtrail/context.tf b/modules/aws-cloudtrail/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-cloudtrail/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-cloudtrail/docs/targets.md b/modules/aws-cloudtrail/docs/targets.md new file mode 100644 index 0000000..3dce8b3 --- /dev/null +++ b/modules/aws-cloudtrail/docs/targets.md @@ -0,0 +1,12 @@ + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + diff --git a/modules/aws-cloudtrail/docs/terraform.md b/modules/aws-cloudtrail/docs/terraform.md new file mode 100644 index 0000000..4835184 --- /dev/null +++ b/modules/aws-cloudtrail/docs/terraform.md @@ -0,0 +1,70 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudtrail.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [cloud\_watch\_logs\_group\_arn](#input\_cloud\_watch\_logs\_group\_arn) | Specifies a log group name using an Amazon Resource Name (ARN), that represents the log group to which CloudTrail logs will be delivered | `string` | `""` | no | +| [cloud\_watch\_logs\_role\_arn](#input\_cloud\_watch\_logs\_role\_arn) | Specifies the role for the CloudWatch Logs endpoint to assume to write to a user’s log group | `string` | `""` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_log\_file\_validation](#input\_enable\_log\_file\_validation) | Specifies whether log file integrity validation is enabled. Creates signed digest for validated contents of logs | `bool` | `true` | no | +| [enable\_logging](#input\_enable\_logging) | Enable logging for the trail | `bool` | `true` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [event\_selector](#input\_event\_selector) | Specifies an event selector for enabling data event logging. See: https://www.terraform.io/docs/providers/aws/r/cloudtrail.html for details on this variable |
list(object({
include_management_events = bool
read_write_type = string

data_resource = list(object({
type = string
values = list(string)
}))
}))
| `[]` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [include\_global\_service\_events](#input\_include\_global\_service\_events) | Specifies whether the trail is publishing events from global services such as IAM to the log files | `bool` | `false` | no | +| [insight\_selector](#input\_insight\_selector) | Specifies an insight selector for type of insights to log on a trail |
list(object({
insight_type = string
}))
| `[]` | no | +| [is\_multi\_region\_trail](#input\_is\_multi\_region\_trail) | Specifies whether the trail is created in the current region or in all regions | `bool` | `true` | no | +| [is\_organization\_trail](#input\_is\_organization\_trail) | The trail is an AWS Organizations trail | `bool` | `false` | no | +| [kms\_key\_arn](#input\_kms\_key\_arn) | Specifies the KMS key ARN to use to encrypt the logs delivered by CloudTrail | `string` | `""` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [s3\_bucket\_name](#input\_s3\_bucket\_name) | S3 bucket name for CloudTrail logs | `string` | n/a | yes | +| [s3\_key\_prefix](#input\_s3\_key\_prefix) | Prefix for S3 bucket used by Cloudtrail to store logs | `string` | `null` | no | +| [sns\_topic\_name](#input\_sns\_topic\_name) | Specifies the name of the Amazon SNS topic defined for notification of log file delivery | `string` | `null` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudtrail\_arn](#output\_cloudtrail\_arn) | The Amazon Resource Name of the trail | +| [cloudtrail\_home\_region](#output\_cloudtrail\_home\_region) | The region in which the trail was created | +| [cloudtrail\_id](#output\_cloudtrail\_id) | The name of the trail | + diff --git a/modules/aws-cloudtrail/examples/complete/context.tf b/modules/aws-cloudtrail/examples/complete/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-cloudtrail/examples/complete/fixtures.us-east-2.tfvars b/modules/aws-cloudtrail/examples/complete/fixtures.us-east-2.tfvars new file mode 100644 index 0000000..8a24e60 --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/fixtures.us-east-2.tfvars @@ -0,0 +1,19 @@ +enabled = true + +region = "us-east-2" + +namespace = "eg" + +stage = "test" + +name = "cloudtrail-test" + +enable_log_file_validation = true + +is_multi_region_trail = false + +include_global_service_events = false + +enable_logging = true + +is_organization_trail = false diff --git a/modules/aws-cloudtrail/examples/complete/main.tf b/modules/aws-cloudtrail/examples/complete/main.tf new file mode 100644 index 0000000..cca3d1f --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/main.tf @@ -0,0 +1,25 @@ +provider "aws" { + region = var.region +} + +module "cloudtrail" { + source = "../../" + + enable_logging = var.enable_logging + enable_log_file_validation = var.enable_log_file_validation + include_global_service_events = var.include_global_service_events + is_multi_region_trail = var.is_multi_region_trail + is_organization_trail = var.is_organization_trail + s3_bucket_name = module.cloudtrail_s3_bucket.bucket_id + + context = module.this.context +} + +module "cloudtrail_s3_bucket" { + source = "cloudposse/cloudtrail-s3-bucket/aws" + version = "0.14.0" + + force_destroy = true + + context = module.this.context +} diff --git a/modules/aws-cloudtrail/examples/complete/outputs.tf b/modules/aws-cloudtrail/examples/complete/outputs.tf new file mode 100644 index 0000000..a5684d1 --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/outputs.tf @@ -0,0 +1,29 @@ +output "cloudtrail_id" { + value = module.cloudtrail.cloudtrail_id + description = "The name of the trail" +} + +output "cloudtrail_home_region" { + value = module.cloudtrail.cloudtrail_home_region + description = "The region in which the trail was created" +} + +output "cloudtrail_arn" { + value = module.cloudtrail.cloudtrail_arn + description = "The Amazon Resource Name of the trail" +} + +output "cloudtrail_bucket_domain_name" { + value = module.cloudtrail_s3_bucket.bucket_domain_name + description = "FQDN of the CloudTral S3 bucket" +} + +output "cloudtrail_bucket_id" { + value = module.cloudtrail_s3_bucket.bucket_id + description = "Name of the CloudTral S3 bucket" +} + +output "cloudtrail_bucket_arn" { + value = module.cloudtrail_s3_bucket.bucket_arn + description = "ARN of the CloudTral S3 bucket" +} diff --git a/modules/aws-cloudtrail/examples/complete/variables.tf b/modules/aws-cloudtrail/examples/complete/variables.tf new file mode 100644 index 0000000..ea3e34b --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/variables.tf @@ -0,0 +1,29 @@ +variable "region" { + type = string + description = "AWS region" +} + +variable "enable_log_file_validation" { + type = bool + description = "Specifies whether log file integrity validation is enabled. Creates signed digest for validated contents of logs" +} + +variable "is_multi_region_trail" { + type = bool + description = "Specifies whether the trail is created in the current region or in all regions" +} + +variable "include_global_service_events" { + type = bool + description = "Specifies whether the trail is publishing events from global services such as IAM to the log files" +} + +variable "enable_logging" { + type = bool + description = "Enable logging for the trail" +} + +variable "is_organization_trail" { + type = bool + description = "The trail is an AWS Organizations trail" +} diff --git a/modules/aws-cloudtrail/examples/complete/versions.tf b/modules/aws-cloudtrail/examples/complete/versions.tf new file mode 100644 index 0000000..28298e5 --- /dev/null +++ b/modules/aws-cloudtrail/examples/complete/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.2" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-cloudtrail/main.tf b/modules/aws-cloudtrail/main.tf new file mode 100644 index 0000000..2152e78 --- /dev/null +++ b/modules/aws-cloudtrail/main.tf @@ -0,0 +1,39 @@ +resource "aws_cloudtrail" "default" { + count = module.this.enabled ? 1 : 0 + name = module.this.id + enable_logging = var.enable_logging + s3_bucket_name = var.s3_bucket_name + enable_log_file_validation = var.enable_log_file_validation + sns_topic_name = var.sns_topic_name + is_multi_region_trail = var.is_multi_region_trail + include_global_service_events = var.include_global_service_events + cloud_watch_logs_role_arn = var.cloud_watch_logs_role_arn + cloud_watch_logs_group_arn = var.cloud_watch_logs_group_arn + tags = module.this.tags + kms_key_id = var.kms_key_arn + is_organization_trail = var.is_organization_trail + s3_key_prefix = var.s3_key_prefix + + dynamic "insight_selector" { + for_each = var.insight_selector + content { + insight_type = insight_selector.value.insight_type + } + } + + dynamic "event_selector" { + for_each = var.event_selector + content { + include_management_events = lookup(event_selector.value, "include_management_events", null) + read_write_type = lookup(event_selector.value, "read_write_type", null) + + dynamic "data_resource" { + for_each = lookup(event_selector.value, "data_resource", []) + content { + type = data_resource.value.type + values = data_resource.value.values + } + } + } + } +} \ No newline at end of file diff --git a/modules/aws-cloudtrail/outputs.tf b/modules/aws-cloudtrail/outputs.tf new file mode 100644 index 0000000..b5d8c0f --- /dev/null +++ b/modules/aws-cloudtrail/outputs.tf @@ -0,0 +1,14 @@ +output "cloudtrail_id" { + value = join("", aws_cloudtrail.default.*.id) + description = "The name of the trail" +} + +output "cloudtrail_home_region" { + value = join("", aws_cloudtrail.default.*.home_region) + description = "The region in which the trail was created" +} + +output "cloudtrail_arn" { + value = join("", aws_cloudtrail.default.*.arn) + description = "The Amazon Resource Name of the trail" +} diff --git a/modules/aws-cloudtrail/variables.tf b/modules/aws-cloudtrail/variables.tf new file mode 100644 index 0000000..f3e5a04 --- /dev/null +++ b/modules/aws-cloudtrail/variables.tf @@ -0,0 +1,88 @@ +variable "enable_log_file_validation" { + type = bool + default = true + description = "Specifies whether log file integrity validation is enabled. Creates signed digest for validated contents of logs" +} + +variable "is_multi_region_trail" { + type = bool + default = true + description = "Specifies whether the trail is created in the current region or in all regions" +} + +variable "include_global_service_events" { + type = bool + default = false + description = "Specifies whether the trail is publishing events from global services such as IAM to the log files" +} + +variable "enable_logging" { + type = bool + default = true + description = "Enable logging for the trail" +} + +variable "s3_bucket_name" { + type = string + description = "S3 bucket name for CloudTrail logs" +} + +variable "cloud_watch_logs_role_arn" { + type = string + description = "Specifies the role for the CloudWatch Logs endpoint to assume to write to a user’s log group" + default = "" +} + +variable "cloud_watch_logs_group_arn" { + type = string + description = "Specifies a log group name using an Amazon Resource Name (ARN), that represents the log group to which CloudTrail logs will be delivered" + default = "" +} + +variable "insight_selector" { + type = list(object({ + insight_type = string + })) + + description = "Specifies an insight selector for type of insights to log on a trail" + default = [] +} + +variable "event_selector" { + type = list(object({ + include_management_events = bool + read_write_type = string + + data_resource = list(object({ + type = string + values = list(string) + })) + })) + + description = "Specifies an event selector for enabling data event logging. See: https://www.terraform.io/docs/providers/aws/r/cloudtrail.html for details on this variable" + default = [] +} + +variable "kms_key_arn" { + type = string + description = "Specifies the KMS key ARN to use to encrypt the logs delivered by CloudTrail" + default = "" +} + +variable "is_organization_trail" { + type = bool + default = false + description = "The trail is an AWS Organizations trail" +} + +variable "sns_topic_name" { + type = string + description = "Specifies the name of the Amazon SNS topic defined for notification of log file delivery" + default = null +} + +variable "s3_key_prefix" { + type = string + description = "Prefix for S3 bucket used by Cloudtrail to store logs" + default = null +} \ No newline at end of file diff --git a/modules/aws-cloudtrail/versions.tf b/modules/aws-cloudtrail/versions.tf new file mode 100644 index 0000000..5b2c49b --- /dev/null +++ b/modules/aws-cloudtrail/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-cloudwatch-event-rule-master/README.md b/modules/aws-cloudwatch-event-rule-master/README.md new file mode 100644 index 0000000..dd1a218 --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/README.md @@ -0,0 +1,161 @@ + + +

+ + +

+ Terraform AWS Cloudwatch Event Rule + + +

+ +

+ Terraform module to create cloudwatch event rule on AWS. +

+ +

+ + + Terraform + + + Licence + + + tfsec + + + static-checks + + + +

+

+ + + + + + + + + + + +

+
+ + +We eat, drink, sleep and most importantly love **DevOps**. We are working towards strategies for standardizing architecture while ensuring security for the infrastructure. We are strong believer of the philosophy Bigger problems are always solved by breaking them into smaller manageable problems. Resonating with microservices architecture, it is considered best-practice to run database, cluster, storage in smaller connected yet manageable pieces within the infrastructure. + +This module is basically combination of [Terraform open source](https://www.terraform.io/) and includes automatation tests and examples. It also helps to create and improve your infrastructure with minimalistic code instead of maintaining the whole infrastructure code yourself. + +We have [*fifty plus terraform modules*][terraform_modules]. A few of them are comepleted and are available for open source usage while a few others are in progress. + + + + +## Prerequisites + +This module has a few dependencies: + +- [Terraform 1.x.x](https://learn.hashicorp.com/terraform/getting-started/install.html) +- [Go](https://golang.org/doc/install) +- [github.com/stretchr/testify/assert](https://github.com/stretchr/testify) +- [github.com/gruntwork-io/terratest/modules/terraform](https://github.com/gruntwork-io/terratest) + + + + + + + +## Examples + + +**IMPORTANT:** Since the `master` branch used in `source` varies based on new modifications, we suggest that you use the release versions [here](https://github.com/clouddrove/terraform-aws-cloudwatch-event-rule/releases). + + +### Simple Example +Here is an example of how you can use this module in your inventory structure: +```hcl + module "event-rule" { + source = "clouddrove/cloudwatch-event-rule/aws" + version = "1.0.1" + name = "event-rule" + environment = "test" + label_order = ["environment", "name"] + description = "Event Rule." + schedule_expression = "cron(0/5 * * * ? *)" + target_id = "test" + arn = "arn:aws:lambda:eu-west-1:012345678901:function:test" + } +``` + + + + + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| arn | The Amazon Resource Name (ARN) associated with the role that is used for target invocation. | `string` | `""` | no | +| description | The description for the rule. | `string` | `""` | no | +| enabled | Enable event. | `bool` | `true` | no | +| environment | Environment (e.g. `prod`, `dev`, `staging`). | `string` | `""` | no | +| event\_pattern | (schedule\_expression isn't specified) Event pattern described a JSON object. See full documentation of CloudWatch Events and Event Patterns for details. | `any` | `null` | no | +| input\_path | The value of the JSONPath that is used for extracting part of the matched event when passing it to the target. | `string` | `""` | no | +| input\_paths | Key value pairs specified in the form of JSONPath (for example, time = $.time) | `map(any)` | `{}` | no | +| input\_template | Template to customize data sent to the target. Must be valid JSON. To send a string value, the string value must include double quotes. Values must be escaped for both JSON and Terraform, | `string` | `""` | no | +| is\_enabled | Whether the rule should be enabled (defaults to true). | `bool` | `true` | no | +| label\_order | Label order, e.g. `name`,`application`. | `list(any)` | `[]` | no | +| managedby | ManagedBy, eg 'CloudDrove' or 'AnmolNagpal'. | `string` | `"anmol@clouddrove.com"` | no | +| name | Name (e.g. `app` or `cluster`). | `string` | `""` | no | +| repository | Terraform current module repo | `string` | `"https://github.com/clouddrove/terraform-aws-cloudwatch-event-rule"` | no | +| role\_arn | The Amazon Resource Name (ARN) associated with the role that is used for target invocation. | `string` | `""` | no | +| schedule\_expression | (if event\_pattern isn't specified) The scheduling expression. For example, cron(0 20 \* \* ? \*) or rate(5 minutes). | `any` | `null` | no | +| target\_id | The Amazon Resource Name (ARN) associated with the role that is used for target invocation. | `string` | `""` | no | +| target\_role\_arn | The Amazon Resource Name (ARN) of the IAM role to be used for this target when the rule is triggered. Required if ecs\_target is used. | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arn | The ARN of the cloudwatch metric alarm. | +| id | The ID of the health check. | +| tags | A mapping of tags to assign to the resource. | + + + + +## Testing +In this module testing is performed with [terratest](https://github.com/gruntwork-io/terratest) and it creates a small piece of infrastructure, matches the output like ARN, ID and Tags name etc and destroy infrastructure in your AWS account. This testing is written in GO, so you need a [GO environment](https://golang.org/doc/install) in your system. + +You need to run the following command in the testing folder: +```hcl + go test -run Test +``` + + + +## Feedback +If you come accross a bug or have any feedback, please log it in our [issue tracker](https://github.com/clouddrove/terraform-aws-cloudwatch-event-rule/issues), or feel free to drop us an email at [hello@clouddrove.com](mailto:hello@clouddrove.com). + +If you have found it worth your time, go ahead and give us a ★ on [our GitHub](https://github.com/clouddrove/terraform-aws-cloudwatch-event-rule)! + +## About us + +At [CloudDrove][website], we offer expert guidance, implementation support and services to help organisations accelerate their journey to the cloud. Our services include docker and container orchestration, cloud migration and adoption, infrastructure automation, application modernisation and remediation, and performance engineering. + +

We are The Cloud Experts!

+
+

We ❤️ Open Source and you can check out our other modules to get help with your new Cloud ideas.

+ + [website]: https://clouddrove.com + [github]: https://github.com/clouddrove + [linkedin]: https://cpco.io/linkedin + [twitter]: https://twitter.com/clouddrove/ + [email]: https://clouddrove.com/contact-us.html + [terraform_modules]: https://github.com/clouddrove?utf8=%E2%9C%93&q=terraform-&type=&language= diff --git a/modules/aws-cloudwatch-event-rule-master/_example/example.tf b/modules/aws-cloudwatch-event-rule-master/_example/example.tf new file mode 100644 index 0000000..33e5fe7 --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/_example/example.tf @@ -0,0 +1,25 @@ +provider "aws" { + region = "eu-west-1" +} + +data "aws_caller_identity" "current" {} + +module "event-rule" { + source = "./../" + + name = "event-rule" + environment = "test" + label_order = ["environment", "name"] + + description = "Event Rule." + schedule_expression = "cron(0/5 * * * ? *)" + + target_id = "test" + arn = "arn:aws:lambda:eu-west-1:${data.aws_caller_identity.current.account_id}:function:hello_world_lambda" + input_template = "\" is in state \"" + input_paths = { + instance = "$.detail.instance", + status = "$.detail.status", + } + +} diff --git a/modules/aws-cloudwatch-event-rule-master/_example/outputs.tf b/modules/aws-cloudwatch-event-rule-master/_example/outputs.tf new file mode 100644 index 0000000..678513e --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/_example/outputs.tf @@ -0,0 +1,9 @@ +output "arn" { + value = module.event-rule.*.arn + description = "The ARN of the cloudwatch metric alarm." +} + +output "tags" { + value = module.event-rule.tags + description = "A mapping of tags to assign to the Cloudwatch Alarm." +} \ No newline at end of file diff --git a/modules/aws-cloudwatch-event-rule-master/_example/versions.tf b/modules/aws-cloudwatch-event-rule-master/_example/versions.tf new file mode 100644 index 0000000..2683fac --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/_example/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 0.14.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.1.15" + } + } +} diff --git a/modules/aws-cloudwatch-event-rule-master/main.tf b/modules/aws-cloudwatch-event-rule-master/main.tf new file mode 100644 index 0000000..16d0285 --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/main.tf @@ -0,0 +1,43 @@ + +/* module "labels" { + source = "clouddrove/labels/aws" + version = "0.15.0" + + enabled = var.enabled + name = var.name + environment = var.environment + label_order = var.label_order + managedby = var.managedby +} */ + +resource "aws_cloudwatch_event_rule" "default" { + count = var.enabled == true ? 1 : 0 + + name = var.name + description = var.description + event_pattern = var.event_pattern + schedule_expression = var.schedule_expression + role_arn = var.role_arn + is_enabled = var.is_enabled + #tags = module.labels.tags +} + +#Module : CLOUDWATCH EVENT TARGET +#Description : Terraform module creates Cloudwatch Event Target on AWS. +resource "aws_cloudwatch_event_target" "default" { + count = var.enabled == true ? 1 : 0 + rule = aws_cloudwatch_event_rule.default.*.name[0] + target_id = var.target_id + arn = var.arn + input_path = var.input_path != "" ? var.input_path : null + role_arn = var.target_role_arn + + input_transformer { + + input_paths = var.input_path == "" ? var.input_paths : null + input_template = var.input_path == "" ? var.input_template : null + } + + +} + diff --git a/modules/aws-cloudwatch-event-rule-master/outputs.tf b/modules/aws-cloudwatch-event-rule-master/outputs.tf new file mode 100644 index 0000000..1cc1b34 --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/outputs.tf @@ -0,0 +1,17 @@ +#Module : CLOUDWATCH METRIC ALARM +#Description : Terraform module creates Cloudwatch Alarm on AWS for monitoriing AWS services. +output "name" { + value = aws_cloudwatch_event_rule.default.*.name + description = "The ID of the health check." +} + +output "arn" { + value = aws_cloudwatch_event_rule.default.*.arn + description = "The ARN of the cloudwatch metric alarm." +} + +/* output "tags" { + value = var.tags + description = "A mapping of tags to assign to the resource." +} */ + diff --git a/modules/aws-cloudwatch-event-rule-master/variables.tf b/modules/aws-cloudwatch-event-rule-master/variables.tf new file mode 100644 index 0000000..262f3d5 --- /dev/null +++ b/modules/aws-cloudwatch-event-rule-master/variables.tf @@ -0,0 +1,111 @@ +#Module : LABEL +#Description : Terraform label module variables. +variable "name" { + type = string + default = "" + description = "Name (e.g. `app` or `cluster`)." +} + + +/* variable "environment" { + type = string + default = "" + description = "Environment (e.g. `prod`, `dev`, `staging`)." +} */ + +/* variable "managedby" { + type = string + default = "anmol@clouddrove.com" + description = "ManagedBy, eg 'CloudDrove' or 'AnmolNagpal'." +} */ + +/* variable "repository" { + type = string + default = "https://github.com/clouddrove/terraform-aws-cloudwatch-event-rule" + description = "Terraform current module repo" + + validation { + # regex(...) fails if it cannot find a match + condition = can(regex("^https://", var.repository)) + error_message = "The module-repo value must be a valid Git repo link." + } +} */ + +/* variable "label_order" { + type = list(any) + default = [] + description = "Label order, e.g. `name`,`application`." +} */ + +#Module : CLOUDWATCH METRIC ALARM +#Description : Provides a CloudWatch Metric Alarm resource. +variable "enabled" { + type = bool + default = true + description = "Enable event." +} + +variable "description" { + type = string + default = "" + description = "The description for the rule." +} + +variable "schedule_expression" { + default = null + description = "(if event_pattern isn't specified) The scheduling expression. For example, cron(0 20 * * ? *) or rate(5 minutes)." +} + +variable "event_pattern" { + default = null + description = "(schedule_expression isn't specified) Event pattern described a JSON object. See full documentation of CloudWatch Events and Event Patterns for details." +} + +variable "role_arn" { + type = string + default = "" + description = "The Amazon Resource Name (ARN) associated with the role that is used for target invocation." +} + +variable "is_enabled" { + type = bool + default = true + description = "Whether the rule should be enabled (defaults to true)." +} + +variable "target_id" { + type = string + default = "" + description = "The Amazon Resource Name (ARN) associated with the role that is used for target invocation." +} + +variable "arn" { + type = string + default = "" + description = "The Amazon Resource Name (ARN) associated with the role that is used for target invocation." +} + +variable "input_path" { + type = string + default = "" + description = "The value of the JSONPath that is used for extracting part of the matched event when passing it to the target." +} + +variable "target_role_arn" { + type = string + default = "" + description = "The Amazon Resource Name (ARN) of the IAM role to be used for this target when the rule is triggered. Required if ecs_target is used." +} + +variable "input_paths" { + type = map(any) + default = {} + description = "Key value pairs specified in the form of JSONPath (for example, time = $.time)" + +} + +variable "input_template" { + type = string + default = "" + description = "Template to customize data sent to the target. Must be valid JSON. To send a string value, the string value must include double quotes. Values must be escaped for both JSON and Terraform," +} \ No newline at end of file diff --git a/modules/aws-cloudwatch/README.md b/modules/aws-cloudwatch/README.md new file mode 100644 index 0000000..9499e20 --- /dev/null +++ b/modules/aws-cloudwatch/README.md @@ -0,0 +1,140 @@ +# AWS Cloudwatch Terraform module + +Terraform module which creates Cloudwatch resources on AWS. + +## Usage + +### Log metric filter + +```hcl +module "log_metric_filter" { + source = "terraform-aws-modules/cloudwatch/aws//modules/log-metric-filter" + version = "~> 3.0" + + log_group_name = "my-application-logs" + + name = "error-metric" + pattern = "ERROR" + + metric_transformation_namespace = "MyApplication" + metric_transformation_name = "ErrorCount" +} +``` + +Read [Filter and Pattern Syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html) for explanation of `pattern`. + +### Log group + +```hcl +module "log_group" { + source = "terraform-aws-modules/cloudwatch/aws//modules/log-group" + version = "~> 3.0" + + name = "my-app" + retention_in_days = 120 +} +``` + +### Log stream + +```hcl +module "log_stream" { + source = "terraform-aws-modules/cloudwatch/aws//modules/log-stream" + version = "~> 3.0" + + name = "stream1" + log_group_name = "my-app" +} +``` + +### Metric alarm + +```hcl +module "metric_alarm" { + source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarm" + version = "~> 3.0" + + alarm_name = "my-application-logs-errors" + alarm_description = "Bad errors in my-application-logs" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 1 + threshold = 10 + period = 60 + unit = "Count" + + namespace = "MyApplication" + metric_name = "ErrorCount" + statistic = "Maximum" + + alarm_actions = ["arn:aws:sns:eu-west-1:012345678901:my-sns-queue"] +} +``` + +### Metric alarms by multiple dimensions + +This submodule is useful when you need to create very similar alarms where only dimensions are different (eg, multiple AWS Lambda functions), but the rest of arguments are the same. + +```hcl +module "metric_alarms" { + source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarms-by-multiple-dimensions" + version = "~> 3.0" + + alarm_name = "lambda-duration-" + alarm_description = "Lambda duration is too high" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 1 + threshold = 10 + period = 60 + unit = "Milliseconds" + + namespace = "AWS/Lambda" + metric_name = "Duration" + statistic = "Maximum" + + dimensions = { + "lambda1" = { + FunctionName = "index" + }, + "lambda2" = { + FunctionName = "signup" + }, + } + + alarm_actions = ["arn:aws:sns:eu-west-1:012345678901:my-sns-queue"] +} +``` + +Check out [list of all AWS services that publish CloudWatch metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html) for detailed information about each supported service. + +### CIS AWS Foundations Controls: Metrics + Alarms + +```hcl +module "cis_alarms" { + source = "terraform-aws-modules/cloudwatch/aws//modules/cis-alarms" + version = "~> 3.0" + + log_group_name = "my-cloudtrail-logs" + alarm_actions = ["arn:aws:sns:eu-west-1:012345678901:my-sns-queue"] +} +``` + +AWS CloudTrail normally publishes logs into AWS CloudWatch Logs. This module creates log metric filters together with metric alarms according to [CIS AWS Foundations Benchmark v1.4.0 (05-28-2021)](https://www.cisecurity.org/benchmark/amazon_web_services/). Read more about [CIS AWS Foundations Controls](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html). + +## Examples + +- [Complete Cloudwatch log metric filter and alarm](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/complete-log-metric-filter-and-alarm) +- [Cloudwatch log group with log stream](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/log-group-with-log-stream) +- [Cloudwatch metric alarms for AWS Lambda](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/lambda-metric-alarm) +- [Cloudwatch metric alarms for AWS Lambda with multiple dimensions](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/metric-alarms-by-multiple-dimensions) +- [CIS AWS Foundations Controls: Metrics + Alarms](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/cis-alarms) + + + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/LICENSE) for full details. diff --git a/modules/aws-cloudwatch/modules/cis-alarms/README.md b/modules/aws-cloudwatch/modules/cis-alarms/README.md new file mode 100644 index 0000000..c960984 --- /dev/null +++ b/modules/aws-cloudwatch/modules/cis-alarms/README.md @@ -0,0 +1,56 @@ +# CIS AWS Foundations Controls + +Read more about [CIS AWS Foundations Controls](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html) and stay secure. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_metric_filter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_metric_filter) | resource | +| [aws_cloudwatch_metric_alarm.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [actions\_enabled](#input\_actions\_enabled) | Indicates whether or not actions should be executed during any changes to the alarm's state. | `bool` | `true` | no | +| [alarm\_actions](#input\_alarm\_actions) | List of ARNs to put as Cloudwatch Alarms actions (eg, ARN of SNS topic) | `list(string)` | `[]` | no | +| [control\_overrides](#input\_control\_overrides) | A map of overrides to apply to each control | `any` | `{}` | no | +| [create](#input\_create) | Whether to create the Cloudwatch log metric filter and metric alarms | `bool` | `true` | no | +| [disabled\_controls](#input\_disabled\_controls) | List of IDs of disabled CIS controls | `list(string)` | `[]` | no | +| [insufficient\_data\_actions](#input\_insufficient\_data\_actions) | List of ARNs to put as Cloudwatch insuficient data actions (eg, ARN of SNS topic) | `list(string)` | `[]` | no | +| [log\_group\_name](#input\_log\_group\_name) | The name of the log group to associate the metric filter with | `string` | `""` | no | +| [name\_prefix](#input\_name\_prefix) | A name prefix for the cloudwatch alarm (if use\_random\_name\_prefix is true, this will be ignored) | `string` | `""` | no | +| [namespace](#input\_namespace) | The namespace where metric filter and metric alarm should be cleated | `string` | `"CISBenchmark"` | no | +| [ok\_actions](#input\_ok\_actions) | List of ARNs to put as Cloudwatch OK actions (eg, ARN of SNS topic) | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [use\_random\_name\_prefix](#input\_use\_random\_name\_prefix) | Whether to prefix resource names with random prefix | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_metric\_alarm\_arns](#output\_cloudwatch\_metric\_alarm\_arns) | List of ARNs of the Cloudwatch metric alarm | +| [cloudwatch\_metric\_alarm\_ids](#output\_cloudwatch\_metric\_alarm\_ids) | List of IDs of the Cloudwatch metric alarm | + diff --git a/modules/aws-cloudwatch/modules/cis-alarms/main.tf b/modules/aws-cloudwatch/modules/cis-alarms/main.tf new file mode 100644 index 0000000..21a0720 --- /dev/null +++ b/modules/aws-cloudwatch/modules/cis-alarms/main.tf @@ -0,0 +1,132 @@ +locals { + all_controls = { + UnauthorizedAPICalls = { + pattern = "{ (($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")) && (($.sourceIPAddress!=\"delivery.logs.amazonaws.com\") && ($.eventName!=\"HeadBucket\")) }" + description = "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity." + } + + NoMFAConsoleSignin = { + pattern = "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.userIdentity.type = \"IAMUser\") && ($.responseElements.ConsoleLogin = \"Success\") }" + description = "Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA." + } + + RootUsage = { + pattern = "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }" + description = "Monitoring for root account logins will provide visibility into the use of a fully privileged account and an opportunity to reduce the use of it." + } + + IAMChanges = { + pattern = "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}" + description = "Monitoring changes to IAM policies will help ensure authentication and authorization controls remain intact." + } + + CloudTrailCfgChanges = { + pattern = "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }" + description = "Monitoring changes to CloudTrail's configuration will help ensure sustained visibility to activities performed in the AWS account." + } + + ConsoleSigninFailures = { + pattern = "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }" + description = "Monitoring failed console logins may decrease lead time to detect an attempt to brute force a credential, which may provide an indicator, such as source IP, that can be used in other event correlation." + } + + DisableOrDeleteCMK = { + pattern = "{ ($.eventSource = kms.amazonaws.com) && (($.eventName = DisableKey) || ($.eventName = ScheduleKeyDeletion)) }" + description = "Data encrypted with disabled or deleted keys will no longer be accessible." + } + + S3BucketPolicyChanges = { + pattern = "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }" + description = "Monitoring changes to S3 bucket policies may reduce time to detect and correct permissive policies on sensitive S3 buckets." + } + + AWSConfigChanges = { + pattern = "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }" + description = "Monitoring changes to AWS Config configuration will help ensure sustained visibility of configuration items within the AWS account." + } + + SecurityGroupChanges = { + pattern = "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}" + description = "Monitoring changes to security group will help ensure that resources and services are not unintentionally exposed." + } + + NACLChanges = { + pattern = "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }" + description = "Monitoring changes to NACLs will help ensure that AWS resources and services are not unintentionally exposed." + } + + NetworkGWChanges = { + pattern = "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }" + description = "Monitoring changes to network gateways will help ensure that all ingress/egress traffic traverses the VPC border via a controlled path." + } + + RouteTableChanges = { + pattern = "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }" + description = "Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path." + } + + VPCChanges = { + pattern = "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }" + description = "Monitoring changes to VPC will help ensure VPC traffic flow is not getting impacted." + } + + AWSOrganizationsChanges = { + pattern = "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }" + description = "Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or intentional modifications that may lead to unauthorized access or other security breaches. This monitoring technique helps you to ensure that any unexpected changes performed within your AWS Organizations can be investigated and any unwanted changes can be rolled back." + } + } + + ############### + + prefix = var.use_random_name_prefix ? "${random_pet.this[0].id}-" : var.name_prefix + controls = { for k, v in local.all_controls : k => merge(v, try(var.control_overrides[k], {})) if var.create && !contains(var.disabled_controls, k) } +} + +resource "random_pet" "this" { + count = var.use_random_name_prefix ? 1 : 0 + + length = 2 +} + +resource "aws_cloudwatch_log_metric_filter" "this" { + for_each = local.controls + + name = "${local.prefix}${each.key}" + pattern = each.value["pattern"] + log_group_name = lookup(each.value, "log_group_name", var.log_group_name) + + metric_transformation { + name = "${local.prefix}${each.key}" + namespace = lookup(each.value, "namespace", var.namespace) + value = 1 + default_value = 0 + } +} + +resource "aws_cloudwatch_metric_alarm" "this" { + for_each = local.controls + + metric_name = aws_cloudwatch_log_metric_filter.this[each.key].id + namespace = lookup(each.value, "namespace", var.namespace) + alarm_name = "${local.prefix}${each.key}" + alarm_description = lookup(each.value, "description", null) + + actions_enabled = lookup(each.value, "actions_enabled", var.actions_enabled) + alarm_actions = lookup(each.value, "alarm_actions", var.alarm_actions) + ok_actions = lookup(each.value, "ok_actions", var.ok_actions) + insufficient_data_actions = lookup(each.value, "insufficient_data_actions", var.insufficient_data_actions) + + comparison_operator = lookup(each.value, "comparison_operator", "GreaterThanOrEqualToThreshold") + evaluation_periods = lookup(each.value, "evaluation_periods", 1) + threshold = lookup(each.value, "threshold", 1) + unit = lookup(each.value, "unit", null) + datapoints_to_alarm = lookup(each.value, "datapoints_to_alarm", null) + treat_missing_data = lookup(each.value, "treat_missing_data", "notBreaching") + evaluate_low_sample_count_percentiles = lookup(each.value, "evaluate_low_sample_count_percentiles", null) + + period = lookup(each.value, "period", 300) + statistic = lookup(each.value, "statistic", "Sum") + dimensions = lookup(each.value, "dimensions", null) + + tags = var.tags +} diff --git a/modules/aws-cloudwatch/modules/cis-alarms/outputs.tf b/modules/aws-cloudwatch/modules/cis-alarms/outputs.tf new file mode 100644 index 0000000..db4ee4b --- /dev/null +++ b/modules/aws-cloudwatch/modules/cis-alarms/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_metric_alarm_arns" { + description = "List of ARNs of the Cloudwatch metric alarm" + value = { for k, v in aws_cloudwatch_metric_alarm.this : k => v.arn } +} + +output "cloudwatch_metric_alarm_ids" { + description = "List of IDs of the Cloudwatch metric alarm" + value = { for k, v in aws_cloudwatch_metric_alarm.this : k => v.id } +} diff --git a/modules/aws-cloudwatch/modules/cis-alarms/variables.tf b/modules/aws-cloudwatch/modules/cis-alarms/variables.tf new file mode 100644 index 0000000..87be079 --- /dev/null +++ b/modules/aws-cloudwatch/modules/cis-alarms/variables.tf @@ -0,0 +1,71 @@ +variable "create" { + description = "Whether to create the Cloudwatch log metric filter and metric alarms" + type = bool + default = true +} + +variable "use_random_name_prefix" { + description = "Whether to prefix resource names with random prefix" + type = bool + default = false +} + +variable "name_prefix" { + description = "A name prefix for the cloudwatch alarm (if use_random_name_prefix is true, this will be ignored)" + type = string + default = "" +} + +variable "control_overrides" { + description = "A map of overrides to apply to each control" + default = {} + type = any +} + +variable "disabled_controls" { + description = "List of IDs of disabled CIS controls" + type = list(string) + default = [] +} + +variable "namespace" { + description = "The namespace where metric filter and metric alarm should be cleated" + type = string + default = "CISBenchmark" +} + +variable "log_group_name" { + description = "The name of the log group to associate the metric filter with" + type = string + default = "" +} + +variable "alarm_actions" { + description = "List of ARNs to put as Cloudwatch Alarms actions (eg, ARN of SNS topic)" + type = list(string) + default = [] +} + +variable "actions_enabled" { + description = "Indicates whether or not actions should be executed during any changes to the alarm's state." + type = bool + default = true +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = {} +} + +variable "ok_actions" { + description = "List of ARNs to put as Cloudwatch OK actions (eg, ARN of SNS topic)" + type = list(string) + default = [] +} + +variable "insufficient_data_actions" { + description = "List of ARNs to put as Cloudwatch insuficient data actions (eg, ARN of SNS topic)" + type = list(string) + default = [] +} diff --git a/modules/aws-cloudwatch/modules/cis-alarms/versions.tf b/modules/aws-cloudwatch/modules/cis-alarms/versions.tf new file mode 100644 index 0000000..6834112 --- /dev/null +++ b/modules/aws-cloudwatch/modules/cis-alarms/versions.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/log-group/README.md b/modules/aws-cloudwatch/modules/log-group/README.md new file mode 100644 index 0000000..14ca083 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-group/README.md @@ -0,0 +1,46 @@ +# log-group + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_resource_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_resource_policy) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create the Cloudwatch log group | `bool` | `true` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting logs | `string` | `null` | no | +| [name](#input\_name) | A name for the log group | `string` | `null` | no | +| [name\_prefix](#input\_name\_prefix) | A name prefix for the log group | `string` | `null` | no | +| [retention\_in\_days](#input\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `null` | no | +| [tags](#input\_tags) | A map of tags to add to Cloudwatch log group | `map(string)` | `{}` | no | +| [tags](#policy\_name) | (Required) Name of the resource policy | `string` | `""` | no | +| [tags](#identifiers) | A list of AWS Service identifiers that used to grant permission to write log to Cloudwatch log group. | `list` | `[]` | no | +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of Cloudwatch log group | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of Cloudwatch log group | + diff --git a/modules/aws-cloudwatch/modules/log-group/main.tf b/modules/aws-cloudwatch/modules/log-group/main.tf new file mode 100644 index 0000000..e78b74d --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-group/main.tf @@ -0,0 +1,35 @@ +resource "aws_cloudwatch_log_group" "this" { + count = var.create ? 1 : 0 + + name = var.name + name_prefix = var.name_prefix + retention_in_days = var.retention_in_days + kms_key_id = var.kms_key_id + + tags = var.tags +} + +data "aws_iam_policy_document" "this" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = var.identifiers + } + + actions = [ + "logs:PutLogEvents", + "logs:PutLogEventsBatch", + "logs:CreateLogStream", + ] + + resources = ["arn:aws:logs:*"] + } +} + +resource "aws_cloudwatch_log_resource_policy" "this" { + count = var.create ? 1 : 0 + policy_name = var.policy_name + policy_document = data.aws_iam_policy_document.this.json +} \ No newline at end of file diff --git a/modules/aws-cloudwatch/modules/log-group/outputs.tf b/modules/aws-cloudwatch/modules/log-group/outputs.tf new file mode 100644 index 0000000..49cb7cd --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-group/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_log_group_name" { + description = "Name of Cloudwatch log group" + value = try(aws_cloudwatch_log_group.this[0].name, "") +} + +output "cloudwatch_log_group_arn" { + description = "ARN of Cloudwatch log group" + value = try(aws_cloudwatch_log_group.this[0].arn, "") +} diff --git a/modules/aws-cloudwatch/modules/log-group/variables.tf b/modules/aws-cloudwatch/modules/log-group/variables.tf new file mode 100644 index 0000000..68a9ace --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-group/variables.tf @@ -0,0 +1,52 @@ +variable "create" { + description = "Whether to create the Cloudwatch log group" + type = bool + default = true +} + +variable "name" { + description = "A name for the log group" + type = string + default = null +} + +variable "name_prefix" { + description = "A name prefix for the log group" + type = string + default = null +} + +variable "retention_in_days" { + description = "Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653." + type = number + default = null + + validation { + condition = var.retention_in_days == null ? true : contains([0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653], var.retention_in_days) + error_message = "Must be 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653 or 0 (zero indicates never expire logs)." + } +} + +variable "kms_key_id" { + description = "The ARN of the KMS Key to use when encrypting logs" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to Cloudwatch log group" + type = map(string) + default = {} +} + +variable "policy_name" { + description = "(Required) Name of the resource policy" + type = string + default = "" +} + +variable "identifiers" { + description = "A list of AWS Service identifiers that used to grant permission to write log to Cloudwatch log group." + type = list(any) + default = [] +} diff --git a/modules/aws-cloudwatch/modules/log-group/versions.tf b/modules/aws-cloudwatch/modules/log-group/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/log-metric-filter/README.md b/modules/aws-cloudwatch/modules/log-metric-filter/README.md new file mode 100644 index 0000000..a4294ce --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-metric-filter/README.md @@ -0,0 +1,45 @@ +# log-metric-filter + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_metric_filter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_metric_filter) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create\_cloudwatch\_log\_metric\_filter](#input\_create\_cloudwatch\_log\_metric\_filter) | Whether to create the Cloudwatch log metric filter | `bool` | `true` | no | +| [log\_group\_name](#input\_log\_group\_name) | The name of the log group to associate the metric filter with | `string` | n/a | yes | +| [metric\_transformation\_default\_value](#input\_metric\_transformation\_default\_value) | The value to emit when a filter pattern does not match a log event. | `string` | `null` | no | +| [metric\_transformation\_name](#input\_metric\_transformation\_name) | The name of the CloudWatch metric to which the monitored log information should be published (e.g. ErrorCount) | `string` | n/a | yes | +| [metric\_transformation\_namespace](#input\_metric\_transformation\_namespace) | The destination namespace of the CloudWatch metric. | `string` | n/a | yes | +| [metric\_transformation\_value](#input\_metric\_transformation\_value) | What to publish to the metric. For example, if you're counting the occurrences of a particular term like 'Error', the value will be '1' for each occurrence. If you're counting the bytes transferred the published value will be the value in the log event. | `string` | `"1"` | no | +| [name](#input\_name) | A name for the metric filter. | `string` | n/a | yes | +| [pattern](#input\_pattern) | A valid CloudWatch Logs filter pattern for extracting metric data out of ingested log events. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_metric\_filter\_id](#output\_cloudwatch\_log\_metric\_filter\_id) | The name of the metric filter | + diff --git a/modules/aws-cloudwatch/modules/log-metric-filter/main.tf b/modules/aws-cloudwatch/modules/log-metric-filter/main.tf new file mode 100644 index 0000000..c23f134 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-metric-filter/main.tf @@ -0,0 +1,14 @@ +resource "aws_cloudwatch_log_metric_filter" "this" { + count = var.create_cloudwatch_log_metric_filter ? 1 : 0 + + name = var.name + pattern = var.pattern + log_group_name = var.log_group_name + + metric_transformation { + name = var.metric_transformation_name + namespace = var.metric_transformation_namespace + value = var.metric_transformation_value + default_value = var.metric_transformation_default_value + } +} diff --git a/modules/aws-cloudwatch/modules/log-metric-filter/outputs.tf b/modules/aws-cloudwatch/modules/log-metric-filter/outputs.tf new file mode 100644 index 0000000..34e62e2 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-metric-filter/outputs.tf @@ -0,0 +1,4 @@ +output "cloudwatch_log_metric_filter_id" { + description = "The name of the metric filter" + value = try(aws_cloudwatch_log_metric_filter.this[0].id, "") +} diff --git a/modules/aws-cloudwatch/modules/log-metric-filter/variables.tf b/modules/aws-cloudwatch/modules/log-metric-filter/variables.tf new file mode 100644 index 0000000..bbcc2a9 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-metric-filter/variables.tf @@ -0,0 +1,43 @@ +variable "create_cloudwatch_log_metric_filter" { + description = "Whether to create the Cloudwatch log metric filter" + type = bool + default = true +} + +variable "name" { + description = "A name for the metric filter." + type = string +} + +variable "pattern" { + description = "A valid CloudWatch Logs filter pattern for extracting metric data out of ingested log events." + type = string +} + +variable "log_group_name" { + description = "The name of the log group to associate the metric filter with" + type = string +} + + +variable "metric_transformation_name" { + description = "The name of the CloudWatch metric to which the monitored log information should be published (e.g. ErrorCount)" + type = string +} + +variable "metric_transformation_namespace" { + description = "The destination namespace of the CloudWatch metric." + type = string +} + +variable "metric_transformation_value" { + description = "What to publish to the metric. For example, if you're counting the occurrences of a particular term like 'Error', the value will be '1' for each occurrence. If you're counting the bytes transferred the published value will be the value in the log event." + type = string + default = "1" +} + +variable "metric_transformation_default_value" { + description = "The value to emit when a filter pattern does not match a log event." + type = string + default = null +} diff --git a/modules/aws-cloudwatch/modules/log-metric-filter/versions.tf b/modules/aws-cloudwatch/modules/log-metric-filter/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-metric-filter/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/log-stream/README.md b/modules/aws-cloudwatch/modules/log-stream/README.md new file mode 100644 index 0000000..a7a7791 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-stream/README.md @@ -0,0 +1,41 @@ +# log-stream + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_stream.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_stream) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create the Cloudwatch log stream | `bool` | `true` | no | +| [log\_group\_name](#input\_log\_group\_name) | A name of the log group | `string` | `null` | no | +| [name](#input\_name) | A name for the log stream | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_stream\_arn](#output\_cloudwatch\_log\_stream\_arn) | ARN of Cloudwatch log stream | +| [cloudwatch\_log\_stream\_name](#output\_cloudwatch\_log\_stream\_name) | Name of Cloudwatch log stream | + diff --git a/modules/aws-cloudwatch/modules/log-stream/main.tf b/modules/aws-cloudwatch/modules/log-stream/main.tf new file mode 100644 index 0000000..1624658 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-stream/main.tf @@ -0,0 +1,6 @@ +resource "aws_cloudwatch_log_stream" "this" { + count = var.create ? 1 : 0 + + name = var.name + log_group_name = var.log_group_name +} diff --git a/modules/aws-cloudwatch/modules/log-stream/outputs.tf b/modules/aws-cloudwatch/modules/log-stream/outputs.tf new file mode 100644 index 0000000..1e1ce45 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-stream/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_log_stream_name" { + description = "Name of Cloudwatch log stream" + value = try(aws_cloudwatch_log_stream.this[0].name, "") +} + +output "cloudwatch_log_stream_arn" { + description = "ARN of Cloudwatch log stream" + value = try(aws_cloudwatch_log_stream.this[0].arn, "") +} diff --git a/modules/aws-cloudwatch/modules/log-stream/variables.tf b/modules/aws-cloudwatch/modules/log-stream/variables.tf new file mode 100644 index 0000000..406a9a0 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-stream/variables.tf @@ -0,0 +1,17 @@ +variable "create" { + description = "Whether to create the Cloudwatch log stream" + type = bool + default = true +} + +variable "name" { + description = "A name for the log stream" + type = string + default = null +} + +variable "log_group_name" { + description = "A name of the log group" + type = string + default = null +} diff --git a/modules/aws-cloudwatch/modules/log-stream/versions.tf b/modules/aws-cloudwatch/modules/log-stream/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-stream/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/log-subscription-filter/README.md b/modules/aws-cloudwatch/modules/log-subscription-filter/README.md new file mode 100644 index 0000000..0cbc569 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-subscription-filter/README.md @@ -0,0 +1,35 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_subscription_filter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [role\_arn](#role\_arn) | The ARN of an IAM role that grants Amazon CloudWatch Logs permissions to deliver ingested log events to the destination. If you use Lambda as a destination, you should skip this argument and use aws_lambda_permission resource for granting access from CloudWatch logs to the destination Lambda function. | `string` | `""` | no | +| [filter\_pattern](#filter\_pattern) | A valid CloudWatch Logs filter pattern for subscribing to a filtered stream of log events. | `string` | `""` | yes | +| [destination\_arn](#destination\_arn) | The ARN of the destination to deliver matching log events to. Kinesis stream or Lambda function ARN. | `string` | `true` | yes | +| [log\_group\_names](#log\_group\_names) | The names of the log group to associate the subscription filter with | `list(string)` | `[]` | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [subscription\_filter\_specs](#subscription\_filter\_specs) | Subscription Filter Specifications | + diff --git a/modules/aws-cloudwatch/modules/log-subscription-filter/main.tf b/modules/aws-cloudwatch/modules/log-subscription-filter/main.tf new file mode 100644 index 0000000..4d591f5 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-subscription-filter/main.tf @@ -0,0 +1,8 @@ +resource "aws_cloudwatch_log_subscription_filter" "this" { + for_each = toset(var.log_group_names) + name = "LamdaFuncFilter-${each.key}" + role_arn = var.role_arn + log_group_name = each.value + filter_pattern = var.filter_pattern + destination_arn = var.destination_arn +} \ No newline at end of file diff --git a/modules/aws-cloudwatch/modules/log-subscription-filter/outputs.tf b/modules/aws-cloudwatch/modules/log-subscription-filter/outputs.tf new file mode 100644 index 0000000..d22e5a9 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-subscription-filter/outputs.tf @@ -0,0 +1,4 @@ +output "subscription_filter_specs" { + value = aws_cloudwatch_log_subscription_filter.this[*] + description = "Subscription Filter Specs" +} diff --git a/modules/aws-cloudwatch/modules/log-subscription-filter/variables.tf b/modules/aws-cloudwatch/modules/log-subscription-filter/variables.tf new file mode 100644 index 0000000..ba6242e --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-subscription-filter/variables.tf @@ -0,0 +1,23 @@ +variable "role_arn" { + description = "(Optional) The ARN of an IAM role that grants Amazon CloudWatch Logs permissions to deliver ingested log events to the destination. If you use Lambda as a destination, you should skip this argument and use aws_lambda_permission resource for granting access from CloudWatch logs to the destination Lambda function." + type = string + default = "" +} + +variable "filter_pattern" { + description = "(Required) A valid CloudWatch Logs filter pattern for subscribing to a filtered stream of log events." + type = string + default = "" +} + +variable "destination_arn" { + description = "(Required) The ARN of the destination to deliver matching log events to. Kinesis stream or Lambda function ARN." + type = string + default = "" +} + +variable "log_group_names" { + description = "(Required) The names of the log group to associate the subscription filter with" + type = list(any) + default = [] +} \ No newline at end of file diff --git a/modules/aws-cloudwatch/modules/log-subscription-filter/versions.tf b/modules/aws-cloudwatch/modules/log-subscription-filter/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/log-subscription-filter/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/metric-alarm/README.md b/modules/aws-cloudwatch/modules/metric-alarm/README.md new file mode 100644 index 0000000..613845f --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarm/README.md @@ -0,0 +1,61 @@ +# metric-alarm + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_metric_alarm.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [actions\_enabled](#input\_actions\_enabled) | Indicates whether or not actions should be executed during any changes to the alarm's state. Defaults to true. | `bool` | `true` | no | +| [alarm\_actions](#input\_alarm\_actions) | The list of actions to execute when this alarm transitions into an ALARM state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [alarm\_description](#input\_alarm\_description) | The description for the alarm. | `string` | `null` | no | +| [alarm\_name](#input\_alarm\_name) | The descriptive name for the alarm. This name must be unique within the user's AWS account. | `string` | n/a | yes | +| [comparison\_operator](#input\_comparison\_operator) | The arithmetic operation to use when comparing the specified Statistic and Threshold. The specified Statistic value is used as the first operand. Either of the following is supported: GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold. | `string` | n/a | yes | +| [create\_metric\_alarm](#input\_create\_metric\_alarm) | Whether to create the Cloudwatch metric alarm | `bool` | `true` | no | +| [datapoints\_to\_alarm](#input\_datapoints\_to\_alarm) | The number of datapoints that must be breaching to trigger the alarm. | `number` | `null` | no | +| [dimensions](#input\_dimensions) | The dimensions for the alarm's associated metric. | `any` | `null` | no | +| [evaluate\_low\_sample\_count\_percentiles](#input\_evaluate\_low\_sample\_count\_percentiles) | Used only for alarms based on percentiles. If you specify ignore, the alarm state will not change during periods with too few data points to be statistically significant. If you specify evaluate or omit this parameter, the alarm will always be evaluated and possibly change state no matter how many data points are available. The following values are supported: ignore, and evaluate. | `string` | `null` | no | +| [evaluation\_periods](#input\_evaluation\_periods) | The number of periods over which data is compared to the specified threshold. | `number` | n/a | yes | +| [extended\_statistic](#input\_extended\_statistic) | The percentile statistic for the metric associated with the alarm. Specify a value between p0.0 and p100. | `string` | `null` | no | +| [insufficient\_data\_actions](#input\_insufficient\_data\_actions) | The list of actions to execute when this alarm transitions into an INSUFFICIENT\_DATA state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [metric\_name](#input\_metric\_name) | The name for the alarm's associated metric. See docs for supported metrics. | `string` | `null` | no | +| [metric\_query](#input\_metric\_query) | Enables you to create an alarm based on a metric math expression. You may specify at most 20. | `any` | `[]` | no | +| [namespace](#input\_namespace) | The namespace for the alarm's associated metric. See docs for the list of namespaces. See docs for supported metrics. | `string` | `null` | no | +| [ok\_actions](#input\_ok\_actions) | The list of actions to execute when this alarm transitions into an OK state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [period](#input\_period) | The period in seconds over which the specified statistic is applied. | `string` | `null` | no | +| [statistic](#input\_statistic) | The statistic to apply to the alarm's associated metric. Either of the following is supported: SampleCount, Average, Sum, Minimum, Maximum | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [threshold](#input\_threshold) | The value against which the specified statistic is compared. | `number` | `null` | no | +| [threshold\_metric\_id](#input\_threshold\_metric\_id) | If this is an alarm based on an anomaly detection model, make this value match the ID of the ANOMALY\_DETECTION\_BAND function. | `string` | `null` | no | +| [treat\_missing\_data](#input\_treat\_missing\_data) | Sets how this alarm is to handle missing data points. The following values are supported: missing, ignore, breaching and notBreaching. | `string` | `"missing"` | no | +| [unit](#input\_unit) | The unit for the alarm's associated metric. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_metric\_alarm\_arn](#output\_cloudwatch\_metric\_alarm\_arn) | The ARN of the Cloudwatch metric alarm. | +| [cloudwatch\_metric\_alarm\_id](#output\_cloudwatch\_metric\_alarm\_id) | The ID of the Cloudwatch metric alarm. | + diff --git a/modules/aws-cloudwatch/modules/metric-alarm/main.tf b/modules/aws-cloudwatch/modules/metric-alarm/main.tf new file mode 100644 index 0000000..aaaa52e --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarm/main.tf @@ -0,0 +1,56 @@ +resource "aws_cloudwatch_metric_alarm" "this" { + count = var.create_metric_alarm ? 1 : 0 + + alarm_name = var.alarm_name + alarm_description = var.alarm_description + actions_enabled = var.actions_enabled + + alarm_actions = var.alarm_actions + ok_actions = var.ok_actions + insufficient_data_actions = var.insufficient_data_actions + + comparison_operator = var.comparison_operator + evaluation_periods = var.evaluation_periods + threshold = var.threshold + unit = var.unit + + datapoints_to_alarm = var.datapoints_to_alarm + treat_missing_data = var.treat_missing_data + evaluate_low_sample_count_percentiles = var.evaluate_low_sample_count_percentiles + + # conflicts with metric_query + metric_name = var.metric_name + namespace = var.namespace + period = var.period + statistic = var.statistic + extended_statistic = var.extended_statistic + + dimensions = var.dimensions + + # conflicts with metric_name + dynamic "metric_query" { + for_each = var.metric_query + content { + id = lookup(metric_query.value, "id") + account_id = lookup(metric_query.value, "account_id", null) + label = lookup(metric_query.value, "label", null) + return_data = lookup(metric_query.value, "return_data", null) + expression = lookup(metric_query.value, "expression", null) + + dynamic "metric" { + for_each = lookup(metric_query.value, "metric", []) + content { + metric_name = lookup(metric.value, "metric_name") + namespace = lookup(metric.value, "namespace") + period = lookup(metric.value, "period") + stat = lookup(metric.value, "stat") + unit = lookup(metric.value, "unit", null) + dimensions = lookup(metric.value, "dimensions", null) + } + } + } + } + threshold_metric_id = var.threshold_metric_id + + tags = var.tags +} diff --git a/modules/aws-cloudwatch/modules/metric-alarm/outputs.tf b/modules/aws-cloudwatch/modules/metric-alarm/outputs.tf new file mode 100644 index 0000000..690927b --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarm/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_metric_alarm_arn" { + description = "The ARN of the Cloudwatch metric alarm." + value = try(aws_cloudwatch_metric_alarm.this[0].arn, "") +} + +output "cloudwatch_metric_alarm_id" { + description = "The ID of the Cloudwatch metric alarm." + value = try(aws_cloudwatch_metric_alarm.this[0].id, "") +} diff --git a/modules/aws-cloudwatch/modules/metric-alarm/variables.tf b/modules/aws-cloudwatch/modules/metric-alarm/variables.tf new file mode 100644 index 0000000..6f4b2f9 --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarm/variables.tf @@ -0,0 +1,134 @@ +variable "create_metric_alarm" { + description = "Whether to create the Cloudwatch metric alarm" + type = bool + default = true +} + +variable "alarm_name" { + description = "The descriptive name for the alarm. This name must be unique within the user's AWS account." + type = string +} + +variable "alarm_description" { + description = "The description for the alarm." + type = string + default = null +} + +variable "comparison_operator" { + description = "The arithmetic operation to use when comparing the specified Statistic and Threshold. The specified Statistic value is used as the first operand. Either of the following is supported: GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold." + type = string +} + +variable "evaluation_periods" { + description = "The number of periods over which data is compared to the specified threshold." + type = number +} + +variable "threshold" { + description = "The value against which the specified statistic is compared." + type = number + default = null +} + +variable "threshold_metric_id" { + description = "If this is an alarm based on an anomaly detection model, make this value match the ID of the ANOMALY_DETECTION_BAND function." + type = string + default = null +} + +variable "unit" { + description = "The unit for the alarm's associated metric." + type = string + default = null +} + +variable "metric_name" { + description = "The name for the alarm's associated metric. See docs for supported metrics." + type = string + default = null +} + +variable "namespace" { + description = "The namespace for the alarm's associated metric. See docs for the list of namespaces. See docs for supported metrics." + type = string + default = null +} + +variable "period" { + description = "The period in seconds over which the specified statistic is applied." + type = string + default = null +} + +variable "statistic" { + description = "The statistic to apply to the alarm's associated metric. Either of the following is supported: SampleCount, Average, Sum, Minimum, Maximum" + type = string + default = null +} + +variable "actions_enabled" { + description = "Indicates whether or not actions should be executed during any changes to the alarm's state. Defaults to true." + type = bool + default = true +} + +variable "datapoints_to_alarm" { + description = "The number of datapoints that must be breaching to trigger the alarm." + type = number + default = null +} + +variable "dimensions" { + description = "The dimensions for the alarm's associated metric." + type = any + default = null +} + +variable "alarm_actions" { + description = "The list of actions to execute when this alarm transitions into an ALARM state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "insufficient_data_actions" { + description = "The list of actions to execute when this alarm transitions into an INSUFFICIENT_DATA state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "ok_actions" { + description = "The list of actions to execute when this alarm transitions into an OK state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "extended_statistic" { + description = "The percentile statistic for the metric associated with the alarm. Specify a value between p0.0 and p100." + type = string + default = null +} + +variable "treat_missing_data" { + description = "Sets how this alarm is to handle missing data points. The following values are supported: missing, ignore, breaching and notBreaching." + type = string + default = "missing" +} + +variable "evaluate_low_sample_count_percentiles" { + description = "Used only for alarms based on percentiles. If you specify ignore, the alarm state will not change during periods with too few data points to be statistically significant. If you specify evaluate or omit this parameter, the alarm will always be evaluated and possibly change state no matter how many data points are available. The following values are supported: ignore, and evaluate." + type = string + default = null +} + +variable "metric_query" { + description = "Enables you to create an alarm based on a metric math expression. You may specify at most 20." + type = any + default = [] +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = {} +} diff --git a/modules/aws-cloudwatch/modules/metric-alarm/versions.tf b/modules/aws-cloudwatch/modules/metric-alarm/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/README.md b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/README.md new file mode 100644 index 0000000..da13e3e --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/README.md @@ -0,0 +1,60 @@ +# metric-alarms-by-multiple-dimensions + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_metric_alarm.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [actions\_enabled](#input\_actions\_enabled) | Indicates whether or not actions should be executed during any changes to the alarm's state. Defaults to true. | `bool` | `true` | no | +| [alarm\_actions](#input\_alarm\_actions) | The list of actions to execute when this alarm transitions into an ALARM state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [alarm\_description](#input\_alarm\_description) | The description for the alarm. | `string` | `null` | no | +| [alarm\_name](#input\_alarm\_name) | The descriptive name for the alarm. This name must be unique within the user's AWS account. | `string` | n/a | yes | +| [comparison\_operator](#input\_comparison\_operator) | The arithmetic operation to use when comparing the specified Statistic and Threshold. The specified Statistic value is used as the first operand. Either of the following is supported: GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold. | `string` | n/a | yes | +| [create\_metric\_alarm](#input\_create\_metric\_alarm) | Whether to create the Cloudwatch metric alarm | `bool` | `true` | no | +| [datapoints\_to\_alarm](#input\_datapoints\_to\_alarm) | The number of datapoints that must be breaching to trigger the alarm. | `number` | `null` | no | +| [dimensions](#input\_dimensions) | The dimensions for the alarm's associated metric. | `any` | `{}` | no | +| [evaluate\_low\_sample\_count\_percentiles](#input\_evaluate\_low\_sample\_count\_percentiles) | Used only for alarms based on percentiles. If you specify ignore, the alarm state will not change during periods with too few data points to be statistically significant. If you specify evaluate or omit this parameter, the alarm will always be evaluated and possibly change state no matter how many data points are available. The following values are supported: ignore, and evaluate. | `string` | `null` | no | +| [evaluation\_periods](#input\_evaluation\_periods) | The number of periods over which data is compared to the specified threshold. | `number` | n/a | yes | +| [extended\_statistic](#input\_extended\_statistic) | The percentile statistic for the metric associated with the alarm. Specify a value between p0.0 and p100. | `string` | `null` | no | +| [insufficient\_data\_actions](#input\_insufficient\_data\_actions) | The list of actions to execute when this alarm transitions into an INSUFFICIENT\_DATA state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [metric\_name](#input\_metric\_name) | The name for the alarm's associated metric. See docs for supported metrics. | `string` | `null` | no | +| [metric\_query](#input\_metric\_query) | Enables you to create an alarm based on a metric math expression. You may specify at most 20. | `any` | `[]` | no | +| [namespace](#input\_namespace) | The namespace for the alarm's associated metric. See docs for the list of namespaces. See docs for supported metrics. | `string` | `null` | no | +| [ok\_actions](#input\_ok\_actions) | The list of actions to execute when this alarm transitions into an OK state from any other state. Each action is specified as an Amazon Resource Name (ARN). | `list(string)` | `null` | no | +| [period](#input\_period) | The period in seconds over which the specified statistic is applied. | `string` | `null` | no | +| [statistic](#input\_statistic) | The statistic to apply to the alarm's associated metric. Either of the following is supported: SampleCount, Average, Sum, Minimum, Maximum | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [threshold](#input\_threshold) | The value against which the specified statistic is compared. | `number` | n/a | yes | +| [treat\_missing\_data](#input\_treat\_missing\_data) | Sets how this alarm is to handle missing data points. The following values are supported: missing, ignore, breaching and notBreaching. | `string` | `"missing"` | no | +| [unit](#input\_unit) | The unit for the alarm's associated metric. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_metric\_alarm\_arns](#output\_cloudwatch\_metric\_alarm\_arns) | List of ARNs of the Cloudwatch metric alarm | +| [cloudwatch\_metric\_alarm\_ids](#output\_cloudwatch\_metric\_alarm\_ids) | List of IDs of the Cloudwatch metric alarm | + diff --git a/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/main.tf b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/main.tf new file mode 100644 index 0000000..4041569 --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/main.tf @@ -0,0 +1,54 @@ +resource "aws_cloudwatch_metric_alarm" "this" { + for_each = { for k, v in var.dimensions : k => v if var.create_metric_alarm } + + alarm_name = format("%s%s", var.alarm_name, each.key) + alarm_description = var.alarm_description + actions_enabled = var.actions_enabled + + alarm_actions = var.alarm_actions + ok_actions = var.ok_actions + insufficient_data_actions = var.insufficient_data_actions + + comparison_operator = var.comparison_operator + evaluation_periods = var.evaluation_periods + threshold = var.threshold + unit = var.unit + + datapoints_to_alarm = var.datapoints_to_alarm + treat_missing_data = var.treat_missing_data + evaluate_low_sample_count_percentiles = var.evaluate_low_sample_count_percentiles + + # conflicts with metric_query + metric_name = var.metric_name + namespace = var.namespace + period = var.period + statistic = var.statistic + extended_statistic = var.extended_statistic + + dimensions = each.value + + # conflicts with metric_name + dynamic "metric_query" { + for_each = var.metric_query + content { + id = lookup(metric_query.value, "id") + label = lookup(metric_query.value, "label", null) + return_data = lookup(metric_query.value, "return_data", null) + expression = lookup(metric_query.value, "expression", null) + + dynamic "metric" { + for_each = lookup(metric_query.value, "metric", []) + content { + metric_name = lookup(metric.value, "metric_name") + namespace = lookup(metric.value, "namespace") + period = lookup(metric.value, "period") + stat = lookup(metric.value, "stat") + unit = lookup(metric.value, "unit", null) + dimensions = lookup(metric.value, "dimensions", null) + } + } + } + } + + tags = var.tags +} diff --git a/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/outputs.tf b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/outputs.tf new file mode 100644 index 0000000..db4ee4b --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_metric_alarm_arns" { + description = "List of ARNs of the Cloudwatch metric alarm" + value = { for k, v in aws_cloudwatch_metric_alarm.this : k => v.arn } +} + +output "cloudwatch_metric_alarm_ids" { + description = "List of IDs of the Cloudwatch metric alarm" + value = { for k, v in aws_cloudwatch_metric_alarm.this : k => v.id } +} diff --git a/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/variables.tf b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/variables.tf new file mode 100644 index 0000000..f038797 --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/variables.tf @@ -0,0 +1,127 @@ +variable "create_metric_alarm" { + description = "Whether to create the Cloudwatch metric alarm" + type = bool + default = true +} + +variable "alarm_name" { + description = "The descriptive name for the alarm. This name must be unique within the user's AWS account." + type = string +} + +variable "alarm_description" { + description = "The description for the alarm." + type = string + default = null +} + +variable "comparison_operator" { + description = "The arithmetic operation to use when comparing the specified Statistic and Threshold. The specified Statistic value is used as the first operand. Either of the following is supported: GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold." + type = string +} + +variable "evaluation_periods" { + description = "The number of periods over which data is compared to the specified threshold." + type = number +} + +variable "threshold" { + description = "The value against which the specified statistic is compared." + type = number +} + +variable "unit" { + description = "The unit for the alarm's associated metric." + type = string + default = null +} + +variable "metric_name" { + description = "The name for the alarm's associated metric. See docs for supported metrics." + type = string + default = null +} + +variable "namespace" { + description = "The namespace for the alarm's associated metric. See docs for the list of namespaces. See docs for supported metrics." + type = string + default = null +} + +variable "period" { + description = "The period in seconds over which the specified statistic is applied." + type = string + default = null +} + +variable "statistic" { + description = "The statistic to apply to the alarm's associated metric. Either of the following is supported: SampleCount, Average, Sum, Minimum, Maximum" + type = string + default = null +} + +variable "actions_enabled" { + description = "Indicates whether or not actions should be executed during any changes to the alarm's state. Defaults to true." + type = bool + default = true +} + +variable "datapoints_to_alarm" { + description = "The number of datapoints that must be breaching to trigger the alarm." + type = number + default = null +} + +variable "dimensions" { + description = "The dimensions for the alarm's associated metric." + type = any + default = {} +} + +variable "alarm_actions" { + description = "The list of actions to execute when this alarm transitions into an ALARM state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "insufficient_data_actions" { + description = "The list of actions to execute when this alarm transitions into an INSUFFICIENT_DATA state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "ok_actions" { + description = "The list of actions to execute when this alarm transitions into an OK state from any other state. Each action is specified as an Amazon Resource Name (ARN)." + type = list(string) + default = null +} + +variable "extended_statistic" { + description = "The percentile statistic for the metric associated with the alarm. Specify a value between p0.0 and p100." + type = string + default = null +} + +variable "treat_missing_data" { + description = "Sets how this alarm is to handle missing data points. The following values are supported: missing, ignore, breaching and notBreaching." + type = string + default = "missing" +} + +variable "evaluate_low_sample_count_percentiles" { + description = "Used only for alarms based on percentiles. If you specify ignore, the alarm state will not change during periods with too few data points to be statistically significant. If you specify evaluate or omit this parameter, the alarm will always be evaluated and possibly change state no matter how many data points are available. The following values are supported: ignore, and evaluate." + type = string + default = null +} + +variable "metric_query" { + description = "Enables you to create an alarm based on a metric math expression. You may specify at most 20." + type = any + default = [] +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = {} +} diff --git a/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/versions.tf b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-cloudwatch/modules/metric-alarms-by-multiple-dimensions/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-cloudwatch/wrappers/cis-alarms/README.md b/modules/aws-cloudwatch/wrappers/cis-alarms/README.md new file mode 100644 index 0000000..824f035 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/cis-alarms/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/cis-alarms` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/cis-alarms" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/cis-alarms?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/cis-alarms" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/cis-alarms/main.tf b/modules/aws-cloudwatch/wrappers/cis-alarms/main.tf new file mode 100644 index 0000000..ef9e826 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/cis-alarms/main.tf @@ -0,0 +1,18 @@ +module "wrapper" { + source = "../../modules/cis-alarms" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + use_random_name_prefix = try(each.value.use_random_name_prefix, var.defaults.use_random_name_prefix, false) + name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, "") + control_overrides = try(each.value.control_overrides, var.defaults.control_overrides, {}) + disabled_controls = try(each.value.disabled_controls, var.defaults.disabled_controls, []) + namespace = try(each.value.namespace, var.defaults.namespace, "CISBenchmark") + log_group_name = try(each.value.log_group_name, var.defaults.log_group_name, "") + alarm_actions = try(each.value.alarm_actions, var.defaults.alarm_actions, []) + actions_enabled = try(each.value.actions_enabled, var.defaults.actions_enabled, true) + tags = try(each.value.tags, var.defaults.tags, {}) + ok_actions = try(each.value.ok_actions, var.defaults.ok_actions, []) + insufficient_data_actions = try(each.value.insufficient_data_actions, var.defaults.insufficient_data_actions, []) +} diff --git a/modules/aws-cloudwatch/wrappers/cis-alarms/outputs.tf b/modules/aws-cloudwatch/wrappers/cis-alarms/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/cis-alarms/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/cis-alarms/variables.tf b/modules/aws-cloudwatch/wrappers/cis-alarms/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/cis-alarms/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/cis-alarms/versions.tf b/modules/aws-cloudwatch/wrappers/cis-alarms/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/cis-alarms/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudwatch/wrappers/log-group/README.md b/modules/aws-cloudwatch/wrappers/log-group/README.md new file mode 100644 index 0000000..05a7561 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-group/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/log-group` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/log-group" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/log-group?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/log-group" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/log-group/main.tf b/modules/aws-cloudwatch/wrappers/log-group/main.tf new file mode 100644 index 0000000..1751f98 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-group/main.tf @@ -0,0 +1,12 @@ +module "wrapper" { + source = "../../modules/log-group" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name, null) + name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) + retention_in_days = try(each.value.retention_in_days, var.defaults.retention_in_days, null) + kms_key_id = try(each.value.kms_key_id, var.defaults.kms_key_id, null) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/modules/aws-cloudwatch/wrappers/log-group/outputs.tf b/modules/aws-cloudwatch/wrappers/log-group/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-group/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/log-group/variables.tf b/modules/aws-cloudwatch/wrappers/log-group/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-group/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/log-group/versions.tf b/modules/aws-cloudwatch/wrappers/log-group/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-group/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudwatch/wrappers/log-metric-filter/README.md b/modules/aws-cloudwatch/wrappers/log-metric-filter/README.md new file mode 100644 index 0000000..9a22513 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-metric-filter/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/log-metric-filter` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/log-metric-filter" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/log-metric-filter?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/log-metric-filter" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/log-metric-filter/main.tf b/modules/aws-cloudwatch/wrappers/log-metric-filter/main.tf new file mode 100644 index 0000000..b99fb16 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-metric-filter/main.tf @@ -0,0 +1,14 @@ +module "wrapper" { + source = "../../modules/log-metric-filter" + + for_each = var.items + + create_cloudwatch_log_metric_filter = try(each.value.create_cloudwatch_log_metric_filter, var.defaults.create_cloudwatch_log_metric_filter, true) + name = try(each.value.name, var.defaults.name) + pattern = try(each.value.pattern, var.defaults.pattern) + log_group_name = try(each.value.log_group_name, var.defaults.log_group_name) + metric_transformation_name = try(each.value.metric_transformation_name, var.defaults.metric_transformation_name) + metric_transformation_namespace = try(each.value.metric_transformation_namespace, var.defaults.metric_transformation_namespace) + metric_transformation_value = try(each.value.metric_transformation_value, var.defaults.metric_transformation_value, "1") + metric_transformation_default_value = try(each.value.metric_transformation_default_value, var.defaults.metric_transformation_default_value, null) +} diff --git a/modules/aws-cloudwatch/wrappers/log-metric-filter/outputs.tf b/modules/aws-cloudwatch/wrappers/log-metric-filter/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-metric-filter/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/log-metric-filter/variables.tf b/modules/aws-cloudwatch/wrappers/log-metric-filter/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-metric-filter/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/log-metric-filter/versions.tf b/modules/aws-cloudwatch/wrappers/log-metric-filter/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-metric-filter/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudwatch/wrappers/log-stream/README.md b/modules/aws-cloudwatch/wrappers/log-stream/README.md new file mode 100644 index 0000000..46939a0 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-stream/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/log-stream` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/log-stream" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/log-stream?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/log-stream" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/log-stream/main.tf b/modules/aws-cloudwatch/wrappers/log-stream/main.tf new file mode 100644 index 0000000..9cf1a79 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-stream/main.tf @@ -0,0 +1,9 @@ +module "wrapper" { + source = "../../modules/log-stream" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name, null) + log_group_name = try(each.value.log_group_name, var.defaults.log_group_name, null) +} diff --git a/modules/aws-cloudwatch/wrappers/log-stream/outputs.tf b/modules/aws-cloudwatch/wrappers/log-stream/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-stream/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/log-stream/variables.tf b/modules/aws-cloudwatch/wrappers/log-stream/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-stream/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/log-stream/versions.tf b/modules/aws-cloudwatch/wrappers/log-stream/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/log-stream/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarm/README.md b/modules/aws-cloudwatch/wrappers/metric-alarm/README.md new file mode 100644 index 0000000..b4d9a13 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarm/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/metric-alarm` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/metric-alarm" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/metric-alarm?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/metric-alarm" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/metric-alarm/main.tf b/modules/aws-cloudwatch/wrappers/metric-alarm/main.tf new file mode 100644 index 0000000..8b5253d --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarm/main.tf @@ -0,0 +1,29 @@ +module "wrapper" { + source = "../../modules/metric-alarm" + + for_each = var.items + + create_metric_alarm = try(each.value.create_metric_alarm, var.defaults.create_metric_alarm, true) + alarm_name = try(each.value.alarm_name, var.defaults.alarm_name) + alarm_description = try(each.value.alarm_description, var.defaults.alarm_description, null) + comparison_operator = try(each.value.comparison_operator, var.defaults.comparison_operator) + evaluation_periods = try(each.value.evaluation_periods, var.defaults.evaluation_periods) + threshold = try(each.value.threshold, var.defaults.threshold, null) + threshold_metric_id = try(each.value.threshold_metric_id, var.defaults.threshold_metric_id, null) + unit = try(each.value.unit, var.defaults.unit, null) + metric_name = try(each.value.metric_name, var.defaults.metric_name, null) + namespace = try(each.value.namespace, var.defaults.namespace, null) + period = try(each.value.period, var.defaults.period, null) + statistic = try(each.value.statistic, var.defaults.statistic, null) + actions_enabled = try(each.value.actions_enabled, var.defaults.actions_enabled, true) + datapoints_to_alarm = try(each.value.datapoints_to_alarm, var.defaults.datapoints_to_alarm, null) + dimensions = try(each.value.dimensions, var.defaults.dimensions, null) + alarm_actions = try(each.value.alarm_actions, var.defaults.alarm_actions, null) + insufficient_data_actions = try(each.value.insufficient_data_actions, var.defaults.insufficient_data_actions, null) + ok_actions = try(each.value.ok_actions, var.defaults.ok_actions, null) + extended_statistic = try(each.value.extended_statistic, var.defaults.extended_statistic, null) + treat_missing_data = try(each.value.treat_missing_data, var.defaults.treat_missing_data, "missing") + evaluate_low_sample_count_percentiles = try(each.value.evaluate_low_sample_count_percentiles, var.defaults.evaluate_low_sample_count_percentiles, null) + metric_query = try(each.value.metric_query, var.defaults.metric_query, []) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarm/outputs.tf b/modules/aws-cloudwatch/wrappers/metric-alarm/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarm/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarm/variables.tf b/modules/aws-cloudwatch/wrappers/metric-alarm/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarm/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarm/versions.tf b/modules/aws-cloudwatch/wrappers/metric-alarm/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarm/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/README.md b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/README.md new file mode 100644 index 0000000..3c48382 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/metric-alarms-by-multiple-dimensions` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/metric-alarms-by-multiple-dimensions" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/metric-alarms-by-multiple-dimensions?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/metric-alarms-by-multiple-dimensions" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/main.tf b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/main.tf new file mode 100644 index 0000000..c064f60 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/main.tf @@ -0,0 +1,28 @@ +module "wrapper" { + source = "../../modules/metric-alarms-by-multiple-dimensions" + + for_each = var.items + + create_metric_alarm = try(each.value.create_metric_alarm, var.defaults.create_metric_alarm, true) + alarm_name = try(each.value.alarm_name, var.defaults.alarm_name) + alarm_description = try(each.value.alarm_description, var.defaults.alarm_description, null) + comparison_operator = try(each.value.comparison_operator, var.defaults.comparison_operator) + evaluation_periods = try(each.value.evaluation_periods, var.defaults.evaluation_periods) + threshold = try(each.value.threshold, var.defaults.threshold) + unit = try(each.value.unit, var.defaults.unit, null) + metric_name = try(each.value.metric_name, var.defaults.metric_name, null) + namespace = try(each.value.namespace, var.defaults.namespace, null) + period = try(each.value.period, var.defaults.period, null) + statistic = try(each.value.statistic, var.defaults.statistic, null) + actions_enabled = try(each.value.actions_enabled, var.defaults.actions_enabled, true) + datapoints_to_alarm = try(each.value.datapoints_to_alarm, var.defaults.datapoints_to_alarm, null) + dimensions = try(each.value.dimensions, var.defaults.dimensions, {}) + alarm_actions = try(each.value.alarm_actions, var.defaults.alarm_actions, null) + insufficient_data_actions = try(each.value.insufficient_data_actions, var.defaults.insufficient_data_actions, null) + ok_actions = try(each.value.ok_actions, var.defaults.ok_actions, null) + extended_statistic = try(each.value.extended_statistic, var.defaults.extended_statistic, null) + treat_missing_data = try(each.value.treat_missing_data, var.defaults.treat_missing_data, "missing") + evaluate_low_sample_count_percentiles = try(each.value.evaluate_low_sample_count_percentiles, var.defaults.evaluate_low_sample_count_percentiles, null) + metric_query = try(each.value.metric_query, var.defaults.metric_query, []) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/outputs.tf b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/variables.tf b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/versions.tf b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-cloudwatch/wrappers/metric-alarms-by-multiple-dimensions/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-cognito-user-pool/CHANGELOG.md b/modules/aws-cognito-user-pool/CHANGELOG.md new file mode 100644 index 0000000..a7be72d --- /dev/null +++ b/modules/aws-cognito-user-pool/CHANGELOG.md @@ -0,0 +1,313 @@ +## 0.24.0 (November 13, 2023) + +FIXES: + +* Make sure `attribute_constraints` are created for string and number schemas (thanks @@mhorbul) + +## 0.23.0 (July 19, 2023) + +ENHANCEMENTS: + +* Add Cognito User Pool name as output (thanks @SlavaNL) + +## 0.22.0 (March 31, 2023) + +FIXES: + +* Fix user attribute update settings lookup (thanks @trahim) + +## 0.21.0 (January 6, 2023) + +ENHANCEMENTS: + +* Add support for `auth_session_validity` parameter to user pool client (thanks @xposix) + +## 0.20.1 (November 24, 2022) + +FIXES: + +* Updated AWS provider minimum version to v4.38 as a requirement for `deletion_protection` (thanks @gchristidis) + +## 0.20.0 (September 22, 2022) + +ENHANCEMENTS: + +* Add support for user pool deletion_protection (thanks @dmcgillen) +* Set `identity_providers` variable to `sensitive` (thanks @LawrenceWarren) + +FIXES: + +* Remove duplicate `require_lowercasei` key for password policies (thanks @jeromegamez) + +## 0.19.0 (September 22, 2022) + +FIXES: + +* Fix the attributes constraints for number and string schemas (thanks @sgtoj) + +## 0.18.2 (August 12, 2022) + +FIXES: + +* Fix ´username_configuration´ typo in README (thanks @ajoga and @KelvinVenancio) + +## 0.18.1 (August 12, 2022) + +ENHANCEMENTS: + +* Add 0.13.7 as the lowest Terraform version supported (thanks @oleksiidv) + +## 0.18.0 (July 25, 2022) + +ENHANCEMENTS: + +* Add missing option to allow client token revocation (thanks @rastakajakwanna) + +## 0.17.0 (June 3, 2022) + +ENHANCEMENTS: + +* Add `configuration_set` field in `email_configuration` block (thanks @tiagoposse) + +## 0.16.0 (May 30, 2022) + +ENHANCEMENTS: + +* Update complete example +* Update .pre-commit yaml file + +FIXES: + +* Fix `lambda_config` keeps changing + +## 0.15.2 (February 19, 2022) + +FIXES: + +* Change aws provider version constraints to be able to use 4.x + +## 0.15.1 (February 15, 2022) + +FIXES: + +* Change default value for `client_prevent_user_existence_errors` (thanks @juan-acevedo-ntt) + +## 0.15.0 (February 12, 2022) + +ENHANCEMENTS: + +* Add custom sms and email sender support (thanks @xposix) + +## 0.14.2 (September 20, 2021) + +FIXES: + +* Add identity provider as a dependency for `aws_cognito_user_pool_client` (thanks @xposix) + +## 0.14.1 (August 25, 2021) + +FIXES: + +* Use accepted token validity periods (thanks @bobdoah) + +## 0.14.0 (August 19, 2021) + +ENHANCEMENTS: + +* Add map output for client and secret ids (thanks @dmytro-dorofeiev) + +## 0.13.0 (August 10, 2021) + +ENHANCEMENTS: + +* Add support for Cognito Identity Providers (thanks @bobdoah) + +## 0.12.0 (July 4, 2021) + +FIXES: + +* Set client_secrets output to be sensitive (thanks @sapei) + +## 0.11.1 (May 21, 2021) + +FIXES: + +* Revert prevent_destroy due to [Variables may not be used here issue](https://github.com/hashicorp/terraform/issues/22544) + +## 0.11.0 (May 21, 2021) + +ENHANCEMENTS: + +* Add support to prevent the user pool from being destroyed (thanks @Waschnick) + +## 0.10.5 (May 12, 2021) + +FIXES: + +* Fix incorrect example with `access_token_validity` (thanks @tsimbalar) + +## 0.10.4 (April 25, 2021) + +FIXES: + +* Add `depends_on` servers in `aws_cognito_user_pool_client.client` resource + +## 0.10.3 (April 15, 2021) + +FIXES: + +* Make code formatting works with Terraform >= 0.14 (Thanks @stevie-) + +## 0.10.2 (April 10, 2021) + +FIXES: + +* Remove lifecycle for schema addition ([issue](https://github.com/hashicorp/terraform-provider-aws/pull/18512) fixed in the AWS provider) + +## 0.10.1 (April 10, 2021) + +FIXES: + +* Update complete example + +## 0.10.0 (April 10, 2021) + +ENHANCEMENTS: + +* Add support for `access_token_validity`, `id_token_validity` and `token_validity_units` +* Update complete example with `access_token_validity`, `id_token_validity` and `token_validity_units` + +## 0.9.4 (February 14, 2021) + +FIXES: + +* Update README to include schema changes know issue + +## 0.9.3 (January 27, 2021) + +ENHANCEMENTS: + +* Update description for `enabled` variable + + +## 0.9.2 (January 27, 2021) + +ENHANCEMENTS: + +* Update conditional creation example + +## 0.9.1 (January 27, 2021) + +FIXES: + +* Set default value for enable variable to `true` + +## 0.9.0 (January 24, 2021) + +ENHANCEMENTS: + +* Support conditional creation (thanks @Necromancerx) + +## 0.8.0 (December 28, 2020) + +ENHANCEMENTS: + +* Add support for support `account_recovery_setting` + +## 0.7.1 (December 11, 2020) + +FIXES: + +* Ignore schema changes and prevent pool destruction + +## 0.7.0 (November 25, 2020) + +ENHANCEMENTS: + +* Add `from_email_address` + +## 0.6.2 (August 13, 2020) + +FIXES: + +* Update CHANGELOG + +## 0.6.1 (August 13, 2020) + +ENHANCEMENTS: + +* Change source in examples to use Terraform format + +FIXES: + +* Add `username_configuration` dynamic block to avoid forcing a new resource when importing a user pool +* Remove `case_sensitive` variable. Use the `username_configuration` map variable to define the `case_sensitive` attribute + +UPDATES: + +* Updated README and examples + +## 0.5.0 (July 31, 2020) + +FIXES: + +* Depcreate support to `unused_account_validity_days` +* Update README and examples removing any reference to the deprecated `unused_account_validity_days` field + +## 0.4.0 (May 2, 2020) + +ENHANCEMENTS: + +* Add support for `software_token_mfa_configuration` + +## 0.3.3 (April 24, 2020) + +FIXES: + +* Applies `case_sensitive` via `username_configuration` + +## 0.3.2 (April 24, 2020) + +UPDATE: + +* Update README with `case_sensitive` + +## 0.3.1 (April 24, 2020) + +ENHANCEMENTS: + +* Add `case_sensitive` for `aws_cognito_user_pool` + +## 0.3.0 (April 1, 2020) + +ENHANCEMENTS: + +* Add `param client_prevent_user_existence_errors` for client + +UPDATES: + +* Add Terraform logo in README + +## 0.2.2 (March 16, 2020) + +FIXES: + +* Fix typo in comments + +## 0.2.1 (February 6, 2020) + +BUG FIXES: + +* Cognito unused_account_validity_days bug with 2.47: The aws-provider reports the existence of the `unused_account_validity_days` even if it was never declared, automatically matching the new `temporary_password_validity_day` + +## 0.2.0 (February 5, 2020) + +UPDATES: + +* AWS Provider 2.47.0: Deprecate unused_account_validity_days argument and add support for temporary_password_validity_days argument + +## 0.1.0 (November 23, 2019) + +FEATURES: + + * Module implementation diff --git a/modules/aws-cognito-user-pool/LICENSE b/modules/aws-cognito-user-pool/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/modules/aws-cognito-user-pool/LICENSE @@ -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 [yyyy] [name of copyright owner] + + 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. diff --git a/modules/aws-cognito-user-pool/README.md b/modules/aws-cognito-user-pool/README.md new file mode 100644 index 0000000..6122cb0 --- /dev/null +++ b/modules/aws-cognito-user-pool/README.md @@ -0,0 +1,294 @@ +# terraform-aws-cognito-user-pool + +Terraform module to create [Amazon Cognito User Pools](https://aws.amazon.com/cognito/), configure its attributes and resources such as **app clients**, **domain**, **resource servers**. Amazon Cognito User Pools provide a secure user directory that scales to hundreds of millions of users. As a fully managed service, User Pools are easy to set up without any worries about standing up server infrastructure. + +## Usage + +You can use this module to create a Cognito User Pool using the default values or use the detailed definition to set every aspect of the Cognito User Pool + +Check the [examples](examples/) where you can see the **simple** example using the default values, the **simple_extended** version which adds  **app clients**, **domain**, **resource servers** resources, or the **complete** version with a detailed example. + +### Example (simple) + +This simple example creates a AWS Cognito User Pool with the default values: + +``` +module "aws_cognito_user_pool_simple" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "mypool" + + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +``` + +### Example (conditional creation) + +If you need to create Cognito User Pool resources conditionally in ealierform versions such as 0.11, 0,12 and 0.13 you can set the input variable `enabled` to false: + +``` +# This Cognito User Pool will not be created +module "aws_cognito_user_pool_conditional_creation" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "conditional_user_pool" + + enabled = false + + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } + +} +``` + +For Terraform 0.14 and later you can use `count` inside `module` blocks, or use the input variable `enabled` as described above. + +### Example (complete) + +This more complete example creates a AWS Cognito User Pool using a detailed configuration. Please check the example folder to get the example with all options: + +``` +module "aws_cognito_user_pool_complete" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "mypool" + alias_attributes = ["email", "phone_number"] + auto_verified_attributes = ["email"] + + deletion_protection = "ACTIVE" + + admin_create_user_config = { + email_subject = "Here, your verification code baby" + } + + email_configuration = { + email_sending_account = "DEVELOPER" + reply_to_email_address = "email@example.com" + source_arn = "arn:aws:ses:us-east-1:888888888888:identity/example.com" + } + + password_policy = { + minimum_length = 10 + require_lowercase = false + require_numbers = true + require_symbols = true + require_uppercase = true + } + + schemas = [ + { + attribute_data_type = "Boolean" + developer_only_attribute = false + mutable = true + name = "available" + required = false + }, + { + attribute_data_type = "Boolean" + developer_only_attribute = true + mutable = true + name = "registered" + required = false + } + ] + + string_schemas = [ + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + } + ] + + recovery_mechanisms = [ + { + name = "verified_email" + priority = 1 + }, + { + name = "verified_phone_number" + priority = 2 + } + ] + + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } + +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= v0.13.7 | +| [aws](#requirement\_aws) | >= 4.38 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.8.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cognito_identity_provider.identity_provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_identity_provider) | resource | +| [aws_cognito_resource_server.resource](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_resource_server) | resource | +| [aws_cognito_user_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_group) | resource | +| [aws_cognito_user_pool.pool](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool) | resource | +| [aws_cognito_user_pool_client.client](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_client) | resource | +| [aws_cognito_user_pool_domain.domain](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_domain) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_create\_user\_config](#input\_admin\_create\_user\_config) | The configuration for AdminCreateUser requests | `map(any)` | `{}` | no | +| [admin\_create\_user\_config\_allow\_admin\_create\_user\_only](#input\_admin\_create\_user\_config\_allow\_admin\_create\_user\_only) | Set to True if only the administrator is allowed to create user profiles. Set to False if users can sign themselves up via an app | `bool` | `true` | no | +| [admin\_create\_user\_config\_email\_message](#input\_admin\_create\_user\_config\_email\_message) | The message template for email messages. Must contain `{username}` and `{####}` placeholders, for username and temporary password, respectively | `string` | `"{username}, your verification code is `{####}`"` | no | +| [admin\_create\_user\_config\_email\_subject](#input\_admin\_create\_user\_config\_email\_subject) | The subject line for email messages | `string` | `"Your verification code"` | no | +| [admin\_create\_user\_config\_sms\_message](#input\_admin\_create\_user\_config\_sms\_message) | - The message template for SMS messages. Must contain `{username}` and `{####}` placeholders, for username and temporary password, respectively | `string` | `"Your username is {username} and temporary password is `{####}`"` | no | +| [alias\_attributes](#input\_alias\_attributes) | Attributes supported as an alias for this user pool. Possible values: phone\_number, email, or preferred\_username. Conflicts with `username_attributes` | `list(string)` | `null` | no | +| [auto\_verified\_attributes](#input\_auto\_verified\_attributes) | The attributes to be auto-verified. Possible values: email, phone\_number | `list(string)` | `[]` | no | +| [client\_access\_token\_validity](#input\_client\_access\_token\_validity) | Time limit, between 5 minutes and 1 day, after which the access token is no longer valid and cannot be used. This value will be overridden if you have entered a value in `token_validity_units`. | `number` | `60` | no | +| [client\_allowed\_oauth\_flows](#input\_client\_allowed\_oauth\_flows) | The name of the application client | `list(string)` | `[]` | no | +| [client\_allowed\_oauth\_flows\_user\_pool\_client](#input\_client\_allowed\_oauth\_flows\_user\_pool\_client) | Whether the client is allowed to follow the OAuth protocol when interacting with Cognito user pools | `bool` | `true` | no | +| [client\_allowed\_oauth\_scopes](#input\_client\_allowed\_oauth\_scopes) | List of allowed OAuth scopes (phone, email, openid, profile, and aws.cognito.signin.user.admin) | `list(string)` | `[]` | no | +| [client\_auth\_session\_validity](#input\_client\_auth\_session\_validity) | Amazon Cognito creates a session token for each API request in an authentication flow. AuthSessionValidity is the duration, in minutes, of that session token. Your user pool native user must respond to each authentication challenge before the session expires. Valid values between 3 and 15. Default value is 3. | `number` | `3` | no | +| [client\_callback\_urls](#input\_client\_callback\_urls) | List of allowed callback URLs for the identity providers | `list(string)` | `[]` | no | +| [client\_default\_redirect\_uri](#input\_client\_default\_redirect\_uri) | The default redirect URI. Must be in the list of callback URLs | `string` | `""` | no | +| [client\_enable\_token\_revocation](#input\_client\_enable\_token\_revocation) | Whether the client token can be revoked | `bool` | `true` | no | +| [client\_explicit\_auth\_flows](#input\_client\_explicit\_auth\_flows) | List of authentication flows (ADMIN\_NO\_SRP\_AUTH, CUSTOM\_AUTH\_FLOW\_ONLY, USER\_PASSWORD\_AUTH) | `list(string)` | `[]` | no | +| [client\_generate\_secret](#input\_client\_generate\_secret) | Should an application secret be generated | `bool` | `true` | no | +| [client\_id\_token\_validity](#input\_client\_id\_token\_validity) | Time limit, between 5 minutes and 1 day, after which the ID token is no longer valid and cannot be used. Must be between 5 minutes and 1 day. Cannot be greater than refresh token expiration. This value will be overridden if you have entered a value in `token_validity_units`. | `number` | `60` | no | +| [client\_logout\_urls](#input\_client\_logout\_urls) | List of allowed logout URLs for the identity providers | `list(string)` | `[]` | no | +| [client\_name](#input\_client\_name) | The name of the application client | `string` | `null` | no | +| [client\_prevent\_user\_existence\_errors](#input\_client\_prevent\_user\_existence\_errors) | Choose which errors and responses are returned by Cognito APIs during authentication, account confirmation, and password recovery when the user does not exist in the user pool. When set to ENABLED and the user does not exist, authentication returns an error indicating either the username or password was incorrect, and account confirmation and password recovery return a response indicating a code was sent to a simulated destination. When set to LEGACY, those APIs will return a UserNotFoundException exception if the user does not exist in the user pool. | `string` | `null` | no | +| [client\_read\_attributes](#input\_client\_read\_attributes) | List of user pool attributes the application client can read from | `list(string)` | `[]` | no | +| [client\_refresh\_token\_validity](#input\_client\_refresh\_token\_validity) | The time limit in days refresh tokens are valid for. Must be between 60 minutes and 3650 days. This value will be overridden if you have entered a value in `token_validity_units` | `number` | `30` | no | +| [client\_supported\_identity\_providers](#input\_client\_supported\_identity\_providers) | List of provider names for the identity providers that are supported on this client | `list(string)` | `[]` | no | +| [client\_token\_validity\_units](#input\_client\_token\_validity\_units) | Configuration block for units in which the validity times are represented in. Valid values for the following arguments are: `seconds`, `minutes`, `hours` or `days`. | `any` |
{
"access_token": "minutes",
"id_token": "minutes",
"refresh_token": "days"
}
| no | +| [client\_write\_attributes](#input\_client\_write\_attributes) | List of user pool attributes the application client can write to | `list(string)` | `[]` | no | +| [clients](#input\_clients) | A container with the clients definitions | `any` | `[]` | no | +| [deletion\_protection](#input\_deletion\_protection) | When active, DeletionProtection prevents accidental deletion of your user pool. Before you can delete a user pool that you have protected against deletion, you must deactivate this feature. Valid values are `ACTIVE` and `INACTIVE`. | `string` | `"INACTIVE"` | no | +| [device\_configuration](#input\_device\_configuration) | The configuration for the user pool's device tracking | `map(any)` | `{}` | no | +| [device\_configuration\_challenge\_required\_on\_new\_device](#input\_device\_configuration\_challenge\_required\_on\_new\_device) | Indicates whether a challenge is required on a new device. Only applicable to a new device | `bool` | `false` | no | +| [device\_configuration\_device\_only\_remembered\_on\_user\_prompt](#input\_device\_configuration\_device\_only\_remembered\_on\_user\_prompt) | If true, a device is only remembered on user prompt | `bool` | `false` | no | +| [domain](#input\_domain) | Cognito User Pool domain | `string` | `null` | no | +| [domain\_certificate\_arn](#input\_domain\_certificate\_arn) | The ARN of an ISSUED ACM certificate in us-east-1 for a custom domain | `string` | `null` | no | +| [email\_configuration](#input\_email\_configuration) | The Email Configuration | `map(any)` | `{}` | no | +| [email\_configuration\_configuration\_set](#input\_email\_configuration\_configuration\_set) | The name of the configuration set | `string` | `null` | no | +| [email\_configuration\_email\_sending\_account](#input\_email\_configuration\_email\_sending\_account) | Instruct Cognito to either use its built-in functional or Amazon SES to send out emails. Allowed values: `COGNITO_DEFAULT` or `DEVELOPER` | `string` | `"COGNITO_DEFAULT"` | no | +| [email\_configuration\_from\_email\_address](#input\_email\_configuration\_from\_email\_address) | Sender’s email address or sender’s display name with their email address (e.g. `john@example.com`, `John Smith ` or `"John Smith Ph.D." )`. Escaped double quotes are required around display names that contain certain characters as specified in RFC 5322 | `string` | `null` | no | +| [email\_configuration\_reply\_to\_email\_address](#input\_email\_configuration\_reply\_to\_email\_address) | The REPLY-TO email address | `string` | `""` | no | +| [email\_configuration\_source\_arn](#input\_email\_configuration\_source\_arn) | The ARN of the email source | `string` | `""` | no | +| [email\_verification\_message](#input\_email\_verification\_message) | A string representing the email verification message | `string` | `null` | no | +| [email\_verification\_subject](#input\_email\_verification\_subject) | A string representing the email verification subject | `string` | `null` | no | +| [enabled](#input\_enabled) | Change to false to avoid deploying any resources | `bool` | `true` | no | +| [identity\_providers](#input\_identity\_providers) | Cognito Pool Identity Providers | `list(any)` | `[]` | no | +| [lambda\_config](#input\_lambda\_config) | A container for the AWS Lambda triggers associated with the user pool | `any` | `{}` | no | +| [lambda\_config\_create\_auth\_challenge](#input\_lambda\_config\_create\_auth\_challenge) | The ARN of the lambda creating an authentication challenge. | `string` | `null` | no | +| [lambda\_config\_custom\_email\_sender](#input\_lambda\_config\_custom\_email\_sender) | A custom email sender AWS Lambda trigger. | `any` | `{}` | no | +| [lambda\_config\_custom\_message](#input\_lambda\_config\_custom\_message) | A custom Message AWS Lambda trigger. | `string` | `null` | no | +| [lambda\_config\_custom\_sms\_sender](#input\_lambda\_config\_custom\_sms\_sender) | A custom SMS sender AWS Lambda trigger. | `any` | `{}` | no | +| [lambda\_config\_define\_auth\_challenge](#input\_lambda\_config\_define\_auth\_challenge) | Defines the authentication challenge. | `string` | `null` | no | +| [lambda\_config\_kms\_key\_id](#input\_lambda\_config\_kms\_key\_id) | The Amazon Resource Name of Key Management Service Customer master keys. Amazon Cognito uses the key to encrypt codes and temporary passwords sent to CustomEmailSender and CustomSMSSender. | `string` | `null` | no | +| [lambda\_config\_post\_authentication](#input\_lambda\_config\_post\_authentication) | A post-authentication AWS Lambda trigger | `string` | `null` | no | +| [lambda\_config\_post\_confirmation](#input\_lambda\_config\_post\_confirmation) | A post-confirmation AWS Lambda trigger | `string` | `null` | no | +| [lambda\_config\_pre\_authentication](#input\_lambda\_config\_pre\_authentication) | A pre-authentication AWS Lambda trigger | `string` | `null` | no | +| [lambda\_config\_pre\_sign\_up](#input\_lambda\_config\_pre\_sign\_up) | A pre-registration AWS Lambda trigger | `string` | `null` | no | +| [lambda\_config\_pre\_token\_generation](#input\_lambda\_config\_pre\_token\_generation) | Allow to customize identity token claims before token generation | `string` | `null` | no | +| [lambda\_config\_user\_migration](#input\_lambda\_config\_user\_migration) | The user migration Lambda config type | `string` | `null` | no | +| [lambda\_config\_verify\_auth\_challenge\_response](#input\_lambda\_config\_verify\_auth\_challenge\_response) | Verifies the authentication challenge response | `string` | `null` | no | +| [mfa\_configuration](#input\_mfa\_configuration) | Set to enable multi-factor authentication. Must be one of the following values (ON, OFF, OPTIONAL) | `string` | `"OFF"` | no | +| [number\_schemas](#input\_number\_schemas) | A container with the number schema attributes of a user pool. Maximum of 50 attributes | `list(any)` | `[]` | no | +| [password\_policy](#input\_password\_policy) | A container for information about the user pool password policy |
object({
minimum_length = number,
require_lowercase = bool,
require_numbers = bool,
require_symbols = bool,
require_uppercase = bool,
temporary_password_validity_days = number
})
| `null` | no | +| [password\_policy\_minimum\_length](#input\_password\_policy\_minimum\_length) | The minimum length of the password policy that you have set | `number` | `8` | no | +| [password\_policy\_require\_lowercase](#input\_password\_policy\_require\_lowercase) | Whether you have required users to use at least one lowercase letter in their password | `bool` | `true` | no | +| [password\_policy\_require\_numbers](#input\_password\_policy\_require\_numbers) | Whether you have required users to use at least one number in their password | `bool` | `true` | no | +| [password\_policy\_require\_symbols](#input\_password\_policy\_require\_symbols) | Whether you have required users to use at least one symbol in their password | `bool` | `true` | no | +| [password\_policy\_require\_uppercase](#input\_password\_policy\_require\_uppercase) | Whether you have required users to use at least one uppercase letter in their password | `bool` | `true` | no | +| [password\_policy\_temporary\_password\_validity\_days](#input\_password\_policy\_temporary\_password\_validity\_days) | The minimum length of the password policy that you have set | `number` | `7` | no | +| [recovery\_mechanisms](#input\_recovery\_mechanisms) | The list of Account Recovery Options | `list(any)` | `[]` | no | +| [resource\_server\_identifier](#input\_resource\_server\_identifier) | An identifier for the resource server | `string` | `null` | no | +| [resource\_server\_name](#input\_resource\_server\_name) | A name for the resource server | `string` | `null` | no | +| [resource\_server\_scope\_description](#input\_resource\_server\_scope\_description) | The scope description | `string` | `null` | no | +| [resource\_server\_scope\_name](#input\_resource\_server\_scope\_name) | The scope name | `string` | `null` | no | +| [resource\_servers](#input\_resource\_servers) | A container with the user\_groups definitions | `list(any)` | `[]` | no | +| [schemas](#input\_schemas) | A container with the schema attributes of a user pool. Maximum of 50 attributes | `list(any)` | `[]` | no | +| [sms\_authentication\_message](#input\_sms\_authentication\_message) | A string representing the SMS authentication message | `string` | `null` | no | +| [sms\_configuration](#input\_sms\_configuration) | The SMS Configuration | `map(any)` | `{}` | no | +| [sms\_configuration\_external\_id](#input\_sms\_configuration\_external\_id) | The external ID used in IAM role trust relationships | `string` | `""` | no | +| [sms\_configuration\_sns\_caller\_arn](#input\_sms\_configuration\_sns\_caller\_arn) | The ARN of the Amazon SNS caller. This is usually the IAM role that you've given Cognito permission to assume | `string` | `""` | no | +| [sms\_verification\_message](#input\_sms\_verification\_message) | A string representing the SMS verification message | `string` | `null` | no | +| [software\_token\_mfa\_configuration](#input\_software\_token\_mfa\_configuration) | Configuration block for software token MFA (multifactor-auth). mfa\_configuration must also be enabled for this to work | `map(any)` | `{}` | no | +| [software\_token\_mfa\_configuration\_enabled](#input\_software\_token\_mfa\_configuration\_enabled) | If true, and if mfa\_configuration is also enabled, multi-factor authentication by software TOTP generator will be enabled | `bool` | `false` | no | +| [string\_schemas](#input\_string\_schemas) | A container with the string schema attributes of a user pool. Maximum of 50 attributes | `list(any)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the User Pool | `map(string)` | `{}` | no | +| [temporary\_password\_validity\_days](#input\_temporary\_password\_validity\_days) | The user account expiration limit, in days, after which the account is no longer usable | `number` | `7` | no | +| [user\_attribute\_update\_settings](#input\_user\_attribute\_update\_settings) | Configuration block for user attribute update settings. Must contain key `attributes_require_verification_before_update` with list with only valid values of `email` and `phone_number` | `map(list(string))` | `null` | no | +| [user\_group\_description](#input\_user\_group\_description) | The description of the user group | `string` | `null` | no | +| [user\_group\_name](#input\_user\_group\_name) | The name of the user group | `string` | `null` | no | +| [user\_group\_precedence](#input\_user\_group\_precedence) | The precedence of the user group | `number` | `null` | no | +| [user\_group\_role\_arn](#input\_user\_group\_role\_arn) | The ARN of the IAM role to be associated with the user group | `string` | `null` | no | +| [user\_groups](#input\_user\_groups) | A container with the user\_groups definitions | `list(any)` | `[]` | no | +| [user\_pool\_add\_ons](#input\_user\_pool\_add\_ons) | Configuration block for user pool add-ons to enable user pool advanced security mode features | `map(any)` | `{}` | no | +| [user\_pool\_add\_ons\_advanced\_security\_mode](#input\_user\_pool\_add\_ons\_advanced\_security\_mode) | The mode for advanced security, must be one of `OFF`, `AUDIT` or `ENFORCED` | `string` | `null` | no | +| [user\_pool\_name](#input\_user\_pool\_name) | The name of the user pool | `string` | n/a | yes | +| [username\_attributes](#input\_username\_attributes) | Specifies whether email addresses or phone numbers can be specified as usernames when a user signs up. Conflicts with `alias_attributes` | `list(string)` | `null` | no | +| [username\_configuration](#input\_username\_configuration) | The Username Configuration. Setting `case_sensitive` specifies whether username case sensitivity will be applied for all users in the user pool through Cognito APIs | `map(any)` | `{}` | no | +| [verification\_message\_template](#input\_verification\_message\_template) | The verification message templates configuration | `map(any)` | `{}` | no | +| [verification\_message\_template\_default\_email\_option](#input\_verification\_message\_template\_default\_email\_option) | The default email option. Must be either `CONFIRM_WITH_CODE` or `CONFIRM_WITH_LINK`. Defaults to `CONFIRM_WITH_CODE` | `string` | `null` | no | +| [verification\_message\_template\_email\_message\_by\_link](#input\_verification\_message\_template\_email\_message\_by\_link) | The email message template for sending a confirmation link to the user, it must contain the `{##Click Here##}` placeholder | `string` | `null` | no | +| [verification\_message\_template\_email\_subject\_by\_link](#input\_verification\_message\_template\_email\_subject\_by\_link) | The subject line for the email message template for sending a confirmation link to the user | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the user pool | +| [client\_ids](#output\_client\_ids) | The ids of the user pool clients | +| [client\_ids\_map](#output\_client\_ids\_map) | The ids map of the user pool clients | +| [client\_secrets](#output\_client\_secrets) | The client secrets of the user pool clients | +| [client\_secrets\_map](#output\_client\_secrets\_map) | The client secrets map of the user pool clients | +| [creation\_date](#output\_creation\_date) | The date the user pool was created | +| [domain\_app\_version](#output\_domain\_app\_version) | The app version | +| [domain\_aws\_account\_id](#output\_domain\_aws\_account\_id) | The AWS account ID for the user pool owner | +| [domain\_cloudfront\_distribution\_arn](#output\_domain\_cloudfront\_distribution\_arn) | The ARN of the CloudFront distribution | +| [domain\_s3\_bucket](#output\_domain\_s3\_bucket) | The S3 bucket where the static files for this domain are stored | +| [endpoint](#output\_endpoint) | The endpoint name of the user pool. Example format: cognito-idp.REGION.amazonaws.com/xxxx\_yyyyy | +| [id](#output\_id) | The id of the user pool | +| [last\_modified\_date](#output\_last\_modified\_date) | The date the user pool was last modified | +| [name](#output\_name) | The name of the user pool | +| [resource\_servers\_scope\_identifiers](#output\_resource\_servers\_scope\_identifiers) | A list of all scopes configured in the format identifier/scope\_name | + + +## Know issues +### Removing all lambda triggers +If you define lambda triggers using the `lambda_config` block or any `lambda_config_*` variable and you want to remove all triggers, define the lambda_config block with an empty map `{}` and apply the plan. Then comment the `lambda_config` block or define it as `null` and apply the plan again. + +This is needed because all parameters for the `lambda_config` block are optional and keeping all block attributes empty or null forces to create a `lambda_config {}` block very time a plan/apply is run. diff --git a/modules/aws-cognito-user-pool/client.tf b/modules/aws-cognito-user-pool/client.tf new file mode 100644 index 0000000..303867c --- /dev/null +++ b/modules/aws-cognito-user-pool/client.tf @@ -0,0 +1,90 @@ +resource "aws_cognito_user_pool_client" "client" { + count = var.enabled ? length(local.clients) : 0 + allowed_oauth_flows = lookup(element(local.clients, count.index), "allowed_oauth_flows", null) + allowed_oauth_flows_user_pool_client = lookup(element(local.clients, count.index), "allowed_oauth_flows_user_pool_client", null) + allowed_oauth_scopes = lookup(element(local.clients, count.index), "allowed_oauth_scopes", null) + auth_session_validity = lookup(element(local.clients, count.index), "auth_session_validity", null) + callback_urls = lookup(element(local.clients, count.index), "callback_urls", null) + default_redirect_uri = lookup(element(local.clients, count.index), "default_redirect_uri", null) + explicit_auth_flows = lookup(element(local.clients, count.index), "explicit_auth_flows", null) + generate_secret = lookup(element(local.clients, count.index), "generate_secret", null) + logout_urls = lookup(element(local.clients, count.index), "logout_urls", null) + name = lookup(element(local.clients, count.index), "name", null) + read_attributes = lookup(element(local.clients, count.index), "read_attributes", null) + access_token_validity = lookup(element(local.clients, count.index), "access_token_validity", null) + id_token_validity = lookup(element(local.clients, count.index), "id_token_validity", null) + refresh_token_validity = lookup(element(local.clients, count.index), "refresh_token_validity", null) + supported_identity_providers = lookup(element(local.clients, count.index), "supported_identity_providers", null) + prevent_user_existence_errors = lookup(element(local.clients, count.index), "prevent_user_existence_errors", null) + write_attributes = lookup(element(local.clients, count.index), "write_attributes", null) + enable_token_revocation = lookup(element(local.clients, count.index), "enable_token_revocation", null) + user_pool_id = aws_cognito_user_pool.pool[0].id + + # token_validity_units + dynamic "token_validity_units" { + for_each = length(lookup(element(local.clients, count.index), "token_validity_units", {})) == 0 ? [] : [lookup(element(local.clients, count.index), "token_validity_units")] + content { + access_token = lookup(token_validity_units.value, "access_token", null) + id_token = lookup(token_validity_units.value, "id_token", null) + refresh_token = lookup(token_validity_units.value, "refresh_token", null) + } + } + + depends_on = [ + aws_cognito_resource_server.resource, + aws_cognito_identity_provider.identity_provider + ] +} + +locals { + clients_default = [ + { + allowed_oauth_flows = var.client_allowed_oauth_flows + allowed_oauth_flows_user_pool_client = var.client_allowed_oauth_flows_user_pool_client + allowed_oauth_scopes = var.client_allowed_oauth_scopes + auth_session_validity = var.client_auth_session_validity + callback_urls = var.client_callback_urls + default_redirect_uri = var.client_default_redirect_uri + explicit_auth_flows = var.client_explicit_auth_flows + generate_secret = var.client_generate_secret + logout_urls = var.client_logout_urls + name = var.client_name + read_attributes = var.client_read_attributes + access_token_validity = var.client_access_token_validity + id_token_validity = var.client_id_token_validity + token_validity_units = var.client_token_validity_units + refresh_token_validity = var.client_refresh_token_validity + supported_identity_providers = var.client_supported_identity_providers + prevent_user_existence_errors = var.client_prevent_user_existence_errors + write_attributes = var.client_write_attributes + enable_token_revocation = var.client_enable_token_revocation + } + ] + + # This parses vars.clients which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.clients_default + clients_parsed = [for e in var.clients : { + allowed_oauth_flows = lookup(e, "allowed_oauth_flows", null) + allowed_oauth_flows_user_pool_client = lookup(e, "allowed_oauth_flows_user_pool_client", null) + allowed_oauth_scopes = lookup(e, "allowed_oauth_scopes", null) + auth_session_validity = lookup(e, "auth_session_validity", null) + callback_urls = lookup(e, "callback_urls", null) + default_redirect_uri = lookup(e, "default_redirect_uri", null) + explicit_auth_flows = lookup(e, "explicit_auth_flows", null) + generate_secret = lookup(e, "generate_secret", null) + logout_urls = lookup(e, "logout_urls", null) + name = lookup(e, "name", null) + read_attributes = lookup(e, "read_attributes", null) + access_token_validity = lookup(e, "access_token_validity", null) + id_token_validity = lookup(e, "id_token_validity", null) + refresh_token_validity = lookup(e, "refresh_token_validity", null) + token_validity_units = lookup(e, "token_validity_units", {}) + supported_identity_providers = lookup(e, "supported_identity_providers", null) + prevent_user_existence_errors = lookup(e, "prevent_user_existence_errors", null) + write_attributes = lookup(e, "write_attributes", null) + enable_token_revocation = lookup(e, "enable_token_revocation", null) + } + ] + + clients = length(var.clients) == 0 && (var.client_name == null || var.client_name == "") ? [] : (length(var.clients) > 0 ? local.clients_parsed : local.clients_default) + +} diff --git a/modules/aws-cognito-user-pool/domain.tf b/modules/aws-cognito-user-pool/domain.tf new file mode 100644 index 0000000..7872cf5 --- /dev/null +++ b/modules/aws-cognito-user-pool/domain.tf @@ -0,0 +1,6 @@ +resource "aws_cognito_user_pool_domain" "domain" { + count = !var.enabled || var.domain == null || var.domain == "" ? 0 : 1 + domain = var.domain + certificate_arn = var.domain_certificate_arn + user_pool_id = aws_cognito_user_pool.pool[0].id +} diff --git a/modules/aws-cognito-user-pool/examples/complete/README.md b/modules/aws-cognito-user-pool/examples/complete/README.md new file mode 100644 index 0000000..c025085 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/complete/README.md @@ -0,0 +1,264 @@ +# This is a complete example + +``` +module "aws_cognito_user_pool_complete_example" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "mypool_complete" + alias_attributes = ["email", "phone_number"] + auto_verified_attributes = ["email"] + sms_authentication_message = "Your username is {username} and temporary password is {####}." + sms_verification_message = "This is the verification message {####}." + + deletion_protection = "ACTIVE" + + mfa_configuration = "OPTIONAL" + software_token_mfa_configuration = { + enabled = true + } + + admin_create_user_config = { + email_message = "Dear {username}, your verification code is {####}." + email_subject = "Here, your verification code baby" + sms_message = "Your username is {username} and temporary password is {####}." + } + + device_configuration = { + challenge_required_on_new_device = true + device_only_remembered_on_user_prompt = true + } + + email_configuration = { + email_sending_account = "DEVELOPER" + reply_to_email_address = "email@mydomain.com" + source_arn = "arn:aws:ses:us-east-1:123456789012:identity/myemail@mydomain.com" + } + + lambda_config = { + create_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:create_auth_challenge" + custom_message = "arn:aws:lambda:us-east-1:123456789012:function:custom_message" + define_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:define_auth_challenge" + post_authentication = "arn:aws:lambda:us-east-1:123456789012:function:post_authentication" + post_confirmation = "arn:aws:lambda:us-east-1:123456789012:function:post_confirmation" + pre_authentication = "arn:aws:lambda:us-east-1:123456789012:function:pre_authentication" + pre_sign_up = "arn:aws:lambda:us-east-1:123456789012:function:pre_sign_up" + pre_token_generation = "arn:aws:lambda:us-east-1:123456789012:function:pre_token_generation" + user_migration = "arn:aws:lambda:us-east-1:123456789012:function:user_migration" + verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:verify_auth_challenge_response" + } + + password_policy = { + minimum_length = 10 + require_lowercase = false + require_numbers = true + require_symbols = true + require_uppercase = true + temporary_password_validity_days = 120 + + } + + user_pool_add_ons = { + advanced_security_mode = "ENFORCED" + } + + verification_message_template = { + default_email_option = "CONFIRM_WITH_CODE" + } + + schemas = [ + { + attribute_data_type = "Boolean" + developer_only_attribute = false + mutable = true + name = "available" + required = false + }, + { + attribute_data_type = "Boolean" + developer_only_attribute = true + mutable = true + name = "registered" + required = false + } + ] + + string_schemas = [ + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "gender" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + ] + + number_schemas = [ + { + attribute_data_type = "Number" + developer_only_attribute = true + mutable = true + name = "mynumber1" + required = false + + number_attribute_constraints = { + min_value = 2 + max_value = 6 + } + }, + { + attribute_data_type = "Number" + developer_only_attribute = true + mutable = true + name = "mynumber2" + required = false + + number_attribute_constraints = { + min_value = 2 + max_value = 6 + } + }, + ] + + # user_pool_domain + domain = "mydomain-com" + + # clients + clients = [ + { + allowed_oauth_flows = [] + allowed_oauth_flows_user_pool_client = false + allowed_oauth_scopes = [] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = [] + generate_secret = true + logout_urls = [] + name = "test1" + read_attributes = ["email"] + supported_identity_providers = [] + write_attributes = [] + access_token_validity = 1 + id_token_validity = 1 + refresh_token_validity = 60 + token_validity_units = { + access_token = "hours" + id_token = "hours" + refresh_token = "days" + } + }, + { + allowed_oauth_flows = [] + allowed_oauth_flows_user_pool_client = false + allowed_oauth_scopes = [] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = [] + generate_secret = false + logout_urls = [] + name = "test2" + read_attributes = [] + supported_identity_providers = [] + write_attributes = [] + refresh_token_validity = 30 + }, + { + allowed_oauth_flows = ["code", "implicit"] + allowed_oauth_flows_user_pool_client = true + allowed_oauth_scopes = ["email", "openid"] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = ["CUSTOM_AUTH_FLOW_ONLY", "ADMIN_NO_SRP_AUTH"] + generate_secret = false + logout_urls = ["https://mydomain.com/logout"] + name = "test3" + read_attributes = ["email", "phone_number"] + supported_identity_providers = [] + write_attributes = ["email", "gender", "locale", ] + refresh_token_validity = 30 + } + ] + + # user_group + user_groups = [ + { name = "mygroup1" + description = "My group 1" + }, + { name = "mygroup2" + description = "My group 2" + }, + ] + + # resource_servers + resource_servers = [ + { + identifier = "https://mydomain.com" + name = "mydomain" + scope = [ + { + scope_name = "sample-scope-1" + scope_description = "A sample Scope Description for mydomain.com" + }, + { + scope_name = "sample-scope-2" + scope_description = "Another sample Scope Description for mydomain.com" + }, + ] + }, + { + identifier = "https://weather-read-app.com" + name = "weather-read" + scope = [ + { + scope_name = "weather.read" + scope_description = "Read weather forecasts" + } + ] + } + ] + + # identity_providers + identity_providers = [ + { + provider_name = "Google" + provider_type = "Google" + + provider_details = { + authorize_scopes = "email" + client_id = "your client_id" + client_secret = "your client_secret" + } + + attribute_mapping = { + email = "email" + username = "sub" + gender = "gender" + } + } + ] + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} +``` diff --git a/modules/aws-cognito-user-pool/examples/complete/main.tf b/modules/aws-cognito-user-pool/examples/complete/main.tf new file mode 100644 index 0000000..f4e4270 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/complete/main.tf @@ -0,0 +1,280 @@ +module "aws_cognito_user_pool_complete_example" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "mypool_complete" + alias_attributes = ["email", "phone_number"] + auto_verified_attributes = ["email"] + sms_authentication_message = "Your username is {username} and temporary password is {####}." + sms_verification_message = "This is the verification message {####}." + + deletion_protection = "ACTIVE" + + mfa_configuration = "OPTIONAL" + software_token_mfa_configuration = { + enabled = true + } + + admin_create_user_config = { + email_message = "Dear {username}, your verification code is {####}." + email_subject = "Here, your verification code baby" + sms_message = "Your username is {username} and temporary password is {####}." + } + + device_configuration = { + challenge_required_on_new_device = true + device_only_remembered_on_user_prompt = true + } + + email_configuration = { + email_sending_account = "DEVELOPER" + reply_to_email_address = "email@mydomain.com" + source_arn = "arn:aws:ses:us-east-1:123456789012:identity/myemail@mydomain.com" + } + + lambda_config = { + create_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:create_auth_challenge" + custom_message = "arn:aws:lambda:us-east-1:123456789012:function:custom_message" + define_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:define_auth_challenge" + post_authentication = "arn:aws:lambda:us-east-1:123456789012:function:post_authentication" + post_confirmation = "arn:aws:lambda:us-east-1:123456789012:function:post_confirmation" + pre_authentication = "arn:aws:lambda:us-east-1:123456789012:function:pre_authentication" + pre_sign_up = "arn:aws:lambda:us-east-1:123456789012:function:pre_sign_up" + pre_token_generation = "arn:aws:lambda:us-east-1:123456789012:function:pre_token_generation" + user_migration = "arn:aws:lambda:us-east-1:123456789012:function:user_migration" + verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:verify_auth_challenge_response" + kms_key_id = aws_kms_key.lambda-custom-sender.arn + custom_email_sender = { + lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:custom_email_sender" + lambda_version = "V1_0" + } + custom_sms_sender = { + lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:custom_sms_sender" + lambda_version = "V1_0" + } + } + + password_policy = { + minimum_length = 10 + require_lowercase = false + require_numbers = true + require_symbols = true + require_uppercase = true + temporary_password_validity_days = 120 + + } + + user_pool_add_ons = { + advanced_security_mode = "ENFORCED" + } + + verification_message_template = { + default_email_option = "CONFIRM_WITH_CODE" + } + + schemas = [ + { + attribute_data_type = "Boolean" + developer_only_attribute = false + mutable = true + name = "available" + required = false + }, + { + attribute_data_type = "Boolean" + developer_only_attribute = true + mutable = true + name = "registered" + required = false + } + ] + + string_schemas = [ + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "gender" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + ] + + number_schemas = [ + { + attribute_data_type = "Number" + developer_only_attribute = true + mutable = true + name = "mynumber1" + required = false + + number_attribute_constraints = { + min_value = 2 + max_value = 6 + } + }, + { + attribute_data_type = "Number" + developer_only_attribute = true + mutable = true + name = "mynumber2" + required = false + + number_attribute_constraints = { + min_value = 2 + max_value = 6 + } + }, + ] + + # user_pool_domain + domain = "mydomain-com" + + # clients + clients = [ + { + allowed_oauth_flows = [] + allowed_oauth_flows_user_pool_client = false + allowed_oauth_scopes = [] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = [] + generate_secret = true + logout_urls = [] + name = "test1" + read_attributes = ["email"] + supported_identity_providers = [] + write_attributes = [] + access_token_validity = 1 + id_token_validity = 1 + refresh_token_validity = 60 + token_validity_units = { + access_token = "hours" + id_token = "hours" + refresh_token = "days" + } + }, + { + allowed_oauth_flows = [] + allowed_oauth_flows_user_pool_client = false + allowed_oauth_scopes = [] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = [] + generate_secret = false + logout_urls = [] + name = "test2" + read_attributes = [] + supported_identity_providers = [] + write_attributes = [] + refresh_token_validity = 30 + }, + { + allowed_oauth_flows = ["code", "implicit"] + allowed_oauth_flows_user_pool_client = true + allowed_oauth_scopes = ["email", "openid"] + callback_urls = ["https://mydomain.com/callback"] + default_redirect_uri = "https://mydomain.com/callback" + explicit_auth_flows = ["CUSTOM_AUTH_FLOW_ONLY", "ADMIN_NO_SRP_AUTH"] + generate_secret = false + logout_urls = ["https://mydomain.com/logout"] + name = "test3" + read_attributes = ["email", "phone_number"] + supported_identity_providers = [] + write_attributes = ["email", "gender", "locale", ] + refresh_token_validity = 30 + } + ] + + # user_group + user_groups = [ + { name = "mygroup1" + description = "My group 1" + }, + { name = "mygroup2" + description = "My group 2" + }, + ] + + # resource_servers + resource_servers = [ + { + identifier = "https://mydomain.com" + name = "mydomain" + scope = [ + { + scope_name = "sample-scope-1" + scope_description = "A sample Scope Description for mydomain.com" + }, + { + scope_name = "sample-scope-2" + scope_description = "Another sample Scope Description for mydomain.com" + }, + ] + }, + { + identifier = "https://weather-read-app.com" + name = "weather-read" + scope = [ + { + scope_name = "weather.read" + scope_description = "Read weather forecasts" + } + ] + } + ] + + # identity_providers + identity_providers = [ + { + provider_name = "Google" + provider_type = "Google" + + provider_details = { + authorize_scopes = "email" + client_id = "your client_id" + client_secret = "your client_secret" + attributes_url_add_attributes = "true" + authorize_url = "https://accounts.google.com/o/oauth2/v2/auth" + oidc_issuer = "https://accounts.google.com" + token_request_method = "POST" + token_url = "https://www.googleapis.com/oauth2/v4/token" + } + + attribute_mapping = { + email = "email" + username = "sub" + gender = "gender" + } + } + ] + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} + + +# KMS key for lambda custom sender config" +resource "aws_kms_key" "lambda-custom-sender" { + description = "KMS key for lambda custom sender config" +} diff --git a/modules/aws-cognito-user-pool/examples/complete/provider.tf b/modules/aws-cognito-user-pool/examples/complete/provider.tf new file mode 100644 index 0000000..634c762 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/complete/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = var.env["region"] + profile = var.env["profile"] +} diff --git a/modules/aws-cognito-user-pool/examples/complete/variables.tf b/modules/aws-cognito-user-pool/examples/complete/variables.tf new file mode 100644 index 0000000..c7b7aed --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/complete/variables.tf @@ -0,0 +1,4 @@ +variable "env" { + type = map(any) + default = {} +} diff --git a/modules/aws-cognito-user-pool/examples/simple/README.md b/modules/aws-cognito-user-pool/examples/simple/README.md new file mode 100644 index 0000000..b70971f --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple/README.md @@ -0,0 +1,17 @@ +# The simple example + +``` +module "aws_cognito_user_pool_simple_example" { + + source = "../modules/terraform-aws-cognito-user-pool" + + user_pool_name = "simple_pool" + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} +``` diff --git a/modules/aws-cognito-user-pool/examples/simple/main.tf b/modules/aws-cognito-user-pool/examples/simple/main.tf new file mode 100644 index 0000000..1600e81 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple/main.tf @@ -0,0 +1,13 @@ +module "aws_cognito_user_pool_simple_example" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "simple_pool" + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} diff --git a/modules/aws-cognito-user-pool/examples/simple/provider.tf b/modules/aws-cognito-user-pool/examples/simple/provider.tf new file mode 100644 index 0000000..634c762 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = var.env["region"] + profile = var.env["profile"] +} diff --git a/modules/aws-cognito-user-pool/examples/simple/variables.tf b/modules/aws-cognito-user-pool/examples/simple/variables.tf new file mode 100644 index 0000000..c7b7aed --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple/variables.tf @@ -0,0 +1,4 @@ +variable "env" { + type = map(any) + default = {} +} diff --git a/modules/aws-cognito-user-pool/examples/simple_extended/README.md b/modules/aws-cognito-user-pool/examples/simple_extended/README.md new file mode 100644 index 0000000..34e2987 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple_extended/README.md @@ -0,0 +1,73 @@ +# This is the simple example, but extended +``` +module "aws_cognito_user_pool_simple_extended_example" { + + source = "../modules/terraform-aws-cognito-user-pool" + + user_pool_name = "simple_extended_pool" + alias_attributes = ["email", "phone_number"] + auto_verified_attributes = ["email"] + sms_authentication_message = "Your username is {username} and temporary password is {####}." + sms_verification_message = "This is the verification message {####}." + lambda_config_verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:my_lambda_function" + password_policy_require_lowercase = false + password_policy_minimum_length = 11 + user_pool_add_ons_advanced_security_mode = "OFF" + verification_message_template_default_email_option = "CONFIRM_WITH_CODE" + + # schemas + schemas = [ + { + attribute_data_type = "Boolean" + developer_only_attribute = false + mutable = true + name = "available" + required = false + }, + ] + + string_schemas = [ + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + ] + + # user_pool_domain + domain = "mydomain-com" + + # client + client_name = "client0" + client_allowed_oauth_flows_user_pool_client = false + client_callback_urls = ["https://mydomain.com/callback"] + client_default_redirect_uri = "https://mydomain.com/callback" + client_read_attributes = ["email"] + client_refresh_token_validity = 30 + + + # user_group + user_group_name = "mygroup" + user_group_description = "My group" + + # ressource server + resource_server_identifier = "https://mydomain.com" + resource_server_name = "mydomain" + resource_server_scope_name = "scope" + resource_server_scope_description = "a Sample Scope Description for mydomain" + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} +``` diff --git a/modules/aws-cognito-user-pool/examples/simple_extended/main.tf b/modules/aws-cognito-user-pool/examples/simple_extended/main.tf new file mode 100644 index 0000000..c0ce344 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple_extended/main.tf @@ -0,0 +1,70 @@ +module "aws_cognito_user_pool_simple_extended_example" { + + source = "lgallard/cognito-user-pool/aws" + + user_pool_name = "simple_extended_pool" + alias_attributes = ["email", "phone_number"] + auto_verified_attributes = ["email"] + sms_authentication_message = "Your username is {username} and temporary password is {####}." + sms_verification_message = "This is the verification message {####}." + lambda_config_verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:my_lambda_function" + password_policy_require_lowercase = false + password_policy_minimum_length = 11 + user_pool_add_ons_advanced_security_mode = "OFF" + verification_message_template_default_email_option = "CONFIRM_WITH_CODE" + + # schemas + schemas = [ + { + attribute_data_type = "Boolean" + developer_only_attribute = false + mutable = true + name = "available" + required = false + }, + ] + + string_schemas = [ + { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + + string_attribute_constraints = { + min_length = 7 + max_length = 15 + } + }, + ] + + # user_pool_domain + domain = "mydomain-com" + + # client + client_name = "client0" + client_allowed_oauth_flows_user_pool_client = false + client_callback_urls = ["https://mydomain.com/callback"] + client_default_redirect_uri = "https://mydomain.com/callback" + client_read_attributes = ["email"] + client_refresh_token_validity = 30 + + + # user_group + user_group_name = "mygroup" + user_group_description = "My group" + + # ressource server + resource_server_identifier = "https://mydomain.com" + resource_server_name = "mydomain" + resource_server_scope_name = "scope" + resource_server_scope_description = "a Sample Scope Description for mydomain" + + # tags + tags = { + Owner = "infra" + Environment = "production" + Terraform = true + } +} diff --git a/modules/aws-cognito-user-pool/examples/simple_extended/provider.tf b/modules/aws-cognito-user-pool/examples/simple_extended/provider.tf new file mode 100644 index 0000000..634c762 --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple_extended/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = var.env["region"] + profile = var.env["profile"] +} diff --git a/modules/aws-cognito-user-pool/examples/simple_extended/variables.tf b/modules/aws-cognito-user-pool/examples/simple_extended/variables.tf new file mode 100644 index 0000000..c7b7aed --- /dev/null +++ b/modules/aws-cognito-user-pool/examples/simple_extended/variables.tf @@ -0,0 +1,4 @@ +variable "env" { + type = map(any) + default = {} +} diff --git a/modules/aws-cognito-user-pool/identity-provider.tf b/modules/aws-cognito-user-pool/identity-provider.tf new file mode 100644 index 0000000..5c00861 --- /dev/null +++ b/modules/aws-cognito-user-pool/identity-provider.tf @@ -0,0 +1,11 @@ +resource "aws_cognito_identity_provider" "identity_provider" { + count = var.enabled ? length(var.identity_providers) : 0 + user_pool_id = aws_cognito_user_pool.pool[0].id + provider_name = lookup(element(var.identity_providers, count.index), "provider_name") + provider_type = lookup(element(var.identity_providers, count.index), "provider_type") + + # Optional arguments + attribute_mapping = lookup(element(var.identity_providers, count.index), "attribute_mapping", {}) + idp_identifiers = lookup(element(var.identity_providers, count.index), "idp_identifiers", []) + provider_details = lookup(element(var.identity_providers, count.index), "provider_details", {}) +} diff --git a/modules/aws-cognito-user-pool/main.tf b/modules/aws-cognito-user-pool/main.tf new file mode 100644 index 0000000..a7466db --- /dev/null +++ b/modules/aws-cognito-user-pool/main.tf @@ -0,0 +1,325 @@ +resource "aws_cognito_user_pool" "pool" { + count = var.enabled ? 1 : 0 + + alias_attributes = var.alias_attributes + auto_verified_attributes = var.auto_verified_attributes + name = var.user_pool_name + email_verification_subject = var.email_verification_subject == "" || var.email_verification_subject == null ? var.admin_create_user_config_email_subject : var.email_verification_subject + email_verification_message = var.email_verification_message == "" || var.email_verification_message == null ? var.admin_create_user_config_email_message : var.email_verification_message + mfa_configuration = var.mfa_configuration + sms_authentication_message = var.sms_authentication_message + sms_verification_message = var.sms_verification_message + username_attributes = var.username_attributes + deletion_protection = var.deletion_protection + + # username_configuration + dynamic "username_configuration" { + for_each = local.username_configuration + content { + case_sensitive = lookup(username_configuration.value, "case_sensitive") + } + } + + # admin_create_user_config + dynamic "admin_create_user_config" { + for_each = local.admin_create_user_config + content { + allow_admin_create_user_only = lookup(admin_create_user_config.value, "allow_admin_create_user_only") + + dynamic "invite_message_template" { + for_each = lookup(admin_create_user_config.value, "email_message", null) == null && lookup(admin_create_user_config.value, "email_subject", null) == null && lookup(admin_create_user_config.value, "sms_message", null) == null ? [] : [1] + content { + email_message = lookup(admin_create_user_config.value, "email_message") + email_subject = lookup(admin_create_user_config.value, "email_subject") + sms_message = lookup(admin_create_user_config.value, "sms_message") + } + } + } + } + + # device_configuration + dynamic "device_configuration" { + for_each = local.device_configuration + content { + challenge_required_on_new_device = lookup(device_configuration.value, "challenge_required_on_new_device") + device_only_remembered_on_user_prompt = lookup(device_configuration.value, "device_only_remembered_on_user_prompt") + } + } + + # email_configuration + dynamic "email_configuration" { + for_each = local.email_configuration + content { + configuration_set = lookup(email_configuration.value, "configuration_set") + reply_to_email_address = lookup(email_configuration.value, "reply_to_email_address") + source_arn = lookup(email_configuration.value, "source_arn") + email_sending_account = lookup(email_configuration.value, "email_sending_account") + from_email_address = lookup(email_configuration.value, "from_email_address") + } + } + + # lambda_config + dynamic "lambda_config" { + for_each = var.lambda_config == null || length(var.lambda_config) == 0 ? [] : [1] + content { + create_auth_challenge = lookup(var.lambda_config, "create_auth_challenge", var.lambda_config_create_auth_challenge) + custom_message = lookup(var.lambda_config, "custom_message", var.lambda_config_custom_message) + define_auth_challenge = lookup(var.lambda_config, "define_auth_challenge", var.lambda_config_define_auth_challenge) + post_authentication = lookup(var.lambda_config, "post_authentication", var.lambda_config_post_authentication) + post_confirmation = lookup(var.lambda_config, "post_confirmation", var.lambda_config_post_confirmation) + pre_authentication = lookup(var.lambda_config, "pre_authentication", var.lambda_config_pre_authentication) + pre_sign_up = lookup(var.lambda_config, "pre_sign_up", var.lambda_config_pre_sign_up) + pre_token_generation = lookup(var.lambda_config, "pre_token_generation", var.lambda_config_pre_token_generation) + user_migration = lookup(var.lambda_config, "user_migration", var.lambda_config_user_migration) + verify_auth_challenge_response = lookup(var.lambda_config, "verify_auth_challenge_response", var.lambda_config_verify_auth_challenge_response) + kms_key_id = lookup(var.lambda_config, "kms_key_id", var.lambda_config_kms_key_id) + dynamic "custom_email_sender" { + for_each = lookup(var.lambda_config, "custom_email_sender", var.lambda_config_custom_email_sender) == {} ? [] : [1] + content { + lambda_arn = lookup(lookup(var.lambda_config, "custom_email_sender", var.lambda_config_custom_email_sender), "lambda_arn", null) + lambda_version = lookup(lookup(var.lambda_config, "custom_email_sender", var.lambda_config_custom_email_sender), "lambda_version", null) + } + } + dynamic "custom_sms_sender" { + for_each = lookup(var.lambda_config, "custom_sms_sender", var.lambda_config_custom_sms_sender) == {} ? [] : [1] + content { + lambda_arn = lookup(lookup(var.lambda_config, "custom_sms_sender", var.lambda_config_custom_sms_sender), "lambda_arn", null) + lambda_version = lookup(lookup(var.lambda_config, "custom_sms_sender", var.lambda_config_custom_sms_sender), "lambda_version", null) + } + } + } + } + + # sms_configuration + dynamic "sms_configuration" { + for_each = local.sms_configuration + content { + external_id = lookup(sms_configuration.value, "external_id") + sns_caller_arn = lookup(sms_configuration.value, "sns_caller_arn") + } + } + + # software_token_mfa_configuration + dynamic "software_token_mfa_configuration" { + for_each = local.software_token_mfa_configuration + content { + enabled = lookup(software_token_mfa_configuration.value, "enabled") + } + } + + # password_policy + dynamic "password_policy" { + for_each = local.password_policy + content { + minimum_length = lookup(password_policy.value, "minimum_length") + require_lowercase = lookup(password_policy.value, "require_lowercase") + require_numbers = lookup(password_policy.value, "require_numbers") + require_symbols = lookup(password_policy.value, "require_symbols") + require_uppercase = lookup(password_policy.value, "require_uppercase") + temporary_password_validity_days = lookup(password_policy.value, "temporary_password_validity_days") + } + } + + # schema + dynamic "schema" { + for_each = var.schemas == null ? [] : var.schemas + content { + attribute_data_type = lookup(schema.value, "attribute_data_type") + developer_only_attribute = lookup(schema.value, "developer_only_attribute") + mutable = lookup(schema.value, "mutable") + name = lookup(schema.value, "name") + required = lookup(schema.value, "required") + } + } + + # schema (String) + dynamic "schema" { + for_each = var.string_schemas == null ? [] : var.string_schemas + content { + attribute_data_type = lookup(schema.value, "attribute_data_type") + developer_only_attribute = lookup(schema.value, "developer_only_attribute") + mutable = lookup(schema.value, "mutable") + name = lookup(schema.value, "name") + required = lookup(schema.value, "required") + + # string_attribute_constraints + dynamic "string_attribute_constraints" { + for_each = length(keys(lookup(schema.value, "string_attribute_constraints", {}))) == 0 ? [{}] : [lookup(schema.value, "string_attribute_constraints", {})] + content { + min_length = lookup(string_attribute_constraints.value, "min_length", null) + max_length = lookup(string_attribute_constraints.value, "max_length", null) + } + } + } + } + + # schema (Number) + dynamic "schema" { + for_each = var.number_schemas == null ? [] : var.number_schemas + content { + attribute_data_type = lookup(schema.value, "attribute_data_type") + developer_only_attribute = lookup(schema.value, "developer_only_attribute") + mutable = lookup(schema.value, "mutable") + name = lookup(schema.value, "name") + required = lookup(schema.value, "required") + + # number_attribute_constraints + dynamic "number_attribute_constraints" { + for_each = length(keys(lookup(schema.value, "number_attribute_constraints", {}))) == 0 ? [{}] : [lookup(schema.value, "number_attribute_constraints", {})] + content { + min_value = lookup(number_attribute_constraints.value, "min_value", null) + max_value = lookup(number_attribute_constraints.value, "max_value", null) + } + } + } + } + + # user_pool_add_ons + dynamic "user_pool_add_ons" { + for_each = local.user_pool_add_ons + content { + advanced_security_mode = lookup(user_pool_add_ons.value, "advanced_security_mode") + } + } + + # verification_message_template + dynamic "verification_message_template" { + for_each = local.verification_message_template + content { + default_email_option = lookup(verification_message_template.value, "default_email_option") + email_message_by_link = lookup(verification_message_template.value, "email_message_by_link") + email_subject_by_link = lookup(verification_message_template.value, "email_subject_by_link") + } + } + + dynamic "user_attribute_update_settings" { + for_each = local.user_attribute_update_settings + content { + attributes_require_verification_before_update = lookup(user_attribute_update_settings.value, "attributes_require_verification_before_update") + } + } + + # account_recovery_setting + dynamic "account_recovery_setting" { + for_each = length(var.recovery_mechanisms) == 0 ? [] : [1] + content { + # recovery_mechanism + dynamic "recovery_mechanism" { + for_each = var.recovery_mechanisms + content { + name = lookup(recovery_mechanism.value, "name") + priority = lookup(recovery_mechanism.value, "priority") + } + } + } + } + + # tags + tags = var.tags +} + +locals { + # username_configuration + # If no username_configuration is provided return a empty list + username_configuration_default = length(var.username_configuration) == 0 ? {} : { + case_sensitive = lookup(var.username_configuration, "case_sensitive", true) + } + username_configuration = length(local.username_configuration_default) == 0 ? [] : [local.username_configuration_default] + + # admin_create_user_config + # If no admin_create_user_config list is provided, build a admin_create_user_config using the default values + admin_create_user_config_default = { + allow_admin_create_user_only = lookup(var.admin_create_user_config, "allow_admin_create_user_only", null) == null ? var.admin_create_user_config_allow_admin_create_user_only : lookup(var.admin_create_user_config, "allow_admin_create_user_only") + email_message = lookup(var.admin_create_user_config, "email_message", null) == null ? (var.email_verification_message == "" || var.email_verification_message == null ? var.admin_create_user_config_email_message : var.email_verification_message) : lookup(var.admin_create_user_config, "email_message") + email_subject = lookup(var.admin_create_user_config, "email_subject", null) == null ? (var.email_verification_subject == "" || var.email_verification_subject == null ? var.admin_create_user_config_email_subject : var.email_verification_subject) : lookup(var.admin_create_user_config, "email_subject") + sms_message = lookup(var.admin_create_user_config, "sms_message", null) == null ? var.admin_create_user_config_sms_message : lookup(var.admin_create_user_config, "sms_message") + + } + + admin_create_user_config = [local.admin_create_user_config_default] + + # sms_configuration + # If no sms_configuration list is provided, build a sms_configuration using the default values + sms_configuration_default = { + external_id = lookup(var.sms_configuration, "external_id", null) == null ? var.sms_configuration_external_id : lookup(var.sms_configuration, "external_id") + sns_caller_arn = lookup(var.sms_configuration, "sns_caller_arn", null) == null ? var.sms_configuration_sns_caller_arn : lookup(var.sms_configuration, "sns_caller_arn") + } + + sms_configuration = lookup(local.sms_configuration_default, "external_id") == "" || lookup(local.sms_configuration_default, "sns_caller_arn") == "" ? [] : [local.sms_configuration_default] + + # device_configuration + # If no device_configuration list is provided, build a device_configuration using the default values + device_configuration_default = { + challenge_required_on_new_device = lookup(var.device_configuration, "challenge_required_on_new_device", null) == null ? var.device_configuration_challenge_required_on_new_device : lookup(var.device_configuration, "challenge_required_on_new_device") + device_only_remembered_on_user_prompt = lookup(var.device_configuration, "device_only_remembered_on_user_prompt", null) == null ? var.device_configuration_device_only_remembered_on_user_prompt : lookup(var.device_configuration, "device_only_remembered_on_user_prompt") + } + + device_configuration = lookup(local.device_configuration_default, "challenge_required_on_new_device") == false && lookup(local.device_configuration_default, "device_only_remembered_on_user_prompt") == false ? [] : [local.device_configuration_default] + + # email_configuration + # If no email_configuration is provided, build a email_configuration using the default values + email_configuration_default = { + configuration_set = lookup(var.email_configuration, "configuration_set", null) == null ? var.email_configuration_configuration_set : lookup(var.email_configuration, "configuration_set") + reply_to_email_address = lookup(var.email_configuration, "reply_to_email_address", null) == null ? var.email_configuration_reply_to_email_address : lookup(var.email_configuration, "reply_to_email_address") + source_arn = lookup(var.email_configuration, "source_arn", null) == null ? var.email_configuration_source_arn : lookup(var.email_configuration, "source_arn") + email_sending_account = lookup(var.email_configuration, "email_sending_account", null) == null ? var.email_configuration_email_sending_account : lookup(var.email_configuration, "email_sending_account") + from_email_address = lookup(var.email_configuration, "from_email_address", null) == null ? var.email_configuration_from_email_address : lookup(var.email_configuration, "from_email_address") + } + + email_configuration = [local.email_configuration_default] + + # password_policy + # If no password_policy is provided, build a password_policy using the default values + # If lambda_config is null + password_policy_is_null = { + minimum_length = var.password_policy_minimum_length + require_lowercase = var.password_policy_require_lowercase + require_numbers = var.password_policy_require_numbers + require_symbols = var.password_policy_require_symbols + require_uppercase = var.password_policy_require_uppercase + temporary_password_validity_days = var.password_policy_temporary_password_validity_days + } + + password_policy_not_null = var.password_policy == null ? local.password_policy_is_null : { + minimum_length = lookup(var.password_policy, "minimum_length", null) == null ? var.password_policy_minimum_length : lookup(var.password_policy, "minimum_length") + require_lowercase = lookup(var.password_policy, "require_lowercase", null) == null ? var.password_policy_require_lowercase : lookup(var.password_policy, "require_lowercase") + require_numbers = lookup(var.password_policy, "require_numbers", null) == null ? var.password_policy_require_numbers : lookup(var.password_policy, "require_numbers") + require_symbols = lookup(var.password_policy, "require_symbols", null) == null ? var.password_policy_require_symbols : lookup(var.password_policy, "require_symbols") + require_uppercase = lookup(var.password_policy, "require_uppercase", null) == null ? var.password_policy_require_uppercase : lookup(var.password_policy, "require_uppercase") + temporary_password_validity_days = lookup(var.password_policy, "temporary_password_validity_days", null) == null ? var.password_policy_temporary_password_validity_days : lookup(var.password_policy, "temporary_password_validity_days") + + } + + # Return the default values + password_policy = var.password_policy == null ? [local.password_policy_is_null] : [local.password_policy_not_null] + + # user_pool_add_ons + # If no user_pool_add_ons is provided, build a configuration using the default values + user_pool_add_ons_default = { + advanced_security_mode = lookup(var.user_pool_add_ons, "advanced_security_mode", null) == null ? var.user_pool_add_ons_advanced_security_mode : lookup(var.user_pool_add_ons, "advanced_security_mode") + } + + user_pool_add_ons = var.user_pool_add_ons_advanced_security_mode == null && length(var.user_pool_add_ons) == 0 ? [] : [local.user_pool_add_ons_default] + + # verification_message_template + # If no verification_message_template is provided, build a verification_message_template using the default values + verification_message_template_default = { + default_email_option = lookup(var.verification_message_template, "default_email_option", null) == null ? var.verification_message_template_default_email_option : lookup(var.verification_message_template, "default_email_option") + email_message_by_link = lookup(var.verification_message_template, "email_message_by_link", null) == null ? var.verification_message_template_email_message_by_link : lookup(var.verification_message_template, "email_message_by_link") + email_subject_by_link = lookup(var.verification_message_template, "email_subject_by_link", null) == null ? var.verification_message_template_email_subject_by_link : lookup(var.verification_message_template, "email_subject_by_link") + } + + verification_message_template = [local.verification_message_template_default] + + # software_token_mfa_configuration + # If no software_token_mfa_configuration is provided, build a software_token_mfa_configuration using the default values + software_token_mfa_configuration_default = { + enabled = lookup(var.software_token_mfa_configuration, "enabled", null) == null ? var.software_token_mfa_configuration_enabled : lookup(var.software_token_mfa_configuration, "enabled") + } + + software_token_mfa_configuration = (length(var.sms_configuration) == 0 || local.sms_configuration == null) && var.mfa_configuration == "OFF" ? [] : [local.software_token_mfa_configuration_default] + + # user_attribute_update_settings + # As default, all auto_verified_attributes will become attributes_require_verification_before_update + user_attribute_update_settings = var.user_attribute_update_settings == null ? (length(var.auto_verified_attributes) > 0 ? [{ attributes_require_verification_before_update = var.auto_verified_attributes }] : []) : [var.user_attribute_update_settings] +} diff --git a/modules/aws-cognito-user-pool/outputs.tf b/modules/aws-cognito-user-pool/outputs.tf new file mode 100644 index 0000000..ff94fbe --- /dev/null +++ b/modules/aws-cognito-user-pool/outputs.tf @@ -0,0 +1,85 @@ +output "id" { + description = "The id of the user pool" + value = var.enabled ? aws_cognito_user_pool.pool[0].id : null +} + +output "arn" { + description = "The ARN of the user pool" + value = var.enabled ? aws_cognito_user_pool.pool[0].arn : null +} + +output "endpoint" { + description = "The endpoint name of the user pool. Example format: cognito-idp.REGION.amazonaws.com/xxxx_yyyyy" + value = var.enabled ? aws_cognito_user_pool.pool[0].endpoint : null +} + +output "creation_date" { + description = "The date the user pool was created" + value = var.enabled ? aws_cognito_user_pool.pool[0].creation_date : null +} + +output "last_modified_date" { + description = "The date the user pool was last modified" + value = var.enabled ? aws_cognito_user_pool.pool[0].last_modified_date : null +} + +output "name" { + description = "The name of the user pool" + value = var.enabled ? aws_cognito_user_pool.pool[0].name : null +} + +# +# aws_cognito_user_pool_domain +# +output "domain_aws_account_id" { + description = "The AWS account ID for the user pool owner" + value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.aws_account_id) : null +} + +output "domain_cloudfront_distribution_arn" { + description = "The ARN of the CloudFront distribution" + value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.cloudfront_distribution_arn) : null +} + +output "domain_s3_bucket" { + description = "The S3 bucket where the static files for this domain are stored" + value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.s3_bucket) : null +} + +output "domain_app_version" { + description = "The app version" + value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.version) : null +} + +# +# aws_cognito_user_pool_client +# +output "client_ids" { + description = "The ids of the user pool clients" + value = var.enabled ? aws_cognito_user_pool_client.client.*.id : null +} + +output "client_secrets" { + description = " The client secrets of the user pool clients" + value = var.enabled ? aws_cognito_user_pool_client.client.*.client_secret : null + sensitive = true +} + +output "client_ids_map" { + description = "The ids map of the user pool clients" + value = var.enabled ? { for k, v in aws_cognito_user_pool_client.client : v.name => v.id } : null +} + +output "client_secrets_map" { + description = "The client secrets map of the user pool clients" + value = var.enabled ? { for k, v in aws_cognito_user_pool_client.client : v.name => v.client_secret } : null + sensitive = true +} + +# +# aws_cognito_resource_servers +# +output "resource_servers_scope_identifiers" { + description = " A list of all scopes configured in the format identifier/scope_name" + value = var.enabled ? aws_cognito_resource_server.resource.*.scope_identifiers : null +} diff --git a/modules/aws-cognito-user-pool/resource-server.tf b/modules/aws-cognito-user-pool/resource-server.tf new file mode 100644 index 0000000..ec1f1e7 --- /dev/null +++ b/modules/aws-cognito-user-pool/resource-server.tf @@ -0,0 +1,41 @@ +resource "aws_cognito_resource_server" "resource" { + count = var.enabled ? length(local.resource_servers) : 0 + name = lookup(element(local.resource_servers, count.index), "name") + identifier = lookup(element(local.resource_servers, count.index), "identifier") + + #scope + dynamic "scope" { + for_each = lookup(element(local.resource_servers, count.index), "scope") + content { + scope_name = lookup(scope.value, "scope_name") + scope_description = lookup(scope.value, "scope_description") + } + } + + user_pool_id = aws_cognito_user_pool.pool[0].id +} + +locals { + resource_server_default = [ + { + name = var.resource_server_name + identifier = var.resource_server_identifier + scope = [ + { + scope_name = var.resource_server_scope_name + scope_description = var.resource_server_scope_description + }] + } + ] + + # This parses var.user_groups which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.groups_default + resource_servers_parsed = [for e in var.resource_servers : { + name = lookup(e, "name", null) + identifier = lookup(e, "identifier", null) + scope = lookup(e, "scope", []) + } + ] + + resource_servers = length(var.resource_servers) == 0 && (var.resource_server_name == null || var.resource_server_name == "") ? [] : (length(var.resource_servers) > 0 ? local.resource_servers_parsed : local.resource_server_default) + +} diff --git a/modules/aws-cognito-user-pool/user-group.tf b/modules/aws-cognito-user-pool/user-group.tf new file mode 100644 index 0000000..c21cdf9 --- /dev/null +++ b/modules/aws-cognito-user-pool/user-group.tf @@ -0,0 +1,32 @@ +resource "aws_cognito_user_group" "main" { + count = var.enabled ? length(local.groups) : 0 + name = lookup(element(local.groups, count.index), "name") + description = lookup(element(local.groups, count.index), "description") + precedence = lookup(element(local.groups, count.index), "precedence") + role_arn = lookup(element(local.groups, count.index), "role_arn") + user_pool_id = aws_cognito_user_pool.pool[0].id +} + +locals { + groups_default = [ + { + name = var.user_group_name + description = var.user_group_description + precedence = var.user_group_precedence + role_arn = var.user_group_role_arn + + } + ] + + # This parses var.user_groups which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.groups_default + groups_parsed = [for e in var.user_groups : { + name = lookup(e, "name", null) + description = lookup(e, "description", null) + precedence = lookup(e, "precedence", null) + role_arn = lookup(e, "role_arn", null) + } + ] + + groups = length(var.user_groups) == 0 && (var.user_group_name == null || var.user_group_name == "") ? [] : (length(var.user_groups) > 0 ? local.groups_parsed : local.groups_default) + +} diff --git a/modules/aws-cognito-user-pool/variables.tf b/modules/aws-cognito-user-pool/variables.tf new file mode 100644 index 0000000..1dcd888 --- /dev/null +++ b/modules/aws-cognito-user-pool/variables.tf @@ -0,0 +1,634 @@ +# +# aws_cognito_user_pool +# +variable "enabled" { + description = "Change to false to avoid deploying any resources" + type = bool + default = true +} + +variable "user_pool_name" { + description = "The name of the user pool" + type = string +} + +variable "email_verification_message" { + description = "A string representing the email verification message" + type = string + default = null +} + +variable "email_verification_subject" { + description = "A string representing the email verification subject" + type = string + default = null +} + +# username_configuration +variable "username_configuration" { + description = "The Username Configuration. Setting `case_sensitive` specifies whether username case sensitivity will be applied for all users in the user pool through Cognito APIs" + type = map(any) + default = {} +} + +# admin_create_user_config +variable "admin_create_user_config" { + description = "The configuration for AdminCreateUser requests" + type = map(any) + default = {} +} + +variable "admin_create_user_config_allow_admin_create_user_only" { + description = "Set to True if only the administrator is allowed to create user profiles. Set to False if users can sign themselves up via an app" + type = bool + default = true +} + +variable "temporary_password_validity_days" { + description = "The user account expiration limit, in days, after which the account is no longer usable" + type = number + default = 7 +} + +variable "admin_create_user_config_email_message" { + description = "The message template for email messages. Must contain `{username}` and `{####}` placeholders, for username and temporary password, respectively" + type = string + default = "{username}, your verification code is `{####}`" +} + + +variable "admin_create_user_config_email_subject" { + description = "The subject line for email messages" + type = string + default = "Your verification code" +} + +variable "admin_create_user_config_sms_message" { + description = "- The message template for SMS messages. Must contain `{username}` and `{####}` placeholders, for username and temporary password, respectively" + type = string + default = "Your username is {username} and temporary password is `{####}`" +} + +variable "alias_attributes" { + description = "Attributes supported as an alias for this user pool. Possible values: phone_number, email, or preferred_username. Conflicts with `username_attributes`" + type = list(string) + default = null +} + +variable "username_attributes" { + description = "Specifies whether email addresses or phone numbers can be specified as usernames when a user signs up. Conflicts with `alias_attributes`" + type = list(string) + default = null +} + +variable "deletion_protection" { + description = "When active, DeletionProtection prevents accidental deletion of your user pool. Before you can delete a user pool that you have protected against deletion, you must deactivate this feature. Valid values are `ACTIVE` and `INACTIVE`." + type = string + default = "INACTIVE" +} + +variable "auto_verified_attributes" { + description = "The attributes to be auto-verified. Possible values: email, phone_number" + type = list(string) + default = [] +} + +# sms_configuration +variable "sms_configuration" { + description = "The SMS Configuration" + type = map(any) + default = {} +} + +variable "sms_configuration_external_id" { + description = "The external ID used in IAM role trust relationships" + type = string + default = "" +} + +variable "sms_configuration_sns_caller_arn" { + description = "The ARN of the Amazon SNS caller. This is usually the IAM role that you've given Cognito permission to assume" + type = string + default = "" +} + +# device_configuration +variable "device_configuration" { + description = "The configuration for the user pool's device tracking" + type = map(any) + default = {} +} + +variable "device_configuration_challenge_required_on_new_device" { + description = "Indicates whether a challenge is required on a new device. Only applicable to a new device" + type = bool + default = false +} + +variable "device_configuration_device_only_remembered_on_user_prompt" { + description = "If true, a device is only remembered on user prompt" + type = bool + default = false +} + +# email_configuration +variable "email_configuration" { + description = "The Email Configuration" + type = map(any) + default = {} +} + +variable "email_configuration_configuration_set" { + description = "The name of the configuration set" + type = string + default = null +} + +variable "email_configuration_reply_to_email_address" { + description = "The REPLY-TO email address" + type = string + default = "" +} + +variable "email_configuration_source_arn" { + description = "The ARN of the email source" + type = string + default = "" +} + +variable "email_configuration_email_sending_account" { + description = "Instruct Cognito to either use its built-in functional or Amazon SES to send out emails. Allowed values: `COGNITO_DEFAULT` or `DEVELOPER`" + type = string + default = "COGNITO_DEFAULT" +} + +variable "email_configuration_from_email_address" { + description = "Sender’s email address or sender’s display name with their email address (e.g. `john@example.com`, `John Smith ` or `\"John Smith Ph.D.\" )`. Escaped double quotes are required around display names that contain certain characters as specified in RFC 5322" + type = string + default = null +} + +# lambda_config +variable "lambda_config" { + description = "A container for the AWS Lambda triggers associated with the user pool" + type = any + default = {} +} + +variable "lambda_config_create_auth_challenge" { + description = "The ARN of the lambda creating an authentication challenge." + type = string + default = null +} + +variable "lambda_config_custom_message" { + description = "A custom Message AWS Lambda trigger." + type = string + default = null +} + +variable "lambda_config_define_auth_challenge" { + description = "Defines the authentication challenge." + type = string + default = null +} + +variable "lambda_config_post_authentication" { + description = "A post-authentication AWS Lambda trigger" + type = string + default = null +} + +variable "lambda_config_post_confirmation" { + description = "A post-confirmation AWS Lambda trigger" + type = string + default = null +} + +variable "lambda_config_pre_authentication" { + description = "A pre-authentication AWS Lambda trigger" + type = string + default = null +} +variable "lambda_config_pre_sign_up" { + description = "A pre-registration AWS Lambda trigger" + type = string + default = null +} + +variable "lambda_config_pre_token_generation" { + description = "Allow to customize identity token claims before token generation" + type = string + default = null +} + +variable "lambda_config_user_migration" { + description = "The user migration Lambda config type" + type = string + default = null +} + +variable "lambda_config_verify_auth_challenge_response" { + description = "Verifies the authentication challenge response" + type = string + default = null +} + +variable "lambda_config_kms_key_id" { + description = "The Amazon Resource Name of Key Management Service Customer master keys. Amazon Cognito uses the key to encrypt codes and temporary passwords sent to CustomEmailSender and CustomSMSSender." + type = string + default = null +} + +variable "lambda_config_custom_email_sender" { + description = "A custom email sender AWS Lambda trigger." + type = any + default = {} +} + +variable "lambda_config_custom_sms_sender" { + description = "A custom SMS sender AWS Lambda trigger." + type = any + default = {} +} + +variable "mfa_configuration" { + description = "Set to enable multi-factor authentication. Must be one of the following values (ON, OFF, OPTIONAL)" + type = string + default = "OFF" +} + +# software_token_mfa_configuration +variable "software_token_mfa_configuration" { + description = "Configuration block for software token MFA (multifactor-auth). mfa_configuration must also be enabled for this to work" + type = map(any) + default = {} +} + +variable "software_token_mfa_configuration_enabled" { + description = "If true, and if mfa_configuration is also enabled, multi-factor authentication by software TOTP generator will be enabled" + type = bool + default = false +} + +# password_policy +variable "password_policy" { + description = "A container for information about the user pool password policy" + type = object({ + minimum_length = number, + require_lowercase = bool, + require_numbers = bool, + require_symbols = bool, + require_uppercase = bool, + temporary_password_validity_days = number + }) + default = null +} + +variable "password_policy_minimum_length" { + description = "The minimum length of the password policy that you have set" + type = number + default = 8 +} + +variable "password_policy_require_lowercase" { + description = "Whether you have required users to use at least one lowercase letter in their password" + type = bool + default = true +} + +variable "password_policy_require_numbers" { + description = "Whether you have required users to use at least one number in their password" + type = bool + default = true +} + +variable "password_policy_require_symbols" { + description = "Whether you have required users to use at least one symbol in their password" + type = bool + default = true +} + +variable "password_policy_require_uppercase" { + description = "Whether you have required users to use at least one uppercase letter in their password" + type = bool + default = true +} + +variable "password_policy_temporary_password_validity_days" { + description = "The minimum length of the password policy that you have set" + type = number + default = 7 +} + +# schema +variable "schemas" { + description = "A container with the schema attributes of a user pool. Maximum of 50 attributes" + type = list(any) + default = [] +} + +variable "string_schemas" { + description = "A container with the string schema attributes of a user pool. Maximum of 50 attributes" + type = list(any) + default = [] +} + +variable "number_schemas" { + description = "A container with the number schema attributes of a user pool. Maximum of 50 attributes" + type = list(any) + default = [] +} + +# sms messages +variable "sms_authentication_message" { + description = "A string representing the SMS authentication message" + type = string + default = null +} + +variable "sms_verification_message" { + description = "A string representing the SMS verification message" + type = string + default = null +} + +# tags +variable "tags" { + description = "A mapping of tags to assign to the User Pool" + type = map(string) + default = {} +} + +# user_attribute_update_settings +variable "user_attribute_update_settings" { + description = "Configuration block for user attribute update settings. Must contain key `attributes_require_verification_before_update` with list with only valid values of `email` and `phone_number`" + type = map(list(string)) + default = null +} + +# user_pool_add_ons +variable "user_pool_add_ons" { + description = "Configuration block for user pool add-ons to enable user pool advanced security mode features" + type = map(any) + default = {} +} + +variable "user_pool_add_ons_advanced_security_mode" { + description = "The mode for advanced security, must be one of `OFF`, `AUDIT` or `ENFORCED`" + type = string + default = null +} + +# verification_message_template +variable "verification_message_template" { + description = "The verification message templates configuration" + type = map(any) + default = {} +} + +variable "verification_message_template_default_email_option" { + description = "The default email option. Must be either `CONFIRM_WITH_CODE` or `CONFIRM_WITH_LINK`. Defaults to `CONFIRM_WITH_CODE`" + type = string + default = null +} + +variable "verification_message_template_email_message_by_link" { + description = "The email message template for sending a confirmation link to the user, it must contain the `{##Click Here##}` placeholder" + type = string + default = null +} + +variable "verification_message_template_email_subject_by_link" { + description = "The subject line for the email message template for sending a confirmation link to the user" + type = string + default = null +} + +# +# aws_cognito_user_pool_domain +# +variable "domain" { + description = "Cognito User Pool domain" + type = string + default = null +} + +variable "domain_certificate_arn" { + description = "The ARN of an ISSUED ACM certificate in us-east-1 for a custom domain" + type = string + default = null +} + +# +# aws_cognito_user_pool_client +# +variable "clients" { + description = "A container with the clients definitions" + type = any + default = [] +} + +variable "client_allowed_oauth_flows" { + description = "The name of the application client" + type = list(string) + default = [] +} + +variable "client_allowed_oauth_flows_user_pool_client" { + description = "Whether the client is allowed to follow the OAuth protocol when interacting with Cognito user pools" + type = bool + default = true +} + +variable "client_allowed_oauth_scopes" { + description = "List of allowed OAuth scopes (phone, email, openid, profile, and aws.cognito.signin.user.admin)" + type = list(string) + default = [] +} + +variable "client_auth_session_validity" { + description = "Amazon Cognito creates a session token for each API request in an authentication flow. AuthSessionValidity is the duration, in minutes, of that session token. Your user pool native user must respond to each authentication challenge before the session expires. Valid values between 3 and 15. Default value is 3." + type = number + default = 3 +} + +variable "client_callback_urls" { + description = "List of allowed callback URLs for the identity providers" + type = list(string) + default = [] +} + +variable "client_default_redirect_uri" { + description = "The default redirect URI. Must be in the list of callback URLs" + type = string + default = "" +} + +variable "client_enable_token_revocation" { + description = "Whether the client token can be revoked" + type = bool + default = true +} + +variable "client_explicit_auth_flows" { + description = "List of authentication flows (ADMIN_NO_SRP_AUTH, CUSTOM_AUTH_FLOW_ONLY, USER_PASSWORD_AUTH)" + type = list(string) + default = [] +} + +variable "client_generate_secret" { + description = "Should an application secret be generated" + type = bool + default = true +} + +variable "client_logout_urls" { + description = "List of allowed logout URLs for the identity providers" + type = list(string) + default = [] +} + +variable "client_name" { + description = "The name of the application client" + type = string + default = null +} + +variable "client_read_attributes" { + description = "List of user pool attributes the application client can read from" + type = list(string) + default = [] +} + +variable "client_prevent_user_existence_errors" { + description = "Choose which errors and responses are returned by Cognito APIs during authentication, account confirmation, and password recovery when the user does not exist in the user pool. When set to ENABLED and the user does not exist, authentication returns an error indicating either the username or password was incorrect, and account confirmation and password recovery return a response indicating a code was sent to a simulated destination. When set to LEGACY, those APIs will return a UserNotFoundException exception if the user does not exist in the user pool." + type = string + default = null +} + +variable "client_supported_identity_providers" { + description = "List of provider names for the identity providers that are supported on this client" + type = list(string) + default = [] +} + +variable "client_write_attributes" { + description = "List of user pool attributes the application client can write to" + type = list(string) + default = [] +} + +variable "client_access_token_validity" { + description = "Time limit, between 5 minutes and 1 day, after which the access token is no longer valid and cannot be used. This value will be overridden if you have entered a value in `token_validity_units`." + type = number + default = 60 +} + +variable "client_id_token_validity" { + description = "Time limit, between 5 minutes and 1 day, after which the ID token is no longer valid and cannot be used. Must be between 5 minutes and 1 day. Cannot be greater than refresh token expiration. This value will be overridden if you have entered a value in `token_validity_units`." + type = number + default = 60 +} + +variable "client_refresh_token_validity" { + description = "The time limit in days refresh tokens are valid for. Must be between 60 minutes and 3650 days. This value will be overridden if you have entered a value in `token_validity_units`" + type = number + default = 30 +} + +variable "client_token_validity_units" { + description = "Configuration block for units in which the validity times are represented in. Valid values for the following arguments are: `seconds`, `minutes`, `hours` or `days`." + type = any + default = { + access_token = "minutes" + id_token = "minutes" + refresh_token = "days" + } + +} + +# +# aws_cognito_user_group +# +variable "user_groups" { + description = "A container with the user_groups definitions" + type = list(any) + default = [] +} + +variable "user_group_name" { + description = "The name of the user group" + type = string + default = null +} + +variable "user_group_description" { + description = "The description of the user group" + type = string + default = null +} + +variable "user_group_precedence" { + description = "The precedence of the user group" + type = number + default = null +} + +variable "user_group_role_arn" { + description = "The ARN of the IAM role to be associated with the user group" + type = string + default = null +} + +# +# aws_cognito_resource_server +# +variable "resource_servers" { + description = "A container with the user_groups definitions" + type = list(any) + default = [] +} + +variable "resource_server_name" { + description = "A name for the resource server" + type = string + default = null +} + +variable "resource_server_identifier" { + description = "An identifier for the resource server" + type = string + default = null +} + +variable "resource_server_scope_name" { + description = "The scope name" + type = string + default = null +} + +variable "resource_server_scope_description" { + description = "The scope description" + type = string + default = null +} + +# +# Account Recovery Setting +# +variable "recovery_mechanisms" { + description = "The list of Account Recovery Options" + type = list(any) + default = [] +} + +# +# aws_cognito_identity_provider +# +variable "identity_providers" { + description = "Cognito Pool Identity Providers" + type = list(any) + default = [] + sensitive = true +} diff --git a/modules/aws-cognito-user-pool/versions.tf b/modules/aws-cognito-user-pool/versions.tf new file mode 100644 index 0000000..f16bdd9 --- /dev/null +++ b/modules/aws-cognito-user-pool/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= v0.13.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.38" + } + } +} diff --git a/modules/aws-dax/README.md b/modules/aws-dax/README.md new file mode 100644 index 0000000..8e75d31 --- /dev/null +++ b/modules/aws-dax/README.md @@ -0,0 +1,13 @@ +# terraform-aws-dax +# Sample way of calling this module + +``` +module "dax" { + version = "0.0.1" + iam_role_arn = "iam_arn" + name = "dax-cluster-name" + node_count = 1 + node_type = "dax.t2.small" + subnet_ids = [sub-xxxx] +} +``` \ No newline at end of file diff --git a/modules/aws-dax/iam.tf b/modules/aws-dax/iam.tf new file mode 100644 index 0000000..02571b1 --- /dev/null +++ b/modules/aws-dax/iam.tf @@ -0,0 +1,3 @@ +data "aws_iam_role" "DaxtoDynamoDBRole" { + name = var.iam_role_name +} \ No newline at end of file diff --git a/modules/aws-dax/main.tf b/modules/aws-dax/main.tf new file mode 100644 index 0000000..6b84979 --- /dev/null +++ b/modules/aws-dax/main.tf @@ -0,0 +1,57 @@ +# Create DAX Subnet Group +resource "aws_dax_subnet_group" "subnet_group" { + name = var.name + subnet_ids = var.subnet_ids +} +# Create DAX Subnet Group +resource "aws_dax_parameter_group" "parameter_group" { + name = var.name + + parameters { + name = "query-ttl-millis" + value = var.query_ttl + } + + parameters { + name = "record-ttl-millis" + value = var.record_ttl + } +} + +# Create DAX Cluster +resource "aws_dax_cluster" "cluster" { + cluster_name = var.name + iam_role_arn = data.aws_iam_role.DaxtoDynamoDBRole.arn + node_type = var.node_type + replication_factor = var.node_count + server_side_encryption { + enabled = var.server_side_encryption + } + parameter_group_name = aws_dax_parameter_group.parameter_group.name + subnet_group_name = aws_dax_subnet_group.subnet_group.name + maintenance_window = var.maintenance_window + security_group_ids = [aws_security_group.dax_sec_group.id] +} + +resource "aws_security_group" "dax_sec_group" { + name = "dax_security_group" + description = "Allow TLS inbound traffic" + vpc_id = var.vpc_id + + ingress { + description = "TLS from VPC" + from_port = 9111 + to_port = 9111 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr_block] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + +} diff --git a/modules/aws-dax/output.tf b/modules/aws-dax/output.tf new file mode 100644 index 0000000..9111813 --- /dev/null +++ b/modules/aws-dax/output.tf @@ -0,0 +1,10 @@ +output "configuration_endpoint" { + value = aws_dax_cluster.cluster.configuration_endpoint + description = "Configuration endpoint for this DAX cluster, consisting of a DNS name and a port number" +} + + +output "cluster_address" { + value = aws_dax_cluster.cluster.cluster_address + description = "DNS name of the DAX cluster without the port appended" +} \ No newline at end of file diff --git a/modules/aws-dax/variables.tf b/modules/aws-dax/variables.tf new file mode 100644 index 0000000..3861942 --- /dev/null +++ b/modules/aws-dax/variables.tf @@ -0,0 +1,62 @@ +variable "name" { + type = string + description = "(Required) Name of Cluster" +} + +variable "subnet_ids" { + type = list(string) + description = "(Required) List of Subnets to use for Cluster Group" +} + +variable "query_ttl" { + type = string + description = "(optional) Query Time To Live in milliseconds Defaults: 300000" + default = "300000" +} + +variable "record_ttl" { + type = string + description = "(optional) Record Time To Live in milliseconds Defaults: 300000" + default = "300000" +} + + +variable "node_type" { + type = string + description = "(Required) The compute and memory capacity of the nodes" +} + +variable "node_count" { + type = number + description = "(Required) The number of nodes in the DAX cluster. If 1 then it will create a single-node cluster, without any read replicas [ Default to 1 ]" + default = 1 +} + +variable "maintenance_window" { + type = string + description = "(Optional) Specifies the weekly time range for when maintenance on the cluster is performed. The format is ddd:hh24:mi-ddd:hh24:mi (24H Clock UTC). The minimum maintenance window is a 60 minute period. Defaults: sun:00:00-sun:01:00" + default = "sun:00:00-sun:01:00" +} + +variable "server_side_encryption" { + type = bool + description = "(Optional) Encrypt at rest options Default = true" + default = true +} + +variable "vpc_cidr_block" { + description = "vpc cidr block" + default = "" +} + +variable "vpc_id" { + description = "vpc id" + default = "" +} + +variable "iam_role_name" { + + description = "role name of dax cluster" + default = "" + +} \ No newline at end of file diff --git a/modules/aws-dax/versions.tf b/modules/aws-dax/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-dax/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-dynamodb-table/README.md b/modules/aws-dynamodb-table/README.md new file mode 100644 index 0000000..17c88ab --- /dev/null +++ b/modules/aws-dynamodb-table/README.md @@ -0,0 +1,122 @@ +# AWS DynamoDB Table Terraform module + +Terraform module to create a DynamoDB table. + +## Usage + +```hcl +module "dynamodb_table" { + source = "terraform-aws-modules/dynamodb-table/aws" + + name = "my-table" + hash_key = "id" + + attributes = [ + { + name = "id" + type = "N" + } + ] + + tags = { + Terraform = "true" + Environment = "staging" + } +} +``` + +## Notes + +**Warning: enabling or disabling autoscaling can cause your table to be recreated** + +There are two separate Terraform resources used for the DynamoDB table: one is for when any autoscaling is enabled the other when disabled. If your table is already created and then you change the variable `autoscaling_enabled` then your table will be recreated by Terraform. In this case you will need to move the old `aws_dynamodb_table` resource that is being `destroyed` to the new resource that is being `created`. For example: + +``` +terraform state mv module.dynamodb_table.aws_dynamodb_table.this module.dynamodb_table.aws_dynamodb_table.autoscaled +``` + +## Module wrappers + +Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13. + +Users of Terragrunt can achieve similar results by using modules provided in the [wrappers](https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table/tree/master/wrappers) directory, if they prefer to reduce amount of configuration files. + +## Examples + +- [Basic example](https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table/tree/master/examples/basic) +- [Autoscaling example](https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table/tree/master/examples/autoscaling) +- [Global tables example](https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table/tree/master/examples/global-tables) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.23 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.23 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.index_read_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.index_write_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.table_read_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.table_write_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_target.index_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_appautoscaling_target.index_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_appautoscaling_target.table_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_appautoscaling_target.table_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_dynamodb_table.autoscaled](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource | +| [aws_dynamodb_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [attributes](#input\_attributes) | List of nested attribute definitions. Only required for hash\_key and range\_key attributes. Each attribute has two properties: name - (Required) The name of the attribute, type - (Required) Attribute type, which must be a scalar type: S, N, or B for (S)tring, (N)umber or (B)inary data | `list(map(string))` | `[]` | no | +| [autoscaling\_defaults](#input\_autoscaling\_defaults) | A map of default autoscaling settings | `map(string)` |
{
"scale_in_cooldown": 0,
"scale_out_cooldown": 0,
"target_value": 70
}
| no | +| [autoscaling\_enabled](#input\_autoscaling\_enabled) | Whether or not to enable autoscaling. See note in README about this setting | `bool` | `false` | no | +| [autoscaling\_indexes](#input\_autoscaling\_indexes) | A map of index autoscaling configurations. See example in examples/autoscaling | `map(map(string))` | `{}` | no | +| [autoscaling\_read](#input\_autoscaling\_read) | A map of read autoscaling settings. `max_capacity` is the only required key. See example in examples/autoscaling | `map(string)` | `{}` | no | +| [autoscaling\_write](#input\_autoscaling\_write) | A map of write autoscaling settings. `max_capacity` is the only required key. See example in examples/autoscaling | `map(string)` | `{}` | no | +| [billing\_mode](#input\_billing\_mode) | Controls how you are billed for read/write throughput and how you manage capacity. The valid values are PROVISIONED or PAY\_PER\_REQUEST | `string` | `"PAY_PER_REQUEST"` | no | +| [create\_table](#input\_create\_table) | Controls if DynamoDB table and associated resources are created | `bool` | `true` | no | +| [global\_secondary\_indexes](#input\_global\_secondary\_indexes) | Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc. | `any` | `[]` | no | +| [hash\_key](#input\_hash\_key) | The attribute to use as the hash (partition) key. Must also be defined as an attribute | `string` | `null` | no | +| [local\_secondary\_indexes](#input\_local\_secondary\_indexes) | Describe an LSI on the table; these can only be allocated at creation so you cannot change this definition after you have created the resource. | `any` | `[]` | no | +| [name](#input\_name) | Name of the DynamoDB table | `string` | `null` | no | +| [point\_in\_time\_recovery\_enabled](#input\_point\_in\_time\_recovery\_enabled) | Whether to enable point-in-time recovery | `bool` | `false` | no | +| [range\_key](#input\_range\_key) | The attribute to use as the range (sort) key. Must also be defined as an attribute | `string` | `null` | no | +| [read\_capacity](#input\_read\_capacity) | The number of read units for this table. If the billing\_mode is PROVISIONED, this field should be greater than 0 | `number` | `null` | no | +| [replica\_regions](#input\_replica\_regions) | Region names for creating replicas for a global DynamoDB table. | `any` | `[]` | no | +| [server\_side\_encryption\_enabled](#input\_server\_side\_encryption\_enabled) | Whether or not to enable encryption at rest using an AWS managed KMS customer master key (CMK) | `bool` | `false` | no | +| [server\_side\_encryption\_kms\_key\_arn](#input\_server\_side\_encryption\_kms\_key\_arn) | The ARN of the CMK that should be used for the AWS KMS encryption. This attribute should only be specified if the key is different from the default DynamoDB CMK, alias/aws/dynamodb. | `string` | `null` | no | +| [stream\_enabled](#input\_stream\_enabled) | Indicates whether Streams are to be enabled (true) or disabled (false). | `bool` | `false` | no | +| [stream\_view\_type](#input\_stream\_view\_type) | When an item in the table is modified, StreamViewType determines what information is written to the table's stream. Valid values are KEYS\_ONLY, NEW\_IMAGE, OLD\_IMAGE, NEW\_AND\_OLD\_IMAGES. | `string` | `null` | no | +| [table\_class](#input\_table\_class) | The storage class of the table. Valid values are STANDARD and STANDARD\_INFREQUENT\_ACCESS | `string` | `null` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Updated Terraform resource management timeouts | `map(string)` |
{
"create": "10m",
"delete": "10m",
"update": "60m"
}
| no | +| [ttl\_attribute\_name](#input\_ttl\_attribute\_name) | The name of the table attribute to store the TTL timestamp in | `string` | `""` | no | +| [ttl\_enabled](#input\_ttl\_enabled) | Indicates whether ttl is enabled | `bool` | `false` | no | +| [write\_capacity](#input\_write\_capacity) | The number of write units for this table. If the billing\_mode is PROVISIONED, this field should be greater than 0 | `number` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | +| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | +| [dynamodb\_table\_stream\_arn](#output\_dynamodb\_table\_stream\_arn) | The ARN of the Table Stream. Only available when var.stream\_enabled is true | +| [dynamodb\_table\_stream\_label](#output\_dynamodb\_table\_stream\_label) | A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream\_enabled is true | + diff --git a/modules/aws-dynamodb-table/autoscaling.tf b/modules/aws-dynamodb-table/autoscaling.tf new file mode 100644 index 0000000..50e765e --- /dev/null +++ b/modules/aws-dynamodb-table/autoscaling.tf @@ -0,0 +1,119 @@ +resource "aws_appautoscaling_target" "table_read" { + count = var.create_table && var.autoscaling_enabled && length(var.autoscaling_read) > 0 ? 1 : 0 + + max_capacity = var.autoscaling_read["max_capacity"] + min_capacity = var.read_capacity + resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}" + scalable_dimension = "dynamodb:table:ReadCapacityUnits" + service_namespace = "dynamodb" +} + +resource "aws_appautoscaling_policy" "table_read_policy" { + count = var.create_table && var.autoscaling_enabled && length(var.autoscaling_read) > 0 ? 1 : 0 + + name = "DynamoDBReadCapacityUtilization:${aws_appautoscaling_target.table_read[0].resource_id}" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.table_read[0].resource_id + scalable_dimension = aws_appautoscaling_target.table_read[0].scalable_dimension + service_namespace = aws_appautoscaling_target.table_read[0].service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "DynamoDBReadCapacityUtilization" + } + + scale_in_cooldown = lookup(var.autoscaling_read, "scale_in_cooldown", var.autoscaling_defaults["scale_in_cooldown"]) + scale_out_cooldown = lookup(var.autoscaling_read, "scale_out_cooldown", var.autoscaling_defaults["scale_out_cooldown"]) + target_value = lookup(var.autoscaling_read, "target_value", var.autoscaling_defaults["target_value"]) + } +} + +resource "aws_appautoscaling_target" "table_write" { + count = var.create_table && var.autoscaling_enabled && length(var.autoscaling_write) > 0 ? 1 : 0 + + max_capacity = var.autoscaling_write["max_capacity"] + min_capacity = var.write_capacity + resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}" + scalable_dimension = "dynamodb:table:WriteCapacityUnits" + service_namespace = "dynamodb" +} + +resource "aws_appautoscaling_policy" "table_write_policy" { + count = var.create_table && var.autoscaling_enabled && length(var.autoscaling_write) > 0 ? 1 : 0 + + name = "DynamoDBWriteCapacityUtilization:${aws_appautoscaling_target.table_write[0].resource_id}" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.table_write[0].resource_id + scalable_dimension = aws_appautoscaling_target.table_write[0].scalable_dimension + service_namespace = aws_appautoscaling_target.table_write[0].service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "DynamoDBWriteCapacityUtilization" + } + + scale_in_cooldown = lookup(var.autoscaling_write, "scale_in_cooldown", var.autoscaling_defaults["scale_in_cooldown"]) + scale_out_cooldown = lookup(var.autoscaling_write, "scale_out_cooldown", var.autoscaling_defaults["scale_out_cooldown"]) + target_value = lookup(var.autoscaling_write, "target_value", var.autoscaling_defaults["target_value"]) + } +} + +resource "aws_appautoscaling_target" "index_read" { + for_each = var.create_table && var.autoscaling_enabled ? var.autoscaling_indexes : {} + + max_capacity = each.value["read_max_capacity"] + min_capacity = each.value["read_min_capacity"] + resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}/index/${each.key}" + scalable_dimension = "dynamodb:index:ReadCapacityUnits" + service_namespace = "dynamodb" +} + +resource "aws_appautoscaling_policy" "index_read_policy" { + for_each = var.create_table && var.autoscaling_enabled ? var.autoscaling_indexes : {} + + name = "DynamoDBReadCapacityUtilization:${aws_appautoscaling_target.index_read[each.key].resource_id}" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.index_read[each.key].resource_id + scalable_dimension = aws_appautoscaling_target.index_read[each.key].scalable_dimension + service_namespace = aws_appautoscaling_target.index_read[each.key].service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "DynamoDBReadCapacityUtilization" + } + + scale_in_cooldown = merge(var.autoscaling_defaults, each.value)["scale_in_cooldown"] + scale_out_cooldown = merge(var.autoscaling_defaults, each.value)["scale_out_cooldown"] + target_value = merge(var.autoscaling_defaults, each.value)["target_value"] + } +} + +resource "aws_appautoscaling_target" "index_write" { + for_each = var.create_table && var.autoscaling_enabled ? var.autoscaling_indexes : {} + + max_capacity = each.value["write_max_capacity"] + min_capacity = each.value["write_min_capacity"] + resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}/index/${each.key}" + scalable_dimension = "dynamodb:index:WriteCapacityUnits" + service_namespace = "dynamodb" +} + +resource "aws_appautoscaling_policy" "index_write_policy" { + for_each = var.create_table && var.autoscaling_enabled ? var.autoscaling_indexes : {} + + name = "DynamoDBWriteCapacityUtilization:${aws_appautoscaling_target.index_write[each.key].resource_id}" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.index_write[each.key].resource_id + scalable_dimension = aws_appautoscaling_target.index_write[each.key].scalable_dimension + service_namespace = aws_appautoscaling_target.index_write[each.key].service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "DynamoDBWriteCapacityUtilization" + } + + scale_in_cooldown = merge(var.autoscaling_defaults, each.value)["scale_in_cooldown"] + scale_out_cooldown = merge(var.autoscaling_defaults, each.value)["scale_out_cooldown"] + target_value = merge(var.autoscaling_defaults, each.value)["target_value"] + } +} diff --git a/modules/aws-dynamodb-table/examples/autoscaling/README.md b/modules/aws-dynamodb-table/examples/autoscaling/README.md new file mode 100644 index 0000000..38b33bf --- /dev/null +++ b/modules/aws-dynamodb-table/examples/autoscaling/README.md @@ -0,0 +1,57 @@ +# DynamoDB Table autoscaling example + +Configuration in this directory creates AWS DynamoDB table with autoscaling. Be sure to read [the note](../../README.md#Notes) about autoscaling settings causing the table to be recreated. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 3.69 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [disabled\_dynamodb\_table](#module\_disabled\_dynamodb\_table) | ../../ | n/a | +| [dynamodb\_table](#module\_dynamodb\_table) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | +| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | +| [dynamodb\_table\_stream\_arn](#output\_dynamodb\_table\_stream\_arn) | The ARN of the Table Stream. Only available when var.stream\_enabled is true | +| [dynamodb\_table\_stream\_label](#output\_dynamodb\_table\_stream\_label) | A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream\_enabled is true | + diff --git a/modules/aws-dynamodb-table/examples/autoscaling/main.tf b/modules/aws-dynamodb-table/examples/autoscaling/main.tf new file mode 100644 index 0000000..7a57c01 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/autoscaling/main.tf @@ -0,0 +1,80 @@ +provider "aws" { + region = "eu-west-1" +} + +resource "random_pet" "this" { + length = 2 +} + +module "dynamodb_table" { + source = "../../" + + name = "my-table-${random_pet.this.id}" + hash_key = "id" + range_key = "title" + billing_mode = "PROVISIONED" + read_capacity = 5 + write_capacity = 5 + autoscaling_enabled = true + + autoscaling_read = { + scale_in_cooldown = 50 + scale_out_cooldown = 40 + target_value = 45 + max_capacity = 10 + } + + autoscaling_write = { + scale_in_cooldown = 50 + scale_out_cooldown = 40 + target_value = 45 + max_capacity = 10 + } + + autoscaling_indexes = { + TitleIndex = { + read_max_capacity = 30 + read_min_capacity = 10 + write_max_capacity = 30 + write_min_capacity = 10 + } + } + + attributes = [ + { + name = "id" + type = "N" + }, + { + name = "title" + type = "S" + }, + { + name = "age" + type = "N" + } + ] + + global_secondary_indexes = [ + { + name = "TitleIndex" + hash_key = "title" + range_key = "age" + projection_type = "INCLUDE" + non_key_attributes = ["id"] + write_capacity = 10 + read_capacity = 10 + } + ] + + tags = { + Terraform = "true" + Environment = "staging" + } +} + +module "disabled_dynamodb_table" { + source = "../../" + + create_table = false +} diff --git a/modules/aws-dynamodb-table/examples/autoscaling/outputs.tf b/modules/aws-dynamodb-table/examples/autoscaling/outputs.tf new file mode 100644 index 0000000..e21f490 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/autoscaling/outputs.tf @@ -0,0 +1,19 @@ +output "dynamodb_table_arn" { + description = "ARN of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_arn +} + +output "dynamodb_table_id" { + description = "ID of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_id +} + +output "dynamodb_table_stream_arn" { + description = "The ARN of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_arn +} + +output "dynamodb_table_stream_label" { + description = "A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_label +} diff --git a/modules/aws-dynamodb-table/examples/autoscaling/variables.tf b/modules/aws-dynamodb-table/examples/autoscaling/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-dynamodb-table/examples/autoscaling/versions.tf b/modules/aws-dynamodb-table/examples/autoscaling/versions.tf new file mode 100644 index 0000000..8d7f35e --- /dev/null +++ b/modules/aws-dynamodb-table/examples/autoscaling/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.69" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-dynamodb-table/examples/basic/README.md b/modules/aws-dynamodb-table/examples/basic/README.md new file mode 100644 index 0000000..caf8131 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/basic/README.md @@ -0,0 +1,57 @@ +# DynamoDB Table example + +Configuration in this directory creates AWS DynamoDB table. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 3.69 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [disabled\_dynamodb\_table](#module\_disabled\_dynamodb\_table) | ../../ | n/a | +| [dynamodb\_table](#module\_dynamodb\_table) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | +| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | +| [dynamodb\_table\_stream\_arn](#output\_dynamodb\_table\_stream\_arn) | The ARN of the Table Stream. Only available when var.stream\_enabled is true | +| [dynamodb\_table\_stream\_label](#output\_dynamodb\_table\_stream\_label) | A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream\_enabled is true | + diff --git a/modules/aws-dynamodb-table/examples/basic/main.tf b/modules/aws-dynamodb-table/examples/basic/main.tf new file mode 100644 index 0000000..74a103c --- /dev/null +++ b/modules/aws-dynamodb-table/examples/basic/main.tf @@ -0,0 +1,53 @@ +provider "aws" { + region = "eu-west-1" +} + +resource "random_pet" "this" { + length = 2 +} + +module "dynamodb_table" { + source = "../../" + + name = "my-table-${random_pet.this.id}" + hash_key = "id" + range_key = "title" + table_class = "STANDARD" + + attributes = [ + { + name = "id" + type = "N" + }, + { + name = "title" + type = "S" + }, + { + name = "age" + type = "N" + } + ] + + global_secondary_indexes = [ + { + name = "TitleIndex" + hash_key = "title" + range_key = "age" + projection_type = "INCLUDE" + non_key_attributes = ["id"] + } + ] + + tags = { + Terraform = "true" + Environment = "staging" + } +} + + +module "disabled_dynamodb_table" { + source = "../../" + + create_table = false +} diff --git a/modules/aws-dynamodb-table/examples/basic/outputs.tf b/modules/aws-dynamodb-table/examples/basic/outputs.tf new file mode 100644 index 0000000..e21f490 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/basic/outputs.tf @@ -0,0 +1,19 @@ +output "dynamodb_table_arn" { + description = "ARN of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_arn +} + +output "dynamodb_table_id" { + description = "ID of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_id +} + +output "dynamodb_table_stream_arn" { + description = "The ARN of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_arn +} + +output "dynamodb_table_stream_label" { + description = "A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_label +} diff --git a/modules/aws-dynamodb-table/examples/basic/variables.tf b/modules/aws-dynamodb-table/examples/basic/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-dynamodb-table/examples/basic/versions.tf b/modules/aws-dynamodb-table/examples/basic/versions.tf new file mode 100644 index 0000000..8d7f35e --- /dev/null +++ b/modules/aws-dynamodb-table/examples/basic/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.69" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-dynamodb-table/examples/global-tables/README.md b/modules/aws-dynamodb-table/examples/global-tables/README.md new file mode 100644 index 0000000..94d7af6 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/global-tables/README.md @@ -0,0 +1,60 @@ +# DynamoDB Table example + +Configuration in this directory creates a Global AWS DynamoDB table with replicas in eu-west-1 and eu-west-2 regions. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.23 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.23 | +| [aws.euwest2](#provider\_aws.euwest2) | >= 4.23 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [dynamodb\_table](#module\_dynamodb\_table) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_key.primary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_kms_key.secondary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | +| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | +| [dynamodb\_table\_stream\_arn](#output\_dynamodb\_table\_stream\_arn) | The ARN of the Table Stream. Only available when var.stream\_enabled is true | +| [dynamodb\_table\_stream\_label](#output\_dynamodb\_table\_stream\_label) | A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream\_enabled is true | + diff --git a/modules/aws-dynamodb-table/examples/global-tables/main.tf b/modules/aws-dynamodb-table/examples/global-tables/main.tf new file mode 100644 index 0000000..b996557 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/global-tables/main.tf @@ -0,0 +1,86 @@ +provider "aws" { + region = "eu-west-1" +} + +provider "aws" { + alias = "euwest2" + region = "eu-west-2" +} + +locals { + tags = { + Terraform = "true" + Environment = "staging" + } +} + +################################################################################ +# Supporting Resources +################################################################################ + +resource "random_pet" "this" { + length = 2 +} + +resource "aws_kms_key" "primary" { + description = "CMK for primary region" + tags = local.tags +} + +resource "aws_kms_key" "secondary" { + provider = aws.euwest2 + + description = "CMK for secondary region" + tags = local.tags +} + +################################################################################ +# DynamoDB Global Table +################################################################################ + +module "dynamodb_table" { + source = "../../" + + name = "my-table-${random_pet.this.id}" + hash_key = "id" + range_key = "title" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + server_side_encryption_enabled = true + server_side_encryption_kms_key_arn = aws_kms_key.primary.arn + + attributes = [ + { + name = "id" + type = "N" + }, + { + name = "title" + type = "S" + }, + { + name = "age" + type = "N" + } + ] + + global_secondary_indexes = [ + { + name = "TitleIndex" + hash_key = "title" + range_key = "age" + projection_type = "INCLUDE" + non_key_attributes = ["id"] + } + ] + + replica_regions = [{ + region_name = "eu-west-2" + kms_key_arn = aws_kms_key.secondary.arn + propagate_tags = true + point_in_time_recovery = true + }] + + tags = local.tags +} diff --git a/modules/aws-dynamodb-table/examples/global-tables/outputs.tf b/modules/aws-dynamodb-table/examples/global-tables/outputs.tf new file mode 100644 index 0000000..e21f490 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/global-tables/outputs.tf @@ -0,0 +1,19 @@ +output "dynamodb_table_arn" { + description = "ARN of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_arn +} + +output "dynamodb_table_id" { + description = "ID of the DynamoDB table" + value = module.dynamodb_table.dynamodb_table_id +} + +output "dynamodb_table_stream_arn" { + description = "The ARN of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_arn +} + +output "dynamodb_table_stream_label" { + description = "A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream_enabled is true" + value = module.dynamodb_table.dynamodb_table_stream_label +} diff --git a/modules/aws-dynamodb-table/examples/global-tables/variables.tf b/modules/aws-dynamodb-table/examples/global-tables/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-dynamodb-table/examples/global-tables/versions.tf b/modules/aws-dynamodb-table/examples/global-tables/versions.tf new file mode 100644 index 0000000..d2bf143 --- /dev/null +++ b/modules/aws-dynamodb-table/examples/global-tables/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.23" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-dynamodb-table/main.tf b/modules/aws-dynamodb-table/main.tf new file mode 100644 index 0000000..a553f24 --- /dev/null +++ b/modules/aws-dynamodb-table/main.tf @@ -0,0 +1,175 @@ +resource "aws_dynamodb_table" "this" { + count = var.create_table && !var.autoscaling_enabled ? 1 : 0 + + name = var.name + billing_mode = var.billing_mode + hash_key = var.hash_key + range_key = var.range_key + read_capacity = var.read_capacity + write_capacity = var.write_capacity + stream_enabled = var.stream_enabled + stream_view_type = var.stream_view_type + table_class = var.table_class + + ttl { + enabled = var.ttl_enabled + attribute_name = var.ttl_attribute_name + } + + point_in_time_recovery { + enabled = var.point_in_time_recovery_enabled + } + + dynamic "attribute" { + for_each = var.attributes + + content { + name = attribute.value.name + type = attribute.value.type + } + } + + dynamic "local_secondary_index" { + for_each = var.local_secondary_indexes + + content { + name = local_secondary_index.value.name + range_key = local_secondary_index.value.range_key + projection_type = local_secondary_index.value.projection_type + non_key_attributes = lookup(local_secondary_index.value, "non_key_attributes", null) + } + } + + dynamic "global_secondary_index" { + for_each = var.global_secondary_indexes + + content { + name = global_secondary_index.value.name + hash_key = global_secondary_index.value.hash_key + projection_type = global_secondary_index.value.projection_type + range_key = lookup(global_secondary_index.value, "range_key", null) + read_capacity = lookup(global_secondary_index.value, "read_capacity", null) + write_capacity = lookup(global_secondary_index.value, "write_capacity", null) + non_key_attributes = lookup(global_secondary_index.value, "non_key_attributes", null) + } + } + + dynamic "replica" { + for_each = var.replica_regions + + content { + region_name = replica.value.region_name + kms_key_arn = lookup(replica.value, "kms_key_arn", null) + propagate_tags = lookup(replica.value, "propagate_tags", null) + point_in_time_recovery = lookup(replica.value, "point_in_time_recovery", null) + } + } + + server_side_encryption { + enabled = var.server_side_encryption_enabled + kms_key_arn = var.server_side_encryption_kms_key_arn + } + + tags = merge( + var.tags, + { + "Name" = format("%s", var.name) + }, + ) + + timeouts { + create = lookup(var.timeouts, "create", null) + delete = lookup(var.timeouts, "delete", null) + update = lookup(var.timeouts, "update", null) + } +} + +resource "aws_dynamodb_table" "autoscaled" { + count = var.create_table && var.autoscaling_enabled ? 1 : 0 + + name = var.name + billing_mode = var.billing_mode + hash_key = var.hash_key + range_key = var.range_key + read_capacity = var.read_capacity + write_capacity = var.write_capacity + stream_enabled = var.stream_enabled + stream_view_type = var.stream_view_type + table_class = var.table_class + + ttl { + enabled = var.ttl_enabled + attribute_name = var.ttl_attribute_name + } + + point_in_time_recovery { + enabled = var.point_in_time_recovery_enabled + } + + dynamic "attribute" { + for_each = var.attributes + + content { + name = attribute.value.name + type = attribute.value.type + } + } + + dynamic "local_secondary_index" { + for_each = var.local_secondary_indexes + + content { + name = local_secondary_index.value.name + range_key = local_secondary_index.value.range_key + projection_type = local_secondary_index.value.projection_type + non_key_attributes = lookup(local_secondary_index.value, "non_key_attributes", null) + } + } + + dynamic "global_secondary_index" { + for_each = var.global_secondary_indexes + + content { + name = global_secondary_index.value.name + hash_key = global_secondary_index.value.hash_key + projection_type = global_secondary_index.value.projection_type + range_key = lookup(global_secondary_index.value, "range_key", null) + read_capacity = lookup(global_secondary_index.value, "read_capacity", null) + write_capacity = lookup(global_secondary_index.value, "write_capacity", null) + non_key_attributes = lookup(global_secondary_index.value, "non_key_attributes", null) + } + } + + dynamic "replica" { + for_each = var.replica_regions + + content { + region_name = replica.value.region_name + kms_key_arn = lookup(replica.value, "kms_key_arn", null) + propagate_tags = lookup(replica.value, "propagate_tags", null) + point_in_time_recovery = lookup(replica.value, "point_in_time_recovery", null) + } + } + + server_side_encryption { + enabled = var.server_side_encryption_enabled + kms_key_arn = var.server_side_encryption_kms_key_arn + } + + tags = merge( + var.tags, + { + "Name" = format("%s", var.name) + }, + ) + + timeouts { + create = lookup(var.timeouts, "create", null) + delete = lookup(var.timeouts, "delete", null) + update = lookup(var.timeouts, "update", null) + } + + lifecycle { + ignore_changes = [read_capacity, write_capacity] + } +} diff --git a/modules/aws-dynamodb-table/outputs.tf b/modules/aws-dynamodb-table/outputs.tf new file mode 100644 index 0000000..310cfc5 --- /dev/null +++ b/modules/aws-dynamodb-table/outputs.tf @@ -0,0 +1,19 @@ +output "dynamodb_table_arn" { + description = "ARN of the DynamoDB table" + value = try(aws_dynamodb_table.this[0].arn, aws_dynamodb_table.autoscaled[0].arn, "") +} + +output "dynamodb_table_id" { + description = "ID of the DynamoDB table" + value = try(aws_dynamodb_table.this[0].id, aws_dynamodb_table.autoscaled[0].id, "") +} + +output "dynamodb_table_stream_arn" { + description = "The ARN of the Table Stream. Only available when var.stream_enabled is true" + value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_arn, aws_dynamodb_table.autoscaled[0].stream_arn, "") : null +} + +output "dynamodb_table_stream_label" { + description = "A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream_enabled is true" + value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_label, aws_dynamodb_table.autoscaled[0].stream_label, "") : null +} diff --git a/modules/aws-dynamodb-table/variables.tf b/modules/aws-dynamodb-table/variables.tf new file mode 100644 index 0000000..0df33ac --- /dev/null +++ b/modules/aws-dynamodb-table/variables.tf @@ -0,0 +1,163 @@ +variable "create_table" { + description = "Controls if DynamoDB table and associated resources are created" + type = bool + default = true +} + +variable "name" { + description = "Name of the DynamoDB table" + type = string + default = null +} + +variable "attributes" { + description = "List of nested attribute definitions. Only required for hash_key and range_key attributes. Each attribute has two properties: name - (Required) The name of the attribute, type - (Required) Attribute type, which must be a scalar type: S, N, or B for (S)tring, (N)umber or (B)inary data" + type = list(map(string)) + default = [] +} + +variable "hash_key" { + description = "The attribute to use as the hash (partition) key. Must also be defined as an attribute" + type = string + default = null +} + +variable "range_key" { + description = "The attribute to use as the range (sort) key. Must also be defined as an attribute" + type = string + default = null +} + +variable "billing_mode" { + description = "Controls how you are billed for read/write throughput and how you manage capacity. The valid values are PROVISIONED or PAY_PER_REQUEST" + type = string + default = "PAY_PER_REQUEST" +} + +variable "write_capacity" { + description = "The number of write units for this table. If the billing_mode is PROVISIONED, this field should be greater than 0" + type = number + default = null +} + +variable "read_capacity" { + description = "The number of read units for this table. If the billing_mode is PROVISIONED, this field should be greater than 0" + type = number + default = null +} + +variable "point_in_time_recovery_enabled" { + description = "Whether to enable point-in-time recovery" + type = bool + default = false +} + +variable "ttl_enabled" { + description = "Indicates whether ttl is enabled" + type = bool + default = false +} + +variable "ttl_attribute_name" { + description = "The name of the table attribute to store the TTL timestamp in" + type = string + default = "" +} + +variable "global_secondary_indexes" { + description = "Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc." + type = any + default = [] +} + +variable "local_secondary_indexes" { + description = "Describe an LSI on the table; these can only be allocated at creation so you cannot change this definition after you have created the resource." + type = any + default = [] +} + +variable "replica_regions" { + description = "Region names for creating replicas for a global DynamoDB table." + type = any + default = [] +} + +variable "stream_enabled" { + description = "Indicates whether Streams are to be enabled (true) or disabled (false)." + type = bool + default = false +} + +variable "stream_view_type" { + description = "When an item in the table is modified, StreamViewType determines what information is written to the table's stream. Valid values are KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES." + type = string + default = null +} + +variable "server_side_encryption_enabled" { + description = "Whether or not to enable encryption at rest using an AWS managed KMS customer master key (CMK)" + type = bool + default = false +} + +variable "server_side_encryption_kms_key_arn" { + description = "The ARN of the CMK that should be used for the AWS KMS encryption. This attribute should only be specified if the key is different from the default DynamoDB CMK, alias/aws/dynamodb." + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "timeouts" { + description = "Updated Terraform resource management timeouts" + type = map(string) + default = { + create = "10m" + update = "60m" + delete = "10m" + } +} + +variable "autoscaling_enabled" { + description = "Whether or not to enable autoscaling. See note in README about this setting" + type = bool + default = false +} + +variable "autoscaling_defaults" { + description = "A map of default autoscaling settings" + type = map(string) + default = { + scale_in_cooldown = 0 + scale_out_cooldown = 0 + target_value = 70 + } +} + +variable "autoscaling_read" { + description = "A map of read autoscaling settings. `max_capacity` is the only required key. See example in examples/autoscaling" + type = map(string) + default = {} +} + +variable "autoscaling_write" { + description = "A map of write autoscaling settings. `max_capacity` is the only required key. See example in examples/autoscaling" + type = map(string) + default = {} +} + +variable "autoscaling_indexes" { + description = "A map of index autoscaling configurations. See example in examples/autoscaling" + type = map(map(string)) + default = {} +} + +variable "table_class" { + description = "The storage class of the table. Valid values are STANDARD and STANDARD_INFREQUENT_ACCESS" + type = string + default = null +} diff --git a/modules/aws-dynamodb-table/versions.tf b/modules/aws-dynamodb-table/versions.tf new file mode 100644 index 0000000..00dcc2d --- /dev/null +++ b/modules/aws-dynamodb-table/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.23" + } + } +} diff --git a/modules/aws-dynamodb-table/wrappers/README.md b/modules/aws-dynamodb-table/wrappers/README.md new file mode 100644 index 0000000..940634f --- /dev/null +++ b/modules/aws-dynamodb-table/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/dynamodb-table/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-dynamodb-table.git?ref=master//wrappers" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/dynamodb-table/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-dynamodb-table/wrappers/main.tf b/modules/aws-dynamodb-table/wrappers/main.tf new file mode 100644 index 0000000..407f4fa --- /dev/null +++ b/modules/aws-dynamodb-table/wrappers/main.tf @@ -0,0 +1,40 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create_table = try(each.value.create_table, var.defaults.create_table, true) + name = try(each.value.name, var.defaults.name, null) + attributes = try(each.value.attributes, var.defaults.attributes, []) + hash_key = try(each.value.hash_key, var.defaults.hash_key, null) + range_key = try(each.value.range_key, var.defaults.range_key, null) + billing_mode = try(each.value.billing_mode, var.defaults.billing_mode, "PAY_PER_REQUEST") + write_capacity = try(each.value.write_capacity, var.defaults.write_capacity, null) + read_capacity = try(each.value.read_capacity, var.defaults.read_capacity, null) + point_in_time_recovery_enabled = try(each.value.point_in_time_recovery_enabled, var.defaults.point_in_time_recovery_enabled, false) + ttl_enabled = try(each.value.ttl_enabled, var.defaults.ttl_enabled, false) + ttl_attribute_name = try(each.value.ttl_attribute_name, var.defaults.ttl_attribute_name, "") + global_secondary_indexes = try(each.value.global_secondary_indexes, var.defaults.global_secondary_indexes, []) + local_secondary_indexes = try(each.value.local_secondary_indexes, var.defaults.local_secondary_indexes, []) + replica_regions = try(each.value.replica_regions, var.defaults.replica_regions, []) + stream_enabled = try(each.value.stream_enabled, var.defaults.stream_enabled, false) + stream_view_type = try(each.value.stream_view_type, var.defaults.stream_view_type, null) + server_side_encryption_enabled = try(each.value.server_side_encryption_enabled, var.defaults.server_side_encryption_enabled, false) + server_side_encryption_kms_key_arn = try(each.value.server_side_encryption_kms_key_arn, var.defaults.server_side_encryption_kms_key_arn, null) + tags = try(each.value.tags, var.defaults.tags, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, { + create = "10m" + update = "60m" + delete = "10m" + }) + autoscaling_enabled = try(each.value.autoscaling_enabled, var.defaults.autoscaling_enabled, false) + autoscaling_defaults = try(each.value.autoscaling_defaults, var.defaults.autoscaling_defaults, { + scale_in_cooldown = 0 + scale_out_cooldown = 0 + target_value = 70 + }) + autoscaling_read = try(each.value.autoscaling_read, var.defaults.autoscaling_read, {}) + autoscaling_write = try(each.value.autoscaling_write, var.defaults.autoscaling_write, {}) + autoscaling_indexes = try(each.value.autoscaling_indexes, var.defaults.autoscaling_indexes, {}) + table_class = try(each.value.table_class, var.defaults.table_class, null) +} diff --git a/modules/aws-dynamodb-table/wrappers/outputs.tf b/modules/aws-dynamodb-table/wrappers/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/aws-dynamodb-table/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-dynamodb-table/wrappers/variables.tf b/modules/aws-dynamodb-table/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-dynamodb-table/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-dynamodb-table/wrappers/versions.tf b/modules/aws-dynamodb-table/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-dynamodb-table/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-ec2-instance/README.md b/modules/aws-ec2-instance/README.md new file mode 100644 index 0000000..4c01414 --- /dev/null +++ b/modules/aws-ec2-instance/README.md @@ -0,0 +1,297 @@ +# AWS EC2 Instance Terraform module + +Terraform module which creates an EC2 instance on AWS. + +## Usage + +### Single EC2 Instance + +```hcl +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + + name = "single-instance" + + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + vpc_security_group_ids = ["sg-12345678"] + subnet_id = "subnet-eddcdzz4" + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +### Multiple EC2 Instance + +```hcl +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + + for_each = toset(["one", "two", "three"]) + + name = "instance-${each.key}" + + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + vpc_security_group_ids = ["sg-12345678"] + subnet_id = "subnet-eddcdzz4" + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +### Spot EC2 Instance + +```hcl +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + + name = "spot-instance" + + create_spot_instance = true + spot_price = "0.60" + spot_type = "persistent" + + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + vpc_security_group_ids = ["sg-12345678"] + subnet_id = "subnet-eddcdzz4" + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +## Module wrappers + +Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13. + +Users of Terragrunt can achieve similar results by using modules provided in the [wrappers](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/wrappers) directory, if they prefer to reduce amount of configuration files. + +## Examples + +- [Complete EC2 instance](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) +- [EC2 instance with EBS volume attachment](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment) + +## Make an encrypted AMI for use + +This module does not support encrypted AMI's out of the box however it is easy enough for you to generate one for use + +This example creates an encrypted image from the latest ubuntu 16.04 base image. + +```hcl +provider "aws" { + region = "us-west-2" +} + +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"] + } +} + +resource "aws_ami_copy" "ubuntu_encrypted_ami" { + name = "ubuntu-encrypted-ami" + description = "An encrypted root ami based off ${data.aws_ami.ubuntu.id}" + source_ami_id = data.aws_ami.ubuntu.id + source_ami_region = "eu-west-2" + encrypted = true + + tags = { Name = "ubuntu-encrypted-ami" } +} + +data "aws_ami" "encrypted-ami" { + most_recent = true + + filter { + name = "name" + values = [aws_ami_copy.ubuntu_encrypted_ami.id] + } + + owners = ["self"] +} +``` + +## Conditional creation + +The following combinations are supported to conditionally create resources: + +- Disable resource creation (no resources created): + +```hcl + create = false +``` + +- Create spot instance: + +```hcl + create_spot_instance = true +``` + +## Notes + +- `network_interface` can't be specified together with `vpc_security_group_ids`, `associate_public_ip_address`, `subnet_id`. See [complete example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) for details. +- Changes in `ebs_block_device` argument will be ignored. Use [aws_volume_attachment](https://www.terraform.io/docs/providers/aws/r/volume_attachment.html) resource to attach and detach volumes from AWS EC2 instances. See [this example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment). +- In regards to spot instances, you must grant the `AWSServiceRoleForEC2Spot` service-linked role access to any custom KMS keys, otherwise your spot request and instances will fail with `bad parameters`. You can see more details about why the request failed by using the awscli and `aws ec2 describe-spot-instance-requests` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_instance.ignore_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | +| [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | +| [aws_spot_instance_request.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request) | resource | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_ssm_parameter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ami](#input\_ami) | ID of AMI to use for the instance | `string` | `null` | no | +| [ami\_ssm\_parameter](#input\_ami\_ssm\_parameter) | SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html) | `string` | `"/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"` | no | +| [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Whether to associate a public IP address with an instance in a VPC | `bool` | `null` | no | +| [availability\_zone](#input\_availability\_zone) | AZ to start the instance in | `string` | `null` | no | +| [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Describes an instance's Capacity Reservation targeting option | `any` | `{}` | no | +| [cpu\_core\_count](#input\_cpu\_core\_count) | Sets the number of CPU cores for an instance | `number` | `null` | no | +| [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no | +| [cpu\_options](#input\_cpu\_options) | Defines CPU options to apply to the instance at launch time. | `any` | `{}` | no | +| [cpu\_threads\_per\_core](#input\_cpu\_threads\_per\_core) | Sets the number of CPU threads per core for an instance (has no effect unless cpu\_core\_count is also set) | `number` | `null` | no | +| [create](#input\_create) | Whether to create an instance | `bool` | `true` | no | +| [create\_iam\_instance\_profile](#input\_create\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `false` | no | +| [create\_spot\_instance](#input\_create\_spot\_instance) | Depicts if the instance is a spot instance | `bool` | `false` | no | +| [disable\_api\_stop](#input\_disable\_api\_stop) | If true, enables EC2 Instance Stop Protection | `bool` | `null` | no | +| [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no | +| [ebs\_block\_device](#input\_ebs\_block\_device) | Additional EBS block devices to attach to the instance | `list(any)` | `[]` | no | +| [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | +| [enable\_volume\_tags](#input\_enable\_volume\_tags) | Whether to enable volume tags (if enabled it conflicts with root\_block\_device tags) | `bool` | `true` | no | +| [enclave\_options\_enabled](#input\_enclave\_options\_enabled) | Whether Nitro Enclaves will be enabled on the instance. Defaults to `false` | `bool` | `null` | no | +| [ephemeral\_block\_device](#input\_ephemeral\_block\_device) | Customize Ephemeral (also known as Instance Store) volumes on the instance | `list(map(string))` | `[]` | no | +| [get\_password\_data](#input\_get\_password\_data) | If true, wait for password data to become available and retrieve it | `bool` | `null` | no | +| [hibernation](#input\_hibernation) | If true, the launched EC2 instance will support hibernation | `bool` | `null` | no | +| [host\_id](#input\_host\_id) | ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host | `string` | `null` | no | +| [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_policies](#input\_iam\_role\_policies) | Policies attached to the IAM role | `map(string)` | `{}` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role/profile created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name` or `name`) is used as a prefix | `bool` | `true` | no | +| [ignore\_ami\_changes](#input\_ignore\_ami\_changes) | Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance | `bool` | `false` | no | +| [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Shutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instance | `string` | `null` | no | +| [instance\_tags](#input\_instance\_tags) | Additional tags for the instance | `map(string)` | `{}` | no | +| [instance\_type](#input\_instance\_type) | The type of instance to start | `string` | `"t3.micro"` | no | +| [ipv6\_address\_count](#input\_ipv6\_address\_count) | A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet | `number` | `null` | no | +| [ipv6\_addresses](#input\_ipv6\_addresses) | Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface | `list(string)` | `null` | no | +| [key\_name](#input\_key\_name) | Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource | `string` | `null` | no | +| [launch\_template](#input\_launch\_template) | Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template | `map(string)` | `{}` | no | +| [maintenance\_options](#input\_maintenance\_options) | The maintenance options for the instance | `any` | `{}` | no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance | `map(string)` |
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "optional"
}
| no | +| [monitoring](#input\_monitoring) | If true, the launched EC2 instance will have detailed monitoring enabled | `bool` | `null` | no | +| [name](#input\_name) | Name to be used on EC2 instance created | `string` | `""` | no | +| [network\_interface](#input\_network\_interface) | Customize network interfaces to be attached at instance boot time | `list(map(string))` | `[]` | no | +| [placement\_group](#input\_placement\_group) | The Placement Group to start the instance in | `string` | `null` | no | +| [private\_ip](#input\_private\_ip) | Private IP address to associate with the instance in a VPC | `string` | `null` | no | +| [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance. See Block Devices below for details | `list(any)` | `[]` | no | +| [secondary\_private\_ips](#input\_secondary\_private\_ips) | A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block` | `list(string)` | `null` | no | +| [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `null` | no | +| [spot\_block\_duration\_minutes](#input\_spot\_block\_duration\_minutes) | The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360) | `number` | `null` | no | +| [spot\_instance\_interruption\_behavior](#input\_spot\_instance\_interruption\_behavior) | Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate` | `string` | `null` | no | +| [spot\_launch\_group](#input\_spot\_launch\_group) | A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually | `string` | `null` | no | +| [spot\_price](#input\_spot\_price) | The maximum price to request on the spot market. Defaults to on-demand price | `string` | `null` | no | +| [spot\_type](#input\_spot\_type) | If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent` | `string` | `null` | no | +| [spot\_valid\_from](#input\_spot\_valid\_from) | The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no | +| [spot\_valid\_until](#input\_spot\_valid\_until) | The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no | +| [spot\_wait\_for\_fulfillment](#input\_spot\_wait\_for\_fulfillment) | If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached | `bool` | `null` | no | +| [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [tenancy](#input\_tenancy) | The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host | `string` | `null` | no | +| [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting EC2 instance resources | `map(string)` | `{}` | no | +| [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user\_data\_base64 instead | `string` | `null` | no | +| [user\_data\_base64](#input\_user\_data\_base64) | Can be used instead of user\_data to pass base64-encoded binary data directly. Use this instead of user\_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption | `string` | `null` | no | +| [user\_data\_replace\_on\_change](#input\_user\_data\_replace\_on\_change) | When used in combination with user\_data or user\_data\_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set | `bool` | `null` | no | +| [volume\_tags](#input\_volume\_tags) | A mapping of tags to assign to the devices created by the instance at launch time | `map(string)` | `{}` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | A list of security group IDs to associate with | `list(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [ami](#output\_ami) | AMI ID that was used to create the instance | +| [arn](#output\_arn) | The ARN of the instance | +| [capacity\_reservation\_specification](#output\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ebs\_block\_device](#output\_ebs\_block\_device) | EBS block device information | +| [ephemeral\_block\_device](#output\_ephemeral\_block\_device) | Ephemeral block device information | +| [iam\_instance\_profile\_arn](#output\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | Instance profile's ID | +| [iam\_instance\_profile\_unique](#output\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [iam\_role\_arn](#output\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [id](#output\_id) | The ID of the instance | +| [instance\_state](#output\_instance\_state) | The state of the instance | +| [ipv6\_addresses](#output\_ipv6\_addresses) | The IPv6 address assigned to the instance, if applicable | +| [outpost\_arn](#output\_outpost\_arn) | The ARN of the Outpost the instance is assigned to | +| [password\_data](#output\_password\_data) | Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true | +| [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [private\_dns](#output\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [private\_ip](#output\_private\_ip) | The private IP address assigned to the instance | +| [public\_dns](#output\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [public\_ip](#output\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [root\_block\_device](#output\_root\_block\_device) | Root block device information | +| [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request | +| [spot\_instance\_id](#output\_spot\_instance\_id) | The Instance ID (if any) that is currently fulfilling the Spot Instance request | +| [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request | +| [tags\_all](#output\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/LICENSE) for full details. diff --git a/modules/aws-ec2-instance/examples/README.md b/modules/aws-ec2-instance/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/modules/aws-ec2-instance/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/modules/aws-ec2-instance/examples/complete/README.md b/modules/aws-ec2-instance/examples/complete/README.md new file mode 100644 index 0000000..9fa60b3 --- /dev/null +++ b/modules/aws-ec2-instance/examples/complete/README.md @@ -0,0 +1,121 @@ +# Complete EC2 instance + +Configuration in this directory creates EC2 instances with different sets of arguments (with Elastic IP, with network interface attached, with credit specifications). + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ec2\_complete](#module\_ec2\_complete) | ../../ | n/a | +| [ec2\_cpu\_options](#module\_ec2\_cpu\_options) | ../../ | n/a | +| [ec2\_disabled](#module\_ec2\_disabled) | ../../ | n/a | +| [ec2\_ignore\_ami\_changes](#module\_ec2\_ignore\_ami\_changes) | ../../ | n/a | +| [ec2\_metadata\_options](#module\_ec2\_metadata\_options) | ../../ | n/a | +| [ec2\_multiple](#module\_ec2\_multiple) | ../../ | n/a | +| [ec2\_network\_interface](#module\_ec2\_network\_interface) | ../../ | n/a | +| [ec2\_open\_capacity\_reservation](#module\_ec2\_open\_capacity\_reservation) | ../../ | n/a | +| [ec2\_spot\_instance](#module\_ec2\_spot\_instance) | ../../ | n/a | +| [ec2\_t2\_unlimited](#module\_ec2\_t2\_unlimited) | ../../ | n/a | +| [ec2\_t3\_unlimited](#module\_ec2\_t3\_unlimited) | ../../ | n/a | +| [ec2\_targeted\_capacity\_reservation](#module\_ec2\_targeted\_capacity\_reservation) | ../../ | n/a | +| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_ec2_capacity_reservation.open](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_capacity_reservation) | resource | +| [aws_ec2_capacity_reservation.targeted](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_capacity_reservation) | resource | +| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_network_interface.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_interface) | resource | +| [aws_placement_group.web](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/placement_group) | resource | +| [aws_ami.amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_ami.amazon_linux_23](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [ec2\_complete\_arn](#output\_ec2\_complete\_arn) | The ARN of the instance | +| [ec2\_complete\_capacity\_reservation\_specification](#output\_ec2\_complete\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ec2\_complete\_ebs\_block\_device](#output\_ec2\_complete\_ebs\_block\_device) | EBS block device information | +| [ec2\_complete\_ephemeral\_block\_device](#output\_ec2\_complete\_ephemeral\_block\_device) | Ephemeral block device information | +| [ec2\_complete\_iam\_instance\_profile\_arn](#output\_ec2\_complete\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [ec2\_complete\_iam\_instance\_profile\_id](#output\_ec2\_complete\_iam\_instance\_profile\_id) | Instance profile's ID | +| [ec2\_complete\_iam\_instance\_profile\_unique](#output\_ec2\_complete\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [ec2\_complete\_iam\_role\_arn](#output\_ec2\_complete\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [ec2\_complete\_iam\_role\_name](#output\_ec2\_complete\_iam\_role\_name) | The name of the IAM role | +| [ec2\_complete\_iam\_role\_unique\_id](#output\_ec2\_complete\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [ec2\_complete\_id](#output\_ec2\_complete\_id) | The ID of the instance | +| [ec2\_complete\_instance\_state](#output\_ec2\_complete\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | +| [ec2\_complete\_primary\_network\_interface\_id](#output\_ec2\_complete\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [ec2\_complete\_private\_dns](#output\_ec2\_complete\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [ec2\_complete\_public\_dns](#output\_ec2\_complete\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [ec2\_complete\_public\_ip](#output\_ec2\_complete\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [ec2\_complete\_root\_block\_device](#output\_ec2\_complete\_root\_block\_device) | Root block device information | +| [ec2\_complete\_tags\_all](#output\_ec2\_complete\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | +| [ec2\_ignore\_ami\_changes\_ami](#output\_ec2\_ignore\_ami\_changes\_ami) | The AMI of the instance (ignore\_ami\_changes = true) | +| [ec2\_multiple](#output\_ec2\_multiple) | The full output of the `ec2_module` module | +| [ec2\_spot\_instance\_arn](#output\_ec2\_spot\_instance\_arn) | The ARN of the instance | +| [ec2\_spot\_instance\_capacity\_reservation\_specification](#output\_ec2\_spot\_instance\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ec2\_spot\_instance\_id](#output\_ec2\_spot\_instance\_id) | The ID of the instance | +| [ec2\_spot\_instance\_instance\_state](#output\_ec2\_spot\_instance\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | +| [ec2\_spot\_instance\_primary\_network\_interface\_id](#output\_ec2\_spot\_instance\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [ec2\_spot\_instance\_private\_dns](#output\_ec2\_spot\_instance\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [ec2\_spot\_instance\_public\_dns](#output\_ec2\_spot\_instance\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [ec2\_spot\_instance\_public\_ip](#output\_ec2\_spot\_instance\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [ec2\_spot\_instance\_tags\_all](#output\_ec2\_spot\_instance\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | +| [ec2\_t2\_unlimited\_arn](#output\_ec2\_t2\_unlimited\_arn) | The ARN of the instance | +| [ec2\_t2\_unlimited\_capacity\_reservation\_specification](#output\_ec2\_t2\_unlimited\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ec2\_t2\_unlimited\_id](#output\_ec2\_t2\_unlimited\_id) | The ID of the instance | +| [ec2\_t2\_unlimited\_instance\_state](#output\_ec2\_t2\_unlimited\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | +| [ec2\_t2\_unlimited\_primary\_network\_interface\_id](#output\_ec2\_t2\_unlimited\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [ec2\_t2\_unlimited\_private\_dns](#output\_ec2\_t2\_unlimited\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [ec2\_t2\_unlimited\_public\_dns](#output\_ec2\_t2\_unlimited\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [ec2\_t2\_unlimited\_public\_ip](#output\_ec2\_t2\_unlimited\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [ec2\_t2\_unlimited\_tags\_all](#output\_ec2\_t2\_unlimited\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | +| [ec2\_t3\_unlimited\_arn](#output\_ec2\_t3\_unlimited\_arn) | The ARN of the instance | +| [ec2\_t3\_unlimited\_capacity\_reservation\_specification](#output\_ec2\_t3\_unlimited\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ec2\_t3\_unlimited\_id](#output\_ec2\_t3\_unlimited\_id) | The ID of the instance | +| [ec2\_t3\_unlimited\_instance\_state](#output\_ec2\_t3\_unlimited\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | +| [ec2\_t3\_unlimited\_primary\_network\_interface\_id](#output\_ec2\_t3\_unlimited\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [ec2\_t3\_unlimited\_private\_dns](#output\_ec2\_t3\_unlimited\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [ec2\_t3\_unlimited\_public\_dns](#output\_ec2\_t3\_unlimited\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [ec2\_t3\_unlimited\_public\_ip](#output\_ec2\_t3\_unlimited\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [ec2\_t3\_unlimited\_tags\_all](#output\_ec2\_t3\_unlimited\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | +| [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request | +| [spot\_instance\_id](#output\_spot\_instance\_id) | The Instance ID (if any) that is currently fulfilling the Spot Instance request | +| [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request | + diff --git a/modules/aws-ec2-instance/examples/complete/main.tf b/modules/aws-ec2-instance/examples/complete/main.tf new file mode 100644 index 0000000..25baae5 --- /dev/null +++ b/modules/aws-ec2-instance/examples/complete/main.tf @@ -0,0 +1,485 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + name = "ex-${basename(path.cwd)}" + region = "eu-west-1" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + user_data = <<-EOT + #!/bin/bash + echo "Hello Terraform!" + EOT + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ec2-instance" + } +} + +################################################################################ +# EC2 Module +################################################################################ + +module "ec2_complete" { + source = "../../" + + name = local.name + + ami = data.aws_ami.amazon_linux.id + instance_type = "c5.xlarge" # used to set core count below + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + placement_group = aws_placement_group.web.id + associate_public_ip_address = true + disable_api_stop = false + + create_iam_instance_profile = true + iam_role_description = "IAM role for EC2 instance" + iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + + # only one of these can be enabled at a time + hibernation = true + # enclave_options_enabled = true + + user_data_base64 = base64encode(local.user_data) + user_data_replace_on_change = true + + cpu_options = { + core_count = 2 + threads_per_core = 1 + } + enable_volume_tags = false + root_block_device = [ + { + encrypted = true + volume_type = "gp3" + throughput = 200 + volume_size = 50 + tags = { + Name = "my-root-block" + } + }, + ] + + ebs_block_device = [ + { + device_name = "/dev/sdf" + volume_type = "gp3" + volume_size = 5 + throughput = 200 + encrypted = true + kms_key_id = aws_kms_key.this.arn + tags = { + MountPoint = "/mnt/data" + } + } + ] + + tags = local.tags +} + +module "ec2_network_interface" { + source = "../../" + + name = "${local.name}-network-interface" + + network_interface = [ + { + device_index = 0 + network_interface_id = aws_network_interface.this.id + delete_on_termination = false + } + ] + + tags = local.tags +} + +module "ec2_metadata_options" { + source = "../../" + + name = "${local.name}-metadata-options" + + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + + metadata_options = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 8 + instance_metadata_tags = "enabled" + } + + tags = local.tags +} + +module "ec2_t2_unlimited" { + source = "../../" + + name = "${local.name}-t2-unlimited" + + instance_type = "t2.micro" + cpu_credits = "unlimited" + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + maintenance_options = { + auto_recovery = "default" + } + + tags = local.tags +} + +module "ec2_t3_unlimited" { + source = "../../" + + name = "${local.name}-t3-unlimited" + + instance_type = "t3.micro" + cpu_credits = "unlimited" + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + tags = local.tags +} + +module "ec2_disabled" { + source = "../../" + + create = false +} + +################################################################################ +# EC2 Module - with ignore AMI changes +################################################################################ + +module "ec2_ignore_ami_changes" { + source = "../../" + + name = local.name + + ignore_ami_changes = true + + ami = data.aws_ami.amazon_linux.id + instance_type = "t2.micro" + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + + tags = local.tags +} + +################################################################################ +# EC2 Module - multiple instances with `for_each` +################################################################################ + +locals { + multiple_instances = { + one = { + instance_type = "t3.micro" + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + root_block_device = [ + { + encrypted = true + volume_type = "gp3" + throughput = 200 + volume_size = 50 + tags = { + Name = "my-root-block" + } + } + ] + } + two = { + instance_type = "t3.small" + availability_zone = element(module.vpc.azs, 1) + subnet_id = element(module.vpc.private_subnets, 1) + root_block_device = [ + { + encrypted = true + volume_type = "gp2" + volume_size = 50 + } + ] + } + three = { + instance_type = "t3.medium" + availability_zone = element(module.vpc.azs, 2) + subnet_id = element(module.vpc.private_subnets, 2) + } + } +} + +module "ec2_multiple" { + source = "../../" + + for_each = local.multiple_instances + + name = "${local.name}-multi-${each.key}" + + instance_type = each.value.instance_type + availability_zone = each.value.availability_zone + subnet_id = each.value.subnet_id + vpc_security_group_ids = [module.security_group.security_group_id] + + enable_volume_tags = false + root_block_device = lookup(each.value, "root_block_device", []) + + tags = local.tags +} + +################################################################################ +# EC2 Module - spot instance request +################################################################################ + +module "ec2_spot_instance" { + source = "../../" + + name = "${local.name}-spot-instance" + create_spot_instance = true + + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + # Spot request specific attributes + spot_price = "0.1" + spot_wait_for_fulfillment = true + spot_type = "persistent" + spot_instance_interruption_behavior = "terminate" + # End spot request specific attributes + + user_data_base64 = base64encode(local.user_data) + + cpu_options = { + core_count = 2 + threads_per_core = 1 + } + + enable_volume_tags = false + root_block_device = [ + { + encrypted = true + volume_type = "gp3" + throughput = 200 + volume_size = 50 + tags = { + Name = "my-root-block" + } + }, + ] + + ebs_block_device = [ + { + device_name = "/dev/sdf" + volume_type = "gp3" + volume_size = 5 + throughput = 200 + encrypted = true + # kms_key_id = aws_kms_key.this.arn # you must grant the AWSServiceRoleForEC2Spot service-linked role access to any custom KMS keys + } + ] + + tags = local.tags +} + +################################################################################ +# EC2 Module - Capacity Reservation +################################################################################ + +module "ec2_open_capacity_reservation" { + source = "../../" + + name = "${local.name}-open-capacity-reservation" + + ami = data.aws_ami.amazon_linux.id + instance_type = "t3.micro" + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = false + + capacity_reservation_specification = { + capacity_reservation_target = { + capacity_reservation_id = aws_ec2_capacity_reservation.open.id + } + } + + tags = local.tags +} + +module "ec2_targeted_capacity_reservation" { + source = "../../" + + name = "${local.name}-targeted-capacity-reservation" + + ami = data.aws_ami.amazon_linux.id + instance_type = "t3.micro" + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = false + + capacity_reservation_specification = { + capacity_reservation_target = { + capacity_reservation_id = aws_ec2_capacity_reservation.targeted.id + } + } + + tags = local.tags +} + +resource "aws_ec2_capacity_reservation" "open" { + instance_type = "t3.micro" + instance_platform = "Linux/UNIX" + availability_zone = "${local.region}a" + instance_count = 1 + instance_match_criteria = "open" +} + +resource "aws_ec2_capacity_reservation" "targeted" { + instance_type = "t3.micro" + instance_platform = "Linux/UNIX" + availability_zone = "${local.region}a" + instance_count = 1 + instance_match_criteria = "targeted" +} + +################################################################################ +# EC2 Module - CPU Options +################################################################################ +module "ec2_cpu_options" { + source = "../../" + + name = "${local.name}-cpu-options" + + ami = data.aws_ami.amazon_linux_23.id + instance_type = "c6a.xlarge" # used to set core count below and test amd_sev_snp attribute + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + placement_group = aws_placement_group.web.id + associate_public_ip_address = true + disable_api_stop = false + + create_iam_instance_profile = true + iam_role_description = "IAM role for EC2 instance" + iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + + user_data_base64 = base64encode(local.user_data) + user_data_replace_on_change = true + + cpu_options = { + core_count = 2 + threads_per_core = 1 + amd_sev_snp = "enabled" + } + enable_volume_tags = false + root_block_device = [ + { + encrypted = true + volume_type = "gp3" + throughput = 200 + volume_size = 50 + tags = { + Name = "my-root-block" + } + }, + ] + + ebs_block_device = [ + { + device_name = "/dev/sdf" + volume_type = "gp3" + volume_size = 5 + throughput = 200 + encrypted = true + kms_key_id = aws_kms_key.this.arn + tags = { + MountPoint = "/mnt/data" + } + } + ] + + instance_tags = { Persistence = "09:00-18:00" } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + tags = local.tags +} + +data "aws_ami" "amazon_linux" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +data "aws_ami" "amazon_linux_23" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["al2023-ami-2023*-x86_64"] + } +} + +module "security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = local.name + description = "Security group for example usage with EC2 instance" + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["http-80-tcp", "all-icmp"] + egress_rules = ["all-all"] + + tags = local.tags +} + +resource "aws_placement_group" "web" { + name = local.name + strategy = "cluster" +} + +resource "aws_kms_key" "this" { +} + +resource "aws_network_interface" "this" { + subnet_id = element(module.vpc.private_subnets, 0) +} diff --git a/modules/aws-ec2-instance/examples/complete/outputs.tf b/modules/aws-ec2-instance/examples/complete/outputs.tf new file mode 100644 index 0000000..1ce36b8 --- /dev/null +++ b/modules/aws-ec2-instance/examples/complete/outputs.tf @@ -0,0 +1,255 @@ +# EC2 Complete +output "ec2_complete_id" { + description = "The ID of the instance" + value = module.ec2_complete.id +} + +output "ec2_complete_arn" { + description = "The ARN of the instance" + value = module.ec2_complete.arn +} + +output "ec2_complete_capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2_complete.capacity_reservation_specification +} + +output "ec2_complete_instance_state" { + description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" + value = module.ec2_complete.instance_state +} + +output "ec2_complete_primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2_complete.primary_network_interface_id +} + +output "ec2_complete_private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_complete.private_dns +} + +output "ec2_complete_public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_complete.public_dns +} + +output "ec2_complete_public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = module.ec2_complete.public_ip +} + +output "ec2_complete_tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2_complete.tags_all +} + +output "ec2_complete_iam_role_name" { + description = "The name of the IAM role" + value = module.ec2_complete.iam_role_name +} + +output "ec2_complete_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ec2_complete.iam_role_arn +} + +output "ec2_complete_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ec2_complete.iam_role_unique_id +} + +output "ec2_complete_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ec2_complete.iam_instance_profile_arn +} + +output "ec2_complete_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ec2_complete.iam_instance_profile_id +} + +output "ec2_complete_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ec2_complete.iam_instance_profile_unique +} + +output "ec2_complete_root_block_device" { + description = "Root block device information" + value = module.ec2_complete.root_block_device +} + +output "ec2_complete_ebs_block_device" { + description = "EBS block device information" + value = module.ec2_complete.ebs_block_device +} + +output "ec2_complete_ephemeral_block_device" { + description = "Ephemeral block device information" + value = module.ec2_complete.ephemeral_block_device +} + +# EC2 T2 Unlimited +output "ec2_t2_unlimited_id" { + description = "The ID of the instance" + value = module.ec2_t2_unlimited.id +} + +output "ec2_t2_unlimited_arn" { + description = "The ARN of the instance" + value = module.ec2_t2_unlimited.arn +} + +output "ec2_t2_unlimited_capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2_t2_unlimited.capacity_reservation_specification +} + +output "ec2_t2_unlimited_instance_state" { + description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" + value = module.ec2_t2_unlimited.instance_state +} + +output "ec2_t2_unlimited_primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2_t2_unlimited.primary_network_interface_id +} + +output "ec2_t2_unlimited_private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_t2_unlimited.private_dns +} + +output "ec2_t2_unlimited_public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_t2_unlimited.public_dns +} + +output "ec2_t2_unlimited_public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = module.ec2_t2_unlimited.public_ip +} + +output "ec2_t2_unlimited_tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2_t2_unlimited.tags_all +} + +# EC2 T3 Unlimited +output "ec2_t3_unlimited_id" { + description = "The ID of the instance" + value = module.ec2_t3_unlimited.id +} + +output "ec2_t3_unlimited_arn" { + description = "The ARN of the instance" + value = module.ec2_t3_unlimited.arn +} + +output "ec2_t3_unlimited_capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2_t3_unlimited.capacity_reservation_specification +} + +output "ec2_t3_unlimited_instance_state" { + description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" + value = module.ec2_t3_unlimited.instance_state +} + +output "ec2_t3_unlimited_primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2_t3_unlimited.primary_network_interface_id +} + +output "ec2_t3_unlimited_private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_t3_unlimited.private_dns +} + +output "ec2_t3_unlimited_public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_t3_unlimited.public_dns +} + +output "ec2_t3_unlimited_public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = module.ec2_t3_unlimited.public_ip +} + +output "ec2_t3_unlimited_tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2_t3_unlimited.tags_all +} + +# EC2 with ignore AMI changes +output "ec2_ignore_ami_changes_ami" { + description = "The AMI of the instance (ignore_ami_changes = true)" + value = module.ec2_ignore_ami_changes.ami +} + +# EC2 Multiple +output "ec2_multiple" { + description = "The full output of the `ec2_module` module" + value = module.ec2_multiple +} + +# EC2 Spot Instance +output "ec2_spot_instance_id" { + description = "The ID of the instance" + value = module.ec2_spot_instance.id +} + +output "ec2_spot_instance_arn" { + description = "The ARN of the instance" + value = module.ec2_spot_instance.arn +} + +output "ec2_spot_instance_capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2_spot_instance.capacity_reservation_specification +} + +output "ec2_spot_instance_instance_state" { + description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" + value = module.ec2_spot_instance.instance_state +} + +output "ec2_spot_instance_primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2_spot_instance.primary_network_interface_id +} + +output "ec2_spot_instance_private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_spot_instance.private_dns +} + +output "ec2_spot_instance_public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_spot_instance.public_dns +} + +output "ec2_spot_instance_public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = module.ec2_spot_instance.public_ip +} + +output "ec2_spot_instance_tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2_spot_instance.tags_all +} + +output "spot_bid_status" { + description = "The current bid status of the Spot Instance Request" + value = module.ec2_spot_instance.spot_bid_status +} + +output "spot_request_state" { + description = "The current request state of the Spot Instance Request" + value = module.ec2_spot_instance.spot_request_state +} + +output "spot_instance_id" { + description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request" + value = module.ec2_spot_instance.spot_instance_id +} diff --git a/modules/aws-ec2-instance/examples/complete/variables.tf b/modules/aws-ec2-instance/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ec2-instance/examples/complete/versions.tf b/modules/aws-ec2-instance/examples/complete/versions.tf new file mode 100644 index 0000000..fd4d116 --- /dev/null +++ b/modules/aws-ec2-instance/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66" + } + } +} diff --git a/modules/aws-ec2-instance/examples/volume-attachment/README.md b/modules/aws-ec2-instance/examples/volume-attachment/README.md new file mode 100644 index 0000000..adede89 --- /dev/null +++ b/modules/aws-ec2-instance/examples/volume-attachment/README.md @@ -0,0 +1,67 @@ +# EC2 instance with EBS volume attachment + +Configuration in this directory creates EC2 instances, EBS volume and attach it together. + +This example outputs instance id and EBS volume id. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.20 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.20 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ec2](#module\_ec2) | ../../ | n/a | +| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_ebs_volume.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume) | resource | +| [aws_volume_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment) | resource | +| [aws_ami.amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [ec2\_arn](#output\_ec2\_arn) | The ARN of the instance | +| [ec2\_capacity\_reservation\_specification](#output\_ec2\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ec2\_id](#output\_ec2\_id) | The ID of the instance | +| [ec2\_instance\_state](#output\_ec2\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | +| [ec2\_primary\_network\_interface\_id](#output\_ec2\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [ec2\_private\_dns](#output\_ec2\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [ec2\_public\_dns](#output\_ec2\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [ec2\_public\_ip](#output\_ec2\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | +| [ec2\_tags\_all](#output\_ec2\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | + diff --git a/modules/aws-ec2-instance/examples/volume-attachment/main.tf b/modules/aws-ec2-instance/examples/volume-attachment/main.tf new file mode 100644 index 0000000..f201cad --- /dev/null +++ b/modules/aws-ec2-instance/examples/volume-attachment/main.tf @@ -0,0 +1,94 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + name = "ex-${basename(path.cwd)}" + region = "eu-west-1" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ec2-instance" + } +} + +################################################################################ +# EC2 Module +################################################################################ + +module "ec2" { + source = "../../" + + name = local.name + + ami = data.aws_ami.amazon_linux.id + instance_type = "c5.large" + availability_zone = element(local.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + tags = local.tags +} + +resource "aws_volume_attachment" "this" { + device_name = "/dev/sdh" + volume_id = aws_ebs_volume.this.id + instance_id = module.ec2.id +} + +resource "aws_ebs_volume" "this" { + availability_zone = element(local.azs, 0) + size = 1 + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 4.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + tags = local.tags +} + +data "aws_ami" "amazon_linux" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +module "security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = local.name + description = "Security group for example usage with EC2 instance" + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["http-80-tcp", "all-icmp"] + egress_rules = ["all-all"] + + tags = local.tags +} diff --git a/modules/aws-ec2-instance/examples/volume-attachment/outputs.tf b/modules/aws-ec2-instance/examples/volume-attachment/outputs.tf new file mode 100644 index 0000000..d9304a3 --- /dev/null +++ b/modules/aws-ec2-instance/examples/volume-attachment/outputs.tf @@ -0,0 +1,45 @@ +# EC2 +output "ec2_id" { + description = "The ID of the instance" + value = module.ec2.id +} + +output "ec2_arn" { + description = "The ARN of the instance" + value = module.ec2.arn +} + +output "ec2_capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2.capacity_reservation_specification +} + +output "ec2_instance_state" { + description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" + value = module.ec2.instance_state +} + +output "ec2_primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2.primary_network_interface_id +} + +output "ec2_private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2.private_dns +} + +output "ec2_public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2.public_dns +} + +output "ec2_public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = module.ec2.public_ip +} + +output "ec2_tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2.tags_all +} diff --git a/modules/aws-ec2-instance/examples/volume-attachment/variables.tf b/modules/aws-ec2-instance/examples/volume-attachment/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ec2-instance/examples/volume-attachment/versions.tf b/modules/aws-ec2-instance/examples/volume-attachment/versions.tf new file mode 100644 index 0000000..eddf9d5 --- /dev/null +++ b/modules/aws-ec2-instance/examples/volume-attachment/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.20" + } + } +} diff --git a/modules/aws-ec2-instance/main.tf b/modules/aws-ec2-instance/main.tf new file mode 100644 index 0000000..b7548c3 --- /dev/null +++ b/modules/aws-ec2-instance/main.tf @@ -0,0 +1,583 @@ +data "aws_partition" "current" {} + +locals { + create = var.create && var.putin_khuylo + + is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a|4g){1}\\..*$/", "1") == "1" ? true : false +} + +data "aws_ssm_parameter" "this" { + count = local.create ? 1 : 0 + + name = var.ami_ssm_parameter +} + +################################################################################ +# Instance +################################################################################ + +resource "aws_instance" "this" { + count = local.create && !var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + dynamic "maintenance_options" { + for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + + content { + auto_recovery = try(maintenance_options.value.auto_recovery, null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + disable_api_stop = var.disable_api_stop + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null +} + +################################################################################ +# Instance - Ignore AMI Changes +################################################################################ + +resource "aws_instance" "ignore_ami" { + count = local.create && var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + dynamic "maintenance_options" { + for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + + content { + auto_recovery = try(maintenance_options.value.auto_recovery, null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + disable_api_stop = var.disable_api_stop + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null + + lifecycle { + ignore_changes = [ + ami + ] + } +} + +################################################################################ +# Spot Instance +################################################################################ + +resource "aws_spot_instance_request" "this" { + count = local.create && var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + # Spot request specific attributes + spot_price = var.spot_price + wait_for_fulfillment = var.spot_wait_for_fulfillment + spot_type = var.spot_type + launch_group = var.spot_launch_group + block_duration_minutes = var.spot_block_duration_minutes + instance_interruption_behavior = var.spot_instance_interruption_behavior + valid_until = var.spot_valid_until + valid_from = var.spot_valid_from + # End spot request specific attributes + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null +} + +################################################################################ +# IAM Role / Instance Profile +################################################################################ + +locals { + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "assume_role_policy" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + statement { + sid = "EC2AssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in var.iam_role_policies : k => v if var.create && var.create_iam_instance_profile } + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +resource "aws_iam_instance_profile" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + role = aws_iam_role.this[0].name + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + + tags = merge(var.tags, var.iam_role_tags) + + lifecycle { + create_before_destroy = true + } +} diff --git a/modules/aws-ec2-instance/outputs.tf b/modules/aws-ec2-instance/outputs.tf new file mode 100644 index 0000000..d79484c --- /dev/null +++ b/modules/aws-ec2-instance/outputs.tf @@ -0,0 +1,221 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this[0].id, + aws_instance.ignore_ami[0].id, + aws_spot_instance_request.this[0].id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this[0].arn, + aws_instance.ignore_ami[0].arn, + aws_spot_instance_request.this[0].arn, + null, + ) +} + +output "capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = try( + aws_instance.this[0].capacity_reservation_specification, + aws_instance.ignore_ami[0].capacity_reservation_specification, + aws_spot_instance_request.this[0].capacity_reservation_specification, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this[0].instance_state, + aws_instance.ignore_ami[0].instance_state, + aws_spot_instance_request.this[0].instance_state, + null, + ) +} + +output "outpost_arn" { + description = "The ARN of the Outpost the instance is assigned to" + value = try( + aws_instance.this[0].outpost_arn, + aws_instance.ignore_ami[0].outpost_arn, + aws_spot_instance_request.this[0].outpost_arn, + null, + ) +} + +output "password_data" { + description = "Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true" + value = try( + aws_instance.this[0].password_data, + aws_instance.ignore_ami[0].password_data, + aws_spot_instance_request.this[0].password_data, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this[0].primary_network_interface_id, + aws_instance.ignore_ami[0].primary_network_interface_id, + aws_spot_instance_request.this[0].primary_network_interface_id, + null, + ) +} + +output "private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = try( + aws_instance.this[0].private_dns, + aws_instance.ignore_ami[0].private_dns, + aws_spot_instance_request.this[0].private_dns, + null, + ) +} + +output "public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = try( + aws_instance.this[0].public_dns, + aws_instance.ignore_ami[0].public_dns, + aws_spot_instance_request.this[0].public_dns, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this[0].public_ip, + aws_instance.ignore_ami[0].public_ip, + aws_spot_instance_request.this[0].public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this[0].private_ip, + aws_instance.ignore_ami[0].private_ip, + aws_spot_instance_request.this[0].private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this[0].ipv6_addresses, + aws_instance.ignore_ami[0].ipv6_addresses, + aws_spot_instance_request.this[0].ipv6_addresses, + [], + ) +} + +output "tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = try( + aws_instance.this[0].tags_all, + aws_instance.ignore_ami[0].tags_all, + aws_spot_instance_request.this[0].tags_all, + {}, + ) +} + +output "spot_bid_status" { + description = "The current bid status of the Spot Instance Request" + value = try(aws_spot_instance_request.this[0].spot_bid_status, null) +} + +output "spot_request_state" { + description = "The current request state of the Spot Instance Request" + value = try(aws_spot_instance_request.this[0].spot_request_state, null) +} + +output "spot_instance_id" { + description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request" + value = try(aws_spot_instance_request.this[0].spot_instance_id, null) +} + +output "ami" { + description = "AMI ID that was used to create the instance" + value = try( + aws_instance.this[0].ami, + aws_instance.ignore_ami[0].ami, + aws_spot_instance_request.this[0].ami, + null, + ) +} + +################################################################################ +# IAM Role / Instance Profile +################################################################################ + +output "iam_role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, null) +} + +output "iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, null) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, null) +} + +output "iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = try(aws_iam_instance_profile.this[0].arn, null) +} + +output "iam_instance_profile_id" { + description = "Instance profile's ID" + value = try(aws_iam_instance_profile.this[0].id, null) +} + +output "iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = try(aws_iam_instance_profile.this[0].unique_id, null) +} + +################################################################################ +# Block Devices +################################################################################ +output "root_block_device" { + description = "Root block device information" + value = try( + aws_instance.this[0].root_block_device, + aws_instance.ignore_ami[0].root_block_device, + aws_spot_instance_request.this[0].root_block_device, + null + ) +} + +output "ebs_block_device" { + description = "EBS block device information" + value = try( + aws_instance.this[0].ebs_block_device, + aws_instance.ignore_ami[0].ebs_block_device, + aws_spot_instance_request.this[0].ebs_block_device, + null + ) +} + +output "ephemeral_block_device" { + description = "Ephemeral block device information" + value = try( + aws_instance.this[0].ephemeral_block_device, + aws_instance.ignore_ami[0].ephemeral_block_device, + aws_spot_instance_request.this[0].ephemeral_block_device, + null + ) +} diff --git a/modules/aws-ec2-instance/variables.tf b/modules/aws-ec2-instance/variables.tf new file mode 100644 index 0000000..a05e304 --- /dev/null +++ b/modules/aws-ec2-instance/variables.tf @@ -0,0 +1,404 @@ +variable "create" { + description = "Whether to create an instance" + type = bool + default = true +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "ami_ssm_parameter" { + description = "SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html)" + type = string + default = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" +} + +variable "ami" { + description = "ID of AMI to use for the instance" + type = string + default = null +} + +variable "ignore_ami_changes" { + description = "Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance" + type = bool + default = false +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = null +} + +variable "maintenance_options" { + description = "The maintenance options for the instance" + type = any + default = {} +} + +variable "availability_zone" { + description = "AZ to start the instance in" + type = string + default = null +} + +variable "capacity_reservation_specification" { + description = "Describes an instance's Capacity Reservation targeting option" + type = any + default = {} +} + +variable "cpu_credits" { + description = "The credit option for CPU usage (unlimited or standard)" + type = string + default = null +} + +variable "disable_api_termination" { + description = "If true, enables EC2 Instance Termination Protection" + type = bool + default = null +} + +variable "ebs_block_device" { + description = "Additional EBS block devices to attach to the instance" + type = list(any) + default = [] +} + +variable "ebs_optimized" { + description = "If true, the launched EC2 instance will be EBS-optimized" + type = bool + default = null +} + +variable "enclave_options_enabled" { + description = "Whether Nitro Enclaves will be enabled on the instance. Defaults to `false`" + type = bool + default = null +} + +variable "ephemeral_block_device" { + description = "Customize Ephemeral (also known as Instance Store) volumes on the instance" + type = list(map(string)) + default = [] +} + +variable "get_password_data" { + description = "If true, wait for password data to become available and retrieve it" + type = bool + default = null +} + +variable "hibernation" { + description = "If true, the launched EC2 instance will support hibernation" + type = bool + default = null +} + +variable "host_id" { + description = "ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host" + type = string + default = null +} + +variable "iam_instance_profile" { + description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" + type = string + default = null +} + +variable "instance_initiated_shutdown_behavior" { + description = "Shutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instance" # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior + type = string + default = null +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_address_count" { + description = "A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet" + type = number + default = null +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "launch_template" { + description = "Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template" + type = map(string) + default = {} +} + +variable "metadata_options" { + description = "Customize the metadata options of the instance" + type = map(string) + default = { + "http_endpoint" = "enabled" + "http_put_response_hop_limit" = 1 + "http_tokens" = "optional" + } +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = null +} + +variable "network_interface" { + description = "Customize network interfaces to be attached at instance boot time" + type = list(map(string)) + default = [] +} + +variable "placement_group" { + description = "The Placement Group to start the instance in" + type = string + default = null +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "secondary_private_ips" { + description = "A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block`" + type = list(string) + default = null +} + +variable "source_dest_check" { + description = "Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs" + type = bool + default = null +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "tenancy" { + description = "The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host" + type = string + default = null +} + +variable "user_data" { + description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user_data_base64 instead" + type = string + default = null +} + +variable "user_data_base64" { + description = "Can be used instead of user_data to pass base64-encoded binary data directly. Use this instead of user_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption" + type = string + default = null +} + +variable "user_data_replace_on_change" { + description = "When used in combination with user_data or user_data_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set" + type = bool + default = null +} + +variable "volume_tags" { + description = "A mapping of tags to assign to the devices created by the instance at launch time" + type = map(string) + default = {} +} + +variable "enable_volume_tags" { + description = "Whether to enable volume tags (if enabled it conflicts with root_block_device tags)" + type = bool + default = true +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +} + +variable "timeouts" { + description = "Define maximum timeout for creating, updating, and deleting EC2 instance resources" + type = map(string) + default = {} +} + +variable "cpu_options" { + description = "Defines CPU options to apply to the instance at launch time." + type = any + default = {} +} + +variable "cpu_core_count" { + description = "Sets the number of CPU cores for an instance" # This option is only supported on creation of instance type that support CPU Options https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html#cpu-options-supported-instances-values + type = number + default = null +} + +variable "cpu_threads_per_core" { + description = "Sets the number of CPU threads per core for an instance (has no effect unless cpu_core_count is also set)" + type = number + default = null +} + +# Spot instance request +variable "create_spot_instance" { + description = "Depicts if the instance is a spot instance" + type = bool + default = false +} + +variable "spot_price" { + description = "The maximum price to request on the spot market. Defaults to on-demand price" + type = string + default = null +} + +variable "spot_wait_for_fulfillment" { + description = "If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached" + type = bool + default = null +} + +variable "spot_type" { + description = "If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent`" + type = string + default = null +} + +variable "spot_launch_group" { + description = "A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually" + type = string + default = null +} + +variable "spot_block_duration_minutes" { + description = "The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360)" + type = number + default = null +} + +variable "spot_instance_interruption_behavior" { + description = "Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate`" + type = string + default = null +} + +variable "spot_valid_until" { + description = "The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" + type = string + default = null +} + +variable "spot_valid_from" { + description = "The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" + type = string + default = null +} + +variable "disable_api_stop" { + description = "If true, enables EC2 Instance Stop Protection" + type = bool + default = null + +} +variable "putin_khuylo" { + description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" + type = bool + default = true +} + +################################################################################ +# IAM Role / Instance Profile +################################################################################ + +variable "create_iam_instance_profile" { + description = "Determines whether an IAM instance profile is created or to use an existing IAM instance profile" + type = bool + default = false +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name` or `name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_policies" { + description = "Policies attached to the IAM role" + type = map(string) + default = {} +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role/profile created" + type = map(string) + default = {} +} diff --git a/modules/aws-ec2-instance/versions.tf b/modules/aws-ec2-instance/versions.tf new file mode 100644 index 0000000..fd4d116 --- /dev/null +++ b/modules/aws-ec2-instance/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66" + } + } +} diff --git a/modules/aws-ec2-instance/wrappers/README.md b/modules/aws-ec2-instance/wrappers/README.md new file mode 100644 index 0000000..dbb2afd --- /dev/null +++ b/modules/aws-ec2-instance/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ec2-instance/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ec2-instance.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ec2-instance/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/aws-ec2-instance/wrappers/main.tf b/modules/aws-ec2-instance/wrappers/main.tf new file mode 100644 index 0000000..c6639ee --- /dev/null +++ b/modules/aws-ec2-instance/wrappers/main.tf @@ -0,0 +1,76 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name, "") + ami_ssm_parameter = try(each.value.ami_ssm_parameter, var.defaults.ami_ssm_parameter, "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2") + ami = try(each.value.ami, var.defaults.ami, null) + ignore_ami_changes = try(each.value.ignore_ami_changes, var.defaults.ignore_ami_changes, false) + associate_public_ip_address = try(each.value.associate_public_ip_address, var.defaults.associate_public_ip_address, null) + maintenance_options = try(each.value.maintenance_options, var.defaults.maintenance_options, {}) + availability_zone = try(each.value.availability_zone, var.defaults.availability_zone, null) + capacity_reservation_specification = try(each.value.capacity_reservation_specification, var.defaults.capacity_reservation_specification, {}) + cpu_credits = try(each.value.cpu_credits, var.defaults.cpu_credits, null) + disable_api_termination = try(each.value.disable_api_termination, var.defaults.disable_api_termination, null) + ebs_block_device = try(each.value.ebs_block_device, var.defaults.ebs_block_device, []) + ebs_optimized = try(each.value.ebs_optimized, var.defaults.ebs_optimized, null) + enclave_options_enabled = try(each.value.enclave_options_enabled, var.defaults.enclave_options_enabled, null) + ephemeral_block_device = try(each.value.ephemeral_block_device, var.defaults.ephemeral_block_device, []) + get_password_data = try(each.value.get_password_data, var.defaults.get_password_data, null) + hibernation = try(each.value.hibernation, var.defaults.hibernation, null) + host_id = try(each.value.host_id, var.defaults.host_id, null) + iam_instance_profile = try(each.value.iam_instance_profile, var.defaults.iam_instance_profile, null) + instance_initiated_shutdown_behavior = try(each.value.instance_initiated_shutdown_behavior, var.defaults.instance_initiated_shutdown_behavior, null) + instance_type = try(each.value.instance_type, var.defaults.instance_type, "t3.micro") + instance_tags = try(each.value.instance_tags, var.defaults.instance_tags, {}) + ipv6_address_count = try(each.value.ipv6_address_count, var.defaults.ipv6_address_count, null) + ipv6_addresses = try(each.value.ipv6_addresses, var.defaults.ipv6_addresses, null) + key_name = try(each.value.key_name, var.defaults.key_name, null) + launch_template = try(each.value.launch_template, var.defaults.launch_template, {}) + metadata_options = try(each.value.metadata_options, var.defaults.metadata_options, { + "http_endpoint" = "enabled" + "http_put_response_hop_limit" = 1 + "http_tokens" = "optional" + }) + monitoring = try(each.value.monitoring, var.defaults.monitoring, null) + network_interface = try(each.value.network_interface, var.defaults.network_interface, []) + placement_group = try(each.value.placement_group, var.defaults.placement_group, null) + private_ip = try(each.value.private_ip, var.defaults.private_ip, null) + root_block_device = try(each.value.root_block_device, var.defaults.root_block_device, []) + secondary_private_ips = try(each.value.secondary_private_ips, var.defaults.secondary_private_ips, null) + source_dest_check = try(each.value.source_dest_check, var.defaults.source_dest_check, null) + subnet_id = try(each.value.subnet_id, var.defaults.subnet_id, null) + tags = try(each.value.tags, var.defaults.tags, {}) + tenancy = try(each.value.tenancy, var.defaults.tenancy, null) + user_data = try(each.value.user_data, var.defaults.user_data, null) + user_data_base64 = try(each.value.user_data_base64, var.defaults.user_data_base64, null) + user_data_replace_on_change = try(each.value.user_data_replace_on_change, var.defaults.user_data_replace_on_change, null) + volume_tags = try(each.value.volume_tags, var.defaults.volume_tags, {}) + enable_volume_tags = try(each.value.enable_volume_tags, var.defaults.enable_volume_tags, true) + vpc_security_group_ids = try(each.value.vpc_security_group_ids, var.defaults.vpc_security_group_ids, null) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + cpu_options = try(each.value.cpu_options, var.defaults.cpu_options, {}) + cpu_core_count = try(each.value.cpu_core_count, var.defaults.cpu_core_count, null) + cpu_threads_per_core = try(each.value.cpu_threads_per_core, var.defaults.cpu_threads_per_core, null) + create_spot_instance = try(each.value.create_spot_instance, var.defaults.create_spot_instance, false) + spot_price = try(each.value.spot_price, var.defaults.spot_price, null) + spot_wait_for_fulfillment = try(each.value.spot_wait_for_fulfillment, var.defaults.spot_wait_for_fulfillment, null) + spot_type = try(each.value.spot_type, var.defaults.spot_type, null) + spot_launch_group = try(each.value.spot_launch_group, var.defaults.spot_launch_group, null) + spot_block_duration_minutes = try(each.value.spot_block_duration_minutes, var.defaults.spot_block_duration_minutes, null) + spot_instance_interruption_behavior = try(each.value.spot_instance_interruption_behavior, var.defaults.spot_instance_interruption_behavior, null) + spot_valid_until = try(each.value.spot_valid_until, var.defaults.spot_valid_until, null) + spot_valid_from = try(each.value.spot_valid_from, var.defaults.spot_valid_from, null) + disable_api_stop = try(each.value.disable_api_stop, var.defaults.disable_api_stop, null) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) + create_iam_instance_profile = try(each.value.create_iam_instance_profile, var.defaults.create_iam_instance_profile, false) + iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) + iam_role_policies = try(each.value.iam_role_policies, var.defaults.iam_role_policies, {}) + iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) +} diff --git a/modules/aws-ec2-instance/wrappers/outputs.tf b/modules/aws-ec2-instance/wrappers/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/modules/aws-ec2-instance/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/aws-ec2-instance/wrappers/variables.tf b/modules/aws-ec2-instance/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/aws-ec2-instance/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/aws-ec2-instance/wrappers/versions.tf b/modules/aws-ec2-instance/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/aws-ec2-instance/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/modules/aws-ecr/main.tf b/modules/aws-ecr/main.tf new file mode 100644 index 0000000..c18b6ff --- /dev/null +++ b/modules/aws-ecr/main.tf @@ -0,0 +1,124 @@ +data "aws_caller_identity" "current" {} + +resource "aws_ecr_repository" "this" { + for_each = toset(var.ecr_repo_names) + + name = each.value + + image_tag_mutability = var.image_tag_mutability + + image_scanning_configuration { + scan_on_push = var.scan_on_pushing + } + + tags = var.tags +} + +data "aws_iam_policy_document" "ecs_ecr_read_perms" { + statement { + sid = "ECRRead" + + effect = "Allow" + + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage", + ] + + principals { + identifiers = var.allowed_read_principals + type = "AWS" + } + condition { + test = "StringLike" + variable = "aws:PrincipalOrgID" + values = [var.organization_id] + } + } +} + +data "aws_iam_policy_document" "ecr_read_and_write_perms" { + statement { + sid = "ECRRead" + + effect = "Allow" + + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage", + ] + + principals { + identifiers = var.allowed_read_principals + type = "AWS" + } + condition { + test = "StringLike" + variable = "aws:PrincipalOrgID" + values = [var.organization_id] + } + } + + statement { + sid = "ECRWrite" + + effect = "Allow" + + actions = [ + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + ] + + principals { + identifiers = var.allowed_write_principals + type = "AWS" + } + condition { + test = "StringLike" + variable = "aws:PrincipalOrgID" + values = [var.organization_id] + } + } +} + +resource "aws_ecr_repository_policy" "this" { + for_each = toset(var.ecr_repo_names) + + repository = each.value + + policy = length(var.allowed_write_principals) > 0 ? data.aws_iam_policy_document.ecr_read_and_write_perms.json : data.aws_iam_policy_document.ecs_ecr_read_perms.json + + depends_on = [aws_ecr_repository.this] +} + +resource "aws_ecr_replication_configuration" "example" { + replication_configuration { + rule { + destination { + region = var.secondary_region + registry_id = data.aws_caller_identity.current.account_id + } + } + } +} diff --git a/modules/aws-ecr/outputs.tf b/modules/aws-ecr/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ecr/variables.tf b/modules/aws-ecr/variables.tf new file mode 100644 index 0000000..cb88030 --- /dev/null +++ b/modules/aws-ecr/variables.tf @@ -0,0 +1,63 @@ +#variable "namespace" { +# description = "The namespace we interpolate in all resources" +#} + +variable "create" { + description = "create defines if resources need to be created true/false" + default = true +} + +# The name of the ECR repository +variable "ecr_repo_names" { + description = "name defines the name of the repository, by default it will be interpolated to {namespace}-{name}" + type = list(any) +} + + +variable "allowed_read_principals" { + description = "allowed_read_principals defines which external principals are allowed to read from the ECR repository" + type = list(any) +} + +variable "allowed_write_principals" { + description = "allowed_write_principals defines which external principals are allowed to write to the ECR repository" + type = list(any) + default = [] +} + +variable "lifecycle_policy_rules_count" { + description = "The amount of lifecycle_policy_rules, this to make sure we are not running into computed count problems" + default = "0" +} + +variable "lifecycle_policy_rules" { + description = "List of json lifecycle policy rules, created by another module: doingcloudright/ecr-lifecycle-policy-rule/aws" + default = [] +} + +variable "image_tag_mutability" { + description = "The tag mutability setting for the repository. Must be one of: MUTABLE or IMMUTABLE." + default = "MUTABLE" +} + +variable "scan_on_pushing" { + description = "Indicates whether images are scanned after being pushed to the repository (true) or not scanned (false)." + default = false +} + +variable "tags" { + description = "A map of tags to assign to the resource." + default = {} +} + + +variable "secondary_region" { + description = "Region to Create ECR Registry" + type = string +} + +variable "organization_id" { + type = string + default = "" + description = "organization id" +} \ No newline at end of file diff --git a/modules/aws-ecr/versions.tf b/modules/aws-ecr/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecr/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs-load-balancer/main.tf b/modules/aws-ecs-load-balancer/main.tf new file mode 100644 index 0000000..d874863 --- /dev/null +++ b/modules/aws-ecs-load-balancer/main.tf @@ -0,0 +1,288 @@ +#------------------------------------------------------------------------------ +# S3 BUCKET - For access logs +#------------------------------------------------------------------------------ +data "aws_elb_service_account" "default" {} + +resource "random_string" "log_s3_name" { + count = var.enable_s3_logs ? 1 : 0 + length = 8 + numeric = true + special = false + upper = false +} + +module "lb_logs_s3" { + source = "../terraform-aws-s3" + count = var.enable_s3_logs ? 1 : 0 + + + bucket = "ecs-alb-log-bucket-${random_string.log_s3_name[0].result}" + acl = "log-delivery-write" + + # Allow deletion of non-empty bucket + force_destroy = true + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs + attach_cross_account_policy = false + +} + +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +resource "random_string" "lb_name" { + count = var.use_random_name_for_lb ? 1 : 0 + length = 32 + numeric = true + special = false +} + +resource "aws_lb" "lb" { + name = var.use_random_name_for_lb ? random_string.lb_name[0].result : substr("${var.name_prefix}-lb", 0, 31) + + internal = var.internal + load_balancer_type = "application" + drop_invalid_header_fields = var.drop_invalid_header_fields + subnets = var.internal ? var.private_subnets : var.public_subnets + idle_timeout = var.idle_timeout + enable_deletion_protection = var.enable_deletion_protection + enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing + enable_http2 = var.enable_http2 + ip_address_type = var.ip_address_type + security_groups = compact(var.security_groups) + + dynamic "access_logs" { + for_each = var.enable_s3_logs ? [1] : [] + content { + bucket = module.lb_logs_s3[0].s3_bucket_id + enabled = var.enable_s3_logs + } + } + + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-lb" + }, + ) +} + +resource "aws_wafv2_web_acl_association" "waf_association" { + count = var.waf_web_acl_arn != "" ? 1 : 0 + resource_arn = aws_lb.lb.arn + web_acl_arn = var.waf_web_acl_arn +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +resource "aws_lb_target_group" "lb_http_tgs" { + for_each = { + for name, config in var.http_ports : name => config + if lookup(config, "type", "") == "" || lookup(config, "type", "") == "forward" + } + name = "${var.name_prefix}-http-${each.value.target_group_port}" + port = each.value.target_group_port + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTP" : each.value.target_group_protocol + vpc_id = var.vpc_id + deregistration_delay = var.deregistration_delay + slow_start = var.slow_start + load_balancing_algorithm_type = var.load_balancing_algorithm_type + dynamic "stickiness" { + for_each = var.stickiness == null ? [] : [var.stickiness] + content { + type = stickiness.value.type + cookie_duration = stickiness.value.cookie_duration + enabled = stickiness.value.enabled + } + } + health_check { + enabled = var.target_group_health_check_enabled + interval = var.target_group_health_check_interval + path = var.target_group_health_check_path + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTP" : each.value.target_group_protocol + timeout = var.target_group_health_check_timeout + healthy_threshold = var.target_group_health_check_healthy_threshold + unhealthy_threshold = var.target_group_health_check_unhealthy_threshold + matcher = var.target_group_health_check_matcher + } + target_type = "ip" + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-http-${each.value.target_group_port}" + }, + ) + lifecycle { + create_before_destroy = true + } + depends_on = [aws_lb.lb] +} + +resource "aws_lb_target_group" "lb_https_tgs" { + for_each = { + for name, config in var.https_ports : name => config + if lookup(config, "type", "") == "" || lookup(config, "type", "") == "forward" + } + name = "${var.name_prefix}-https-${each.value.target_group_port}" + port = each.value.target_group_port + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTPS" : each.value.target_group_protocol + vpc_id = var.vpc_id + deregistration_delay = var.deregistration_delay + slow_start = var.slow_start + load_balancing_algorithm_type = var.load_balancing_algorithm_type + dynamic "stickiness" { + for_each = var.stickiness == null ? [] : [var.stickiness] + content { + type = stickiness.value.type + cookie_duration = stickiness.value.cookie_duration + enabled = stickiness.value.enabled + } + } + health_check { + enabled = var.target_group_health_check_enabled + interval = var.target_group_health_check_interval + path = var.target_group_health_check_path + protocol = lookup(each.value, "target_group_protocol", "") == "" ? "HTTPS" : each.value.target_group_protocol + timeout = var.target_group_health_check_timeout + healthy_threshold = var.target_group_health_check_healthy_threshold + unhealthy_threshold = var.target_group_health_check_unhealthy_threshold + matcher = var.target_group_health_check_matcher + } + target_type = "ip" + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-https-${each.value.target_group_port}" + }, + ) + lifecycle { + create_before_destroy = true + } + depends_on = [aws_lb.lb] +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Listeners +#------------------------------------------------------------------------------ +resource "aws_lb_listener" "lb_http_listeners" { + for_each = var.http_ports + load_balancer_arn = aws_lb.lb.arn + port = each.value.listener_port + protocol = "HTTP" + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "redirect" ? [1] : [] + content { + type = "redirect" + + redirect { + host = lookup(each.value, "host", "#{host}") + path = lookup(each.value, "path", "/#{path}") + port = lookup(each.value, "port", "#{port}") + protocol = lookup(each.value, "protocol", "#{protocol}") + query = lookup(each.value, "query", "#{query}") + status_code = lookup(each.value, "status_code", "HTTP_301") + } + } + } + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "fixed-response" ? [1] : [] + content { + type = "fixed-response" + + fixed_response { + content_type = lookup(each.value, "content_type", "text/plain") + message_body = lookup(each.value, "message_body", "Fixed response content") + status_code = lookup(each.value, "status_code", "200") + } + } + } + + # We fallback to using forward type action if type is not defined + dynamic "default_action" { + for_each = (lookup(each.value, "type", "") == "" || lookup(each.value, "type", "") == "forward") ? [1] : [] + content { + target_group_arn = aws_lb_target_group.lb_http_tgs[each.key].arn + type = "forward" + } + } + + tags = var.tags +} + +resource "aws_lb_listener" "lb_https_listeners" { + for_each = var.https_ports + load_balancer_arn = aws_lb.lb.arn + port = each.value.listener_port + protocol = "HTTPS" + ssl_policy = var.ssl_policy + certificate_arn = var.default_certificate_arn + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "redirect" ? [1] : [] + content { + type = "redirect" + + redirect { + host = lookup(each.value, "host", "#{host}") + path = lookup(each.value, "path", "/#{path}") + port = lookup(each.value, "port", "#{port}") + protocol = lookup(each.value, "protocol", "#{protocol}") + query = lookup(each.value, "query", "#{query}") + status_code = lookup(each.value, "status_code", "HTTP_301") + } + } + } + + dynamic "default_action" { + for_each = lookup(each.value, "type", "") == "fixed-response" ? [1] : [] + content { + type = "fixed-response" + + fixed_response { + content_type = lookup(each.value, "content_type", "text/plain") + message_body = lookup(each.value, "message_body", "Fixed response content") + status_code = lookup(each.value, "status_code", "200") + } + } + } + + # We fallback to using forward type action if type is not defined + dynamic "default_action" { + for_each = (lookup(each.value, "type", "") == "" || lookup(each.value, "type", "") == "forward") ? [1] : [] + content { + target_group_arn = aws_lb_target_group.lb_https_tgs[each.key].arn + type = "forward" + } + } + + tags = var.tags +} + +locals { + list_maps_listener_certificate_arns = flatten([ + for cert_arn in var.additional_certificates_arn_for_https_listeners : [ + for listener in aws_lb_listener.lb_https_listeners : { + name = "${listener}-${cert_arn}" + listener_arn = listener.arn + certificate_arn = cert_arn + } + ] + ]) + + map_listener_certificate_arns = { + for obj in local.list_maps_listener_certificate_arns : obj.name => { + listener_arn = obj.listener_arn, + certificate_arn = obj.certificate_arn + } + } +} + +resource "aws_lb_listener_certificate" "additional_certificates_for_https_listeners" { + for_each = local.map_listener_certificate_arns + listener_arn = each.value.listener_arn + certificate_arn = each.value.certificate_arn +} diff --git a/modules/aws-ecs-load-balancer/outputs.tf b/modules/aws-ecs-load-balancer/outputs.tf new file mode 100644 index 0000000..59ce89b --- /dev/null +++ b/modules/aws-ecs-load-balancer/outputs.tf @@ -0,0 +1,125 @@ +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +output "aws_lb_lb_id" { + description = "The ARN of the load balancer (matches arn)." + value = aws_lb.lb.id +} + +output "aws_lb_lb_arn" { + description = "The ARN of the load balancer (matches id)." + value = aws_lb.lb.arn +} + +output "aws_lb_lb_name" { + description = "The ARN of the load balancer (matches id)." + value = aws_lb.lb.arn +} + +output "aws_lb_lb_arn_suffix" { + description = "The ARN suffix for use with CloudWatch Metrics." + value = aws_lb.lb.arn_suffix +} + +output "aws_lb_lb_dns_name" { + description = "The DNS name of the load balancer." + value = aws_lb.lb.dns_name +} + +output "aws_lb_lb_zone_id" { + description = "The canonical hosted zone ID of the load balancer (to be used in a Route 53 Alias record)." + value = aws_lb.lb.zone_id +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +output "lb_http_tgs_ids" { + description = "List of HTTP Target Groups IDs" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.id] +} + +output "lb_http_tgs_arns" { + description = "List of HTTP Target Groups ARNs" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.arn] +} + +output "lb_http_tgs_names" { + description = "List of HTTP Target Groups Names" + value = [for tg in aws_lb_target_group.lb_http_tgs : tg.name] +} + +output "lb_http_tgs_ports" { + description = "List of HTTP Target Groups ports" + value = [for tg in aws_lb_target_group.lb_http_tgs : tostring(tg.port)] +} + +output "lb_http_tgs_map_arn_port" { + value = zipmap( + [for tg in aws_lb_target_group.lb_http_tgs : tg.arn], + [for tg in aws_lb_target_group.lb_http_tgs : tostring(tg.port)] + ) +} + +output "lb_https_tgs_ids" { + description = "List of HTTPS Target Groups IDs" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.id] +} + +output "lb_https_tgs_arns" { + description = "List of HTTPS Target Groups ARNs" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.arn] +} + +output "lb_https_tgs_names" { + description = "List of HTTPS Target Groups Names" + value = [for tg in aws_lb_target_group.lb_https_tgs : tg.name] +} + +output "lb_https_tgs_ports" { + description = "List of HTTPS Target Groups ports" + value = [for tg in aws_lb_target_group.lb_https_tgs : tostring(tg.port)] +} + +output "lb_https_tgs_map_arn_port" { + value = zipmap( + [for tg in aws_lb_target_group.lb_https_tgs : tg.arn], + [for tg in aws_lb_target_group.lb_https_tgs : tostring(tg.port)] + ) +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Listeners +#------------------------------------------------------------------------------ +output "lb_http_listeners_ids" { + description = "List of HTTP Listeners IDs" + value = [for listener in aws_lb_listener.lb_http_listeners : listener.id] +} + +output "lb_http_listeners_arns" { + description = "List of HTTP Listeners ARNs" + value = [for listener in aws_lb_listener.lb_http_listeners : listener.arn] +} + +output "lb_https_listeners_ids" { + description = "List of HTTPS Listeners IDs" + value = [for listener in aws_lb_listener.lb_https_listeners : listener.id] +} + +output "lb_https_listeners_arns" { + description = "List of HTTPS Listeners ARNs" + value = [for listener in aws_lb_listener.lb_https_listeners : listener.arn] +} + +#------------------------------------------------------------------------------ +# S3 LB Logging Bucket +#------------------------------------------------------------------------------ +output "lb_logs_s3_bucket_id" { + description = "LB Logging S3 Bucket ID" + value = var.enable_s3_logs ? module.lb_logs_s3[0].s3_bucket_id : null +} + +output "lb_logs_s3_bucket_arn" { + description = "LB Logging S3 Bucket ARN" + value = var.enable_s3_logs ? module.lb_logs_s3[0].s3_bucket_arn : null +} \ No newline at end of file diff --git a/modules/aws-ecs-load-balancer/variable.tf b/modules/aws-ecs-load-balancer/variable.tf new file mode 100644 index 0000000..f1499f7 --- /dev/null +++ b/modules/aws-ecs-load-balancer/variable.tf @@ -0,0 +1,249 @@ +#------------------------------------------------------------------------------ +# Misc +#------------------------------------------------------------------------------ +variable "name_prefix" { + description = "Name prefix for resources on AWS" +} + +variable "use_random_name_for_lb" { + description = "If true the LB name will be a random string" + type = bool + default = false +} + +variable "tags" { + type = map(string) + default = {} + description = "Resource tags" +} + +#------------------------------------------------------------------------------ +# AWS Networking +#------------------------------------------------------------------------------ +variable "vpc_id" { + description = "ID of the VPC" +} + +#------------------------------------------------------------------------------ +# S3 logs bucket +#------------------------------------------------------------------------------ +variable "enable_s3_logs" { + description = "(Optional) If true, all resources to send LB logs to S3 will be created" + type = bool + default = true +} + + +#------------------------------------------------------------------------------ +# APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +variable "internal" { + description = "(Optional) If true, the LB will be internal." + type = bool + default = false +} + +variable "security_groups" { + description = "(Optional) A list of security group IDs to assign to the LB." + type = list(string) + default = [] +} + +variable "drop_invalid_header_fields" { + description = "(Optional) Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false). The default is false. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens." + type = bool + default = false +} + +variable "private_subnets" { + description = "A list of private subnet IDs to attach to the LB if it is INTERNAL." + type = list(string) +} + +variable "public_subnets" { + description = "A list of public subnet IDs to attach to the LB if it is NOT internal." + type = list(string) +} + +variable "idle_timeout" { + description = "(Optional) The time in seconds that the connection is allowed to be idle. Default: 60." + type = number + default = 60 +} + +variable "enable_deletion_protection" { + description = "(Optional) If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false." + type = bool + default = false +} + +variable "enable_cross_zone_load_balancing" { + description = "(Optional) If true, cross-zone load balancing of the load balancer will be enabled. Defaults to false." + type = bool + default = false +} + +variable "enable_http2" { + description = "(Optional) Indicates whether HTTP/2 is enabled in the load balancer. Defaults to true." + type = bool + default = true +} + +variable "ip_address_type" { + description = "(Optional) The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack. Defaults to ipv4" + type = string + default = "ipv4" +} + +variable "waf_web_acl_arn" { + description = "ARN of a WAFV2 to associate with the ALB" + type = string + default = "" +} + +#------------------------------------------------------------------------------ +# ACCESS CONTROL TO APPLICATION LOAD BALANCER +#------------------------------------------------------------------------------ +variable "http_ports" { + description = "Map containing objects to define listeners behaviour based on type field. If type field is `forward`, include listener_port and the target_group_port. For `redirect` type, include listener port, host, path, port, protocol, query and status_code. For `fixed-response`, include listener_port, content_type, message_body and status_code" + type = map(any) + default = { + default_http = { + type = "forward" + listener_port = 80 + target_group_port = 80 + } + } +} + +variable "https_ports" { + description = "Map containing objects to define listeners behaviour based on type field. If type field is `forward`, include listener_port and the target_group_port. For `redirect` type, include listener port, host, path, port, protocol, query and status_code. For `fixed-response`, include listener_port, content_type, message_body and status_code" + type = map(any) + default = { + default_http = { + type = "forward" + listener_port = 443 + target_group_port = 443 + } + } +} + + +variable "http_ingress_cidr_blocks" { + description = "List of CIDR blocks to allowed to access the Load Balancer through HTTP" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "http_ingress_prefix_list_ids" { + description = "List of prefix list IDs blocks to allowed to access the Load Balancer through HTTP" + type = list(string) + default = [] +} + +variable "https_ingress_cidr_blocks" { + description = "List of CIDR blocks to allowed to access the Load Balancer through HTTPS" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "https_ingress_prefix_list_ids" { + description = "List of prefix list IDs blocks to allowed to access the Load Balancer through HTTPS" + type = list(string) + default = [] +} + +#------------------------------------------------------------------------------ +# AWS LOAD BALANCER - Target Groups +#------------------------------------------------------------------------------ +variable "deregistration_delay" { + description = "(Optional) The amount time for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds. The default value is 300 seconds." + type = number + default = 300 +} + +variable "slow_start" { + description = "(Optional) The amount time for targets to warm up before the load balancer sends them a full share of requests. The range is 30-900 seconds or 0 to disable. The default value is 0 seconds." + type = number + default = 0 +} + +variable "load_balancing_algorithm_type" { + description = "(Optional) Determines how the load balancer selects targets when routing requests. The value is round_robin or least_outstanding_requests. The default is round_robin." + type = string + default = "round_robin" +} + +variable "stickiness" { + description = "(Optional) A Stickiness block. Provide three fields. type, the type of sticky sessions. The only current possible value is lb_cookie. cookie_duration, the time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). enabled, boolean to enable / disable stickiness. Default is true." + type = object({ + type = string + cookie_duration = string + enabled = bool + }) + default = { + type = "lb_cookie" + cookie_duration = 86400 + enabled = true + } +} + +variable "target_group_health_check_enabled" { + description = "(Optional) Indicates whether health checks are enabled. Defaults to true." + type = bool + default = true +} + +variable "target_group_health_check_interval" { + description = "(Optional) The approximate amount of time, in seconds, between health checks of an individual target. Minimum value 5 seconds, Maximum value 300 seconds. Default 30 seconds." + type = number + default = 30 +} + +variable "target_group_health_check_path" { + description = "The destination for the health check request." + type = string + default = "/" +} + +variable "target_group_health_check_timeout" { + description = "(Optional) The amount of time, in seconds, during which no response means a failed health check. The range is 2 to 120 seconds, and the default is 5 seconds." + type = number + default = 5 +} + +variable "target_group_health_check_healthy_threshold" { + description = "(Optional) The number of consecutive health checks successes required before considering an unhealthy target healthy. Defaults to 3." + type = number + default = 3 +} + +variable "target_group_health_check_unhealthy_threshold" { + description = "(Optional) The number of consecutive health check failures required before considering the target unhealthy. Defaults to 3." + type = number + default = 3 +} + +variable "target_group_health_check_matcher" { + description = "The HTTP codes to use when checking for a successful response from a target. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). Default is 200." + type = string + default = "200" +} + +variable "ssl_policy" { + description = "(Optional) The name of the SSL Policy for the listener. . Required if var.https_ports is set." + type = string + default = null +} + +variable "default_certificate_arn" { + description = "(Optional) The ARN of the default SSL server certificate. Required if var.https_ports is set." + type = string + default = null +} + +variable "additional_certificates_arn_for_https_listeners" { + description = "(Optional) List of SSL server certificate ARNs for HTTPS listener. Use it if you need to set additional certificates besides default_certificate_arn" + type = list(any) + default = [] +} \ No newline at end of file diff --git a/modules/aws-ecs-load-balancer/versions.tf b/modules/aws-ecs-load-balancer/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs-load-balancer/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs-service/main.tf b/modules/aws-ecs-service/main.tf new file mode 100644 index 0000000..d292256 --- /dev/null +++ b/modules/aws-ecs-service/main.tf @@ -0,0 +1,161 @@ +resource "aws_ecs_service" "service" { + name = "${var.name_prefix}-service" + cluster = var.ecs_cluster_arn + deployment_maximum_percent = var.deployment_maximum_percent + deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent + desired_count = var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + health_check_grace_period_seconds = var.health_check_grace_period_seconds + launch_type = "FARGATE" + force_new_deployment = var.force_new_deployment + + dynamic "load_balancer" { + for_each = var.lb_http_tgs_map_arn_port + content { + target_group_arn = load_balancer.key + container_name = var.container_name + container_port = load_balancer.value + } + } + dynamic "load_balancer" { + for_each = var.lb_https_tgs_map_arn_port + content { + target_group_arn = load_balancer.key + container_name = var.container_name + container_port = load_balancer.value + } + } + network_configuration { + security_groups = var.security_groups + subnets = var.assign_public_ip ? var.public_subnets : var.private_subnets + assign_public_ip = var.assign_public_ip + } + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + content { + type = ordered_placement_strategy.value.type + field = lookup(ordered_placement_strategy.value, "field", null) + } + } + dynamic "deployment_controller" { + for_each = var.deployment_controller + content { + type = deployment_controller.value.type + } + } + dynamic "placement_constraints" { + for_each = var.placement_constraints + content { + expression = lookup(placement_constraints.value, "expression", null) + type = placement_constraints.value.type + } + } + platform_version = var.platform_version + propagate_tags = var.propagate_tags + dynamic "service_registries" { + for_each = var.service_registries + content { + registry_arn = service_registries.value.registry_arn + port = lookup(service_registries.value, "port", null) + container_name = lookup(service_registries.value, "container_name", null) + container_port = lookup(service_registries.value, "container_port", null) + } + } + task_definition = var.task_definition_arn + tags = var.tags + +} + +#------------------------------------------------------------------------------ +# AWS Auto Scaling - CloudWatch Alarm CPU High +#------------------------------------------------------------------------------ +resource "aws_cloudwatch_metric_alarm" "cpu_high" { + alarm_name = "${var.name_prefix}-cpu-high" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = var.max_cpu_evaluation_period + metric_name = "CPUUtilization" + namespace = "AWS/ECS" + period = var.max_cpu_period + statistic = "Maximum" + threshold = var.max_cpu_threshold + dimensions = { + ClusterName = var.ecs_cluster_name + ServiceName = aws_ecs_service.service.name + } + alarm_actions = [aws_appautoscaling_policy.scale_up_policy.arn] + + tags = var.tags +} + +#------------------------------------------------------------------------------ +# AWS Auto Scaling - CloudWatch Alarm CPU Low +#------------------------------------------------------------------------------ +resource "aws_cloudwatch_metric_alarm" "cpu_low" { + alarm_name = "${var.name_prefix}-cpu-low" + comparison_operator = "LessThanOrEqualToThreshold" + evaluation_periods = var.min_cpu_evaluation_period + metric_name = "CPUUtilization" + namespace = "AWS/ECS" + period = var.min_cpu_period + statistic = "Average" + threshold = var.min_cpu_threshold + dimensions = { + ClusterName = var.ecs_cluster_name + ServiceName = aws_ecs_service.service.name + } + alarm_actions = [aws_appautoscaling_policy.scale_down_policy.arn] + + tags = var.tags +} + +#------------------------------------------------------------------------------ +# AWS Auto Scaling - Scaling Up Policy +#------------------------------------------------------------------------------ +resource "aws_appautoscaling_policy" "scale_up_policy" { + name = "${var.name_prefix}-scale-up-policy" + depends_on = [aws_appautoscaling_target.scale_target] + service_namespace = "ecs" + resource_id = "service/${var.ecs_cluster_name}/${aws_ecs_service.service.name}" + scalable_dimension = "ecs:service:DesiredCount" + step_scaling_policy_configuration { + adjustment_type = "ChangeInCapacity" + cooldown = 60 + metric_aggregation_type = "Maximum" + step_adjustment { + metric_interval_lower_bound = 0 + scaling_adjustment = 1 + } + } +} + +#------------------------------------------------------------------------------ +# AWS Auto Scaling - Scaling Down Policy +#------------------------------------------------------------------------------ +resource "aws_appautoscaling_policy" "scale_down_policy" { + name = "${var.name_prefix}-scale-down-policy" + depends_on = [aws_appautoscaling_target.scale_target] + service_namespace = "ecs" + resource_id = "service/${var.ecs_cluster_name}/${aws_ecs_service.service.name}" + scalable_dimension = "ecs:service:DesiredCount" + step_scaling_policy_configuration { + adjustment_type = "ChangeInCapacity" + cooldown = 60 + metric_aggregation_type = "Maximum" + step_adjustment { + metric_interval_upper_bound = 0 + scaling_adjustment = -1 + } + } +} + +#------------------------------------------------------------------------------ +# AWS Auto Scaling - Scaling Target +#------------------------------------------------------------------------------ +resource "aws_appautoscaling_target" "scale_target" { + service_namespace = "ecs" + resource_id = "service/${var.ecs_cluster_name}/${aws_ecs_service.service.name}" + scalable_dimension = "ecs:service:DesiredCount" + min_capacity = var.scale_target_min_capacity + max_capacity = var.scale_target_max_capacity +} diff --git a/modules/aws-ecs-service/output.tf b/modules/aws-ecs-service/output.tf new file mode 100644 index 0000000..cb0d76d --- /dev/null +++ b/modules/aws-ecs-service/output.tf @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------------ +# AWS ECS SERVICE +#------------------------------------------------------------------------------ +output "service_arn" { + description = "The Amazon Resource Name (ARN) that identifies the service." + value = aws_ecs_service.service.id +} + +output "service_name" { + description = "The name of the service." + value = aws_ecs_service.service.name +} + +output "service_cluster" { + description = "The Amazon Resource Name (ARN) of cluster which the service runs on." + value = aws_ecs_service.service.cluster +} + +output "desired_count" { + description = "The number of instances of the task definition" + value = aws_ecs_service.service.desired_count +} \ No newline at end of file diff --git a/modules/aws-ecs-service/variables.tf b/modules/aws-ecs-service/variables.tf new file mode 100644 index 0000000..7dace3a --- /dev/null +++ b/modules/aws-ecs-service/variables.tf @@ -0,0 +1,204 @@ +#------------------------------------------------------------------------------ +# Misc +#------------------------------------------------------------------------------ +variable "name_prefix" { + description = "Name prefix for resources on AWS" +} + +#------------------------------------------------------------------------------ +# AWS Networking +#------------------------------------------------------------------------------ +variable "vpc_id" { + description = "ID of the VPC" +} + +#------------------------------------------------------------------------------ +# AWS ECS SERVICE +#------------------------------------------------------------------------------ +variable "ecs_cluster_arn" { + description = "ARN of an ECS cluster" +} + +variable "deployment_maximum_percent" { + description = "(Optional) The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment." + type = number + default = 200 +} + +variable "deployment_minimum_healthy_percent" { + description = "(Optional) The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment." + type = number + default = 100 +} + +variable "desired_count" { + description = "(Optional) The number of instances of the task definition to place and keep running. Defaults to 0." + type = number + default = 1 +} + +variable "tags" { + type = map(string) + default = {} + description = "Resource tags" +} + +variable "enable_ecs_managed_tags" { + description = "(Optional) Specifies whether to enable Amazon ECS managed tags for the tasks within the service." + type = bool + default = false +} + +variable "health_check_grace_period_seconds" { + description = "(Optional) Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers." + type = number + default = 0 +} + +variable "ordered_placement_strategy" { + description = "(Optional) Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence. The maximum number of ordered_placement_strategy blocks is 5. This is a list of maps where each map should contain \"id\" and \"field\"" + type = list(any) + default = [] +} + +variable "deployment_controller" { + description = "(Optional) Deployment controller" + type = list(string) + default = [] +} + +variable "placement_constraints" { + type = list(any) + description = "(Optional) rules that are taken into consideration during task placement. Maximum number of placement_constraints is 10. This is a list of maps, where each map should contain \"type\" and \"expression\"" + default = [] +} + +variable "platform_version" { + description = "(Optional) The platform version on which to run your service. Defaults to 1.4.0. More information about Fargate platform versions can be found in the AWS ECS User Guide." + default = "1.4.0" +} + +variable "propagate_tags" { + description = "(Optional) Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are SERVICE and TASK_DEFINITION. Default to SERVICE" + default = "SERVICE" +} + +variable "service_registries" { + description = "(Optional) The service discovery registries for the service. The maximum number of service_registries blocks is 1. This is a map that should contain the following fields \"registry_arn\", \"port\", \"container_port\" and \"container_name\"" + type = map(any) + default = {} +} + +variable "task_definition_arn" { + description = "(Required) The full ARN of the task definition that you want to run in your service." +} + +variable "force_new_deployment" { + description = "(Optional) Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g. myimage:latest), roll Fargate tasks onto a newer platform version, or immediately deploy ordered_placement_strategy and placement_constraints updates." + type = bool + default = false +} + +variable "enable_execute_command" { + description = "(Optional) Specifies whether to enable Amazon ECS Exec for the tasks within the service." + type = bool + default = false +} + +#------------------------------------------------------------------------------ +# AWS ECS SERVICE network_configuration BLOCK +#------------------------------------------------------------------------------ +variable "public_subnets" { + description = "The public subnets associated with the task or service." + type = list(any) +} + +variable "private_subnets" { + description = "The private subnets associated with the task or service." + type = list(any) +} + +variable "security_groups" { + description = "(Optional) The security groups associated with the task or service. If you do not specify a security group, the default security group for the VPC is used." + type = list(any) + default = [] +} + +variable "assign_public_ip" { + description = "(Optional) Assign a public IP address to the ENI (Fargate launch type only). If true service will be associated with public subnets. Default false. " + type = bool + default = false +} + + +#------------------------------------------------------------------------------ +# AWS ECS SERVICE load_balancer BLOCK +#------------------------------------------------------------------------------ +variable "container_name" { + description = "Name of the running container" +} + +variable "lb_http_tgs_map_arn_port" { + type = map(any) + description = "Load balancer https target groups arns" + default = {} +} + + +variable "lb_https_tgs_map_arn_port" { + type = map(any) + description = "Load balancer https target groups arns" + default = {} +} + +#------------------------------------------------------------------------------ +# AWS ECS SERVICE AUTOSCALING +#------------------------------------------------------------------------------ +variable "ecs_cluster_name" { + description = "Name of the ECS cluster" +} + +variable "max_cpu_threshold" { + description = "Threshold for max CPU usage" + default = "85" + type = string +} +variable "min_cpu_threshold" { + description = "Threshold for min CPU usage" + default = "10" + type = string +} + +variable "max_cpu_evaluation_period" { + description = "The number of periods over which data is compared to the specified threshold for max cpu metric alarm" + default = "3" + type = string +} +variable "min_cpu_evaluation_period" { + description = "The number of periods over which data is compared to the specified threshold for min cpu metric alarm" + default = "3" + type = string +} + +variable "max_cpu_period" { + description = "The period in seconds over which the specified statistic is applied for max cpu metric alarm" + default = "60" + type = string +} +variable "min_cpu_period" { + description = "The period in seconds over which the specified statistic is applied for min cpu metric alarm" + default = "60" + type = string +} + +variable "scale_target_max_capacity" { + description = "The max capacity of the scalable target" + default = 5 + type = number +} + +variable "scale_target_min_capacity" { + description = "The min capacity of the scalable target" + default = 1 + type = number +} \ No newline at end of file diff --git a/modules/aws-ecs-service/versions.tf b/modules/aws-ecs-service/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs-service/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs-task-definition/files/iam/ecs_task_execution_iam_role.json b/modules/aws-ecs-task-definition/files/iam/ecs_task_execution_iam_role.json new file mode 100644 index 0000000..e11b137 --- /dev/null +++ b/modules/aws-ecs-task-definition/files/iam/ecs_task_execution_iam_role.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole", + "Sid": "" + } + ] +} \ No newline at end of file diff --git a/modules/aws-ecs-task-definition/main.tf b/modules/aws-ecs-task-definition/main.tf new file mode 100644 index 0000000..59b6952 --- /dev/null +++ b/modules/aws-ecs-task-definition/main.tf @@ -0,0 +1,119 @@ +#------------------------------------------------------------------------------ +# AWS ECS Task Execution Role +#------------------------------------------------------------------------------ +resource "aws_iam_role" "ecs_task_execution_role" { + name = "${var.name_prefix}-ecs-task-execution-role" + assume_role_policy = file("${path.module}/files/iam/ecs_task_execution_iam_role.json") + permissions_boundary = var.permissions_boundary + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy_attach" { + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = "arn:${var.iam_partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_policy" "ecs_task_execution_role_custom_policy" { + count = length(var.ecs_task_execution_role_custom_policies) + name = "${var.name_prefix}-ecs-task-execution-role-custom-policy-${count.index}" + description = "A custom policy for ${var.name_prefix}-ecs-task-execution-role IAM Role" + policy = var.ecs_task_execution_role_custom_policies[count.index] + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_custom_policy" { + count = length(var.ecs_task_execution_role_custom_policies) + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = aws_iam_policy.ecs_task_execution_role_custom_policy[count.index].arn +} + +#------------------------------------------------------------------------------ +# ECS Task Definition +#------------------------------------------------------------------------------ +# Task Definition +resource "aws_ecs_task_definition" "td" { + family = "${var.name_prefix}-td" + container_definitions = jsonencode([ + { + name = var.container_name + image = var.container_image + cpu = var.container_cpu + memory = var.container_memory + essential = true + portMappings = [ + { + containerPort = var.container_port + hostPort = var.container_host_port + } + ] + } + ]) + task_role_arn = var.task_role_arn == null ? aws_iam_role.ecs_task_execution_role.arn : var.task_role_arn + execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + network_mode = "awsvpc" + dynamic "placement_constraints" { + for_each = var.placement_constraints + content { + expression = lookup(placement_constraints.value, "expression", null) + type = placement_constraints.value.type + } + } + cpu = var.container_cpu + memory = var.container_memory + requires_compatibilities = ["FARGATE"] + dynamic "proxy_configuration" { + for_each = var.proxy_configuration + content { + container_name = proxy_configuration.value.container_name + properties = lookup(proxy_configuration.value, "properties", null) + type = lookup(proxy_configuration.value, "type", null) + } + } + dynamic "ephemeral_storage" { + for_each = var.ephemeral_storage_size == 0 ? [] : [var.ephemeral_storage_size] + content { + size_in_gib = var.ephemeral_storage_size + } + } + dynamic "volume" { + for_each = var.volumes + content { + name = volume.value.name + + host_path = lookup(volume.value, "host_path", null) + + dynamic "docker_volume_configuration" { + for_each = lookup(volume.value, "docker_volume_configuration", []) + content { + autoprovision = lookup(docker_volume_configuration.value, "autoprovision", null) + driver = lookup(docker_volume_configuration.value, "driver", null) + driver_opts = lookup(docker_volume_configuration.value, "driver_opts", null) + labels = lookup(docker_volume_configuration.value, "labels", null) + scope = lookup(docker_volume_configuration.value, "scope", null) + } + } + + dynamic "efs_volume_configuration" { + for_each = lookup(volume.value, "efs_volume_configuration", []) + content { + file_system_id = lookup(efs_volume_configuration.value, "file_system_id", null) + root_directory = lookup(efs_volume_configuration.value, "root_directory", null) + transit_encryption = lookup(efs_volume_configuration.value, "transit_encryption", null) + transit_encryption_port = lookup(efs_volume_configuration.value, "transit_encryption_port", null) + dynamic "authorization_config" { + for_each = lookup(efs_volume_configuration.value, "authorization_config", []) + content { + access_point_id = lookup(authorization_config.value, "access_point_id", null) + iam = lookup(authorization_config.value, "iam", null) + } + } + } + } + } + } + + tags = var.tags +} + +# TODO - Add this missing parameter +# inference_accelerator - (Optional) Configuration block(s) with Inference Accelerators settings. Detailed below. \ No newline at end of file diff --git a/modules/aws-ecs-task-definition/outputs.tf b/modules/aws-ecs-task-definition/outputs.tf new file mode 100644 index 0000000..09bc01f --- /dev/null +++ b/modules/aws-ecs-task-definition/outputs.tf @@ -0,0 +1,7 @@ +output "task_definition_arn" { + value = aws_ecs_task_definition.td.arn +} + +output "container_name" { + value = var.container_name +} \ No newline at end of file diff --git a/modules/aws-ecs-task-definition/variable.tf b/modules/aws-ecs-task-definition/variable.tf new file mode 100644 index 0000000..dbb6132 --- /dev/null +++ b/modules/aws-ecs-task-definition/variable.tf @@ -0,0 +1,416 @@ +#------------------------------------------------------------------------------ +# Misc +#------------------------------------------------------------------------------ +variable "name_prefix" { + description = "Name prefix for resources on AWS" +} + +variable "tags" { + type = map(string) + default = {} + description = "Resource tags" +} + +#------------------------------------------------------------------------------ +# AWS ECS Container Definition Variables for Cloudposse module +#------------------------------------------------------------------------------ +variable "container_name" { + type = string + default = null + description = "The name of the container. Up to 255 characters ([a-z], [A-Z], [0-9], -, _ allowed)" +} + +variable "container_image" { + type = string + default = null + description = "The image used to start the container. Images in the Docker Hub registry available by default" +} + +variable "container_memory" { + type = number + description = "(Optional) The amount of memory (in MiB) to allow the container to use. This is a hard limit, if the container attempts to exceed the container_memory, the container is killed. This field is optional for Fargate launch type and the total amount of container_memory of all containers in a task will need to be lower than the task memory value" + default = 4096 # 4 GB +} + +variable "container_port" { + type = number + description = "Container port" + default = 80 # 80 +} + +variable "container_host_port" { + type = number + description = "Container host port" + default = 80 # 80 +} + +variable "container_memory_reservation" { + type = number + description = "(Optional) The amount of memory (in MiB) to reserve for the container. If container needs to exceed this threshold, it can do so up to the set container_memory hard limit" + default = 2048 # 2 GB +} + +variable "container_definition" { + type = map(any) + description = "Container definition overrides which allows for extra keys or overriding existing keys." + default = {} +} + +variable "containers" { + type = list(any) + description = "Container definitions to use for the task. If this is used, all other container options will be ignored." + default = [] +} + +variable "port_mappings" { + description = "The port mappings to configure for the container. This is a list of maps. Each map should contain \"containerPort\", \"hostPort\", and \"protocol\", where \"protocol\" is one of \"tcp\" or \"udp\". If using containers in a task with the awsvpc or host network mode, the hostPort can either be left blank or set to the same value as the containerPort" + type = list(object({ + containerPort = number + hostPort = number + protocol = string + })) + default = [ + { + containerPort = 80 + hostPort = 80 + protocol = "tcp" + } + ] +} + +# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html +variable "healthcheck" { + description = "(Optional) A map containing command (string), timeout, interval (duration in seconds), retries (1-10, number of times to retry before marking container unhealthy), and startPeriod (0-300, optional grace period to wait, in seconds, before failed healthchecks count toward retries)" + type = object({ + command = list(string) + retries = number + timeout = number + interval = number + startPeriod = number + }) + default = null +} + +variable "container_cpu" { + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html#fargate-task-defs + type = number + description = "(Optional) The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of container_cpu of all containers in a task will need to be lower than the task-level cpu value" + default = 1024 # 1 vCPU +} + +variable "essential" { + type = bool + description = "Determines whether all other containers in a task are stopped, if this container fails or stops for any reason. Due to how Terraform type casts booleans in json it is required to double quote this value" + default = true +} + +variable "entrypoint" { + type = list(string) + description = "The entry point that is passed to the container" + default = null +} + +variable "command" { + type = list(string) + description = "The command that is passed to the container" + default = null +} + +variable "working_directory" { + type = string + description = "The working directory to run commands inside the container" + default = null +} + +variable "environment" { + type = list(object({ + name = string + value = string + })) + description = "The environment variables to pass to the container. This is a list of maps. map_environment overrides environment" + default = [] +} + +variable "extra_hosts" { + type = list(object({ + ipAddress = string + hostname = string + })) + description = "A list of hostnames and IP address mappings to append to the /etc/hosts file on the container. This is a list of maps" + default = null +} + +variable "map_environment" { + type = map(string) + description = "The environment variables to pass to the container. This is a map of string: {key: value}. map_environment overrides environment" + default = null +} + +# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_EnvironmentFile.html +variable "environment_files" { + type = list(object({ + value = string + type = string + })) + description = "One or more files containing the environment variables to pass to the container. This maps to the --env-file option to docker run. The file must be hosted in Amazon S3. This option is only available to tasks using the EC2 launch type. This is a list of maps" + default = [] +} + +variable "secrets" { + type = list(object({ + name = string + valueFrom = string + })) + description = "The secrets to pass to the container. This is a list of maps" + default = [] +} + +variable "readonly_root_filesystem" { + type = bool + description = "Determines whether a container is given read-only access to its root filesystem. Due to how Terraform type casts booleans in json it is required to double quote this value" + default = false +} + +# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LinuxParameters.html +variable "linux_parameters" { + type = object({ + capabilities = object({ + add = list(string) + drop = list(string) + }) + devices = list(object({ + containerPath = string + hostPath = string + permissions = list(string) + })) + initProcessEnabled = bool + maxSwap = number + sharedMemorySize = number + swappiness = number + tmpfs = list(object({ + containerPath = string + mountOptions = list(string) + size = number + })) + }) + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more details, see https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LinuxParameters.html" + default = null +} + +# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html +variable "log_configuration" { + type = any + description = "Log configuration options to send to a custom log driver for the container. For more details, see https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html" + default = null +} + +# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html +variable "firelens_configuration" { + type = object({ + type = string + options = map(string) + }) + description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more details, see https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html" + default = null +} + +variable "mount_points" { + type = list(any) + + description = "Container mount points. This is a list of maps, where each map should contain a `containerPath` and `sourceVolume`. The `readOnly` key is optional." + default = [] +} + +variable "dns_servers" { + type = list(string) + description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + default = [] +} + +variable "dns_search_domains" { + type = list(string) + description = "Container DNS search domains. A list of DNS search domains that are presented to the container" + default = [] +} + +variable "ulimits" { + type = list(object({ + name = string + hardLimit = number + softLimit = number + })) + description = "Container ulimit settings. This is a list of maps, where each map should contain \"name\", \"hardLimit\" and \"softLimit\"" + default = null +} + +variable "repository_credentials" { + type = map(string) + description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + default = null +} + +variable "volumes_from" { + type = list(object({ + sourceContainer = string + readOnly = bool + })) + description = "A list of VolumesFrom maps which contain \"sourceContainer\" (name of the container that has the volumes to mount) and \"readOnly\" (whether the container can write to the volume)" + default = [] +} + +variable "links" { + type = list(string) + description = "List of container names this container can communicate with without port mappings" + default = [] +} + +variable "user" { + type = string + description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set." + default = null +} + +variable "container_depends_on" { + type = list(object({ + containerName = string + condition = string + })) + description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + default = [] +} + +variable "docker_labels" { + type = map(string) + description = "The configuration options to send to the `docker_labels`" + default = null +} + +variable "start_timeout" { + type = number + description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + default = null +} + +variable "stop_timeout" { + type = number + description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + default = null +} + +variable "privileged" { + type = bool + description = "When this variable is `true`, the container is given elevated privileges on the host container instance (similar to the root user). This parameter is not supported for Windows containers or tasks using the Fargate launch type." + default = null +} + +variable "system_controls" { + type = list(map(string)) + description = "A list of namespaced kernel parameters to set in the container, mapping to the --sysctl option to docker run. This is a list of maps: { namespace = \"\", value = \"\"}" + default = [] +} + +variable "hostname" { + type = string + description = "The hostname to use for your container." + default = null +} + +variable "disable_networking" { + type = bool + description = "When this parameter is true, networking is disabled within the container." + default = null +} + +variable "interactive" { + type = bool + description = "When this parameter is true, this allows you to deploy containerized applications that require stdin or a tty to be allocated." + default = null +} + +variable "pseudo_terminal" { + type = bool + description = "When this parameter is true, a TTY is allocated. " + default = null +} + +variable "docker_security_options" { + type = list(string) + description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems." + default = [] +} + +#------------------------------------------------------------------------------ +# AWS ECS Task Definition Variables +#------------------------------------------------------------------------------ +variable "iam_partition" { + description = "IAM partition to use when referencing standard policies. GovCloud and some other regions use different partitions" + type = string + default = "aws" +} + +variable "permissions_boundary" { + description = "(Optional) The ARN of the policy that is used to set the permissions boundary for the `ecs_task_execution_role` role." + type = string + default = null +} + +variable "task_role_arn" { + description = "(Optional) The ARN of IAM role that allows your Amazon ECS container task to make calls to other AWS services. If not specified, `aws_iam_role.ecs_task_execution_role.arn` is used" + type = string + default = null +} + +variable "ecs_task_execution_role_custom_policies" { + description = "(Optional) Custom policies to attach to the ECS task execution role. For example for reading secrets from AWS Systems Manager Parameter Store or Secrets Manager" + type = list(string) + default = [] +} + +variable "placement_constraints" { + description = "(Optional) A set of placement constraints rules that are taken into consideration during task placement. Maximum number of placement_constraints is 10. This is a list of maps, where each map should contain \"type\" and \"expression\"" + type = list(any) + default = [] +} + +variable "proxy_configuration" { + description = "(Optional) The proxy configuration details for the App Mesh proxy. This is a list of maps, where each map should contain \"container_name\", \"properties\" and \"type\"" + type = list(any) + default = [] +} + +variable "ephemeral_storage_size" { + type = number + description = "The number of GBs to provision for ephemeral storage on Fargate tasks. Must be greater than or equal to 21 and less than or equal to 200" + default = 0 + + validation { + condition = var.ephemeral_storage_size == 0 || (var.ephemeral_storage_size >= 21 && var.ephemeral_storage_size <= 200) + error_message = "The ephemeral_storage_size value must be inclusively between 21 and 200." + } +} + +variable "volumes" { + description = "(Optional) A set of volume blocks that containers in your task may use" + type = list(object({ + host_path = string + name = string + docker_volume_configuration = list(object({ + autoprovision = bool + driver = string + driver_opts = map(string) + labels = map(string) + scope = string + })) + efs_volume_configuration = list(object({ + file_system_id = string + root_directory = string + transit_encryption = string + transit_encryption_port = string + authorization_config = list(object({ + access_point_id = string + iam = string + })) + })) + })) + default = [] +} \ No newline at end of file diff --git a/modules/aws-ecs-task-definition/versions.tf b/modules/aws-ecs-task-definition/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs-task-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs/README.md b/modules/aws-ecs/README.md new file mode 100644 index 0000000..e542897 --- /dev/null +++ b/modules/aws-ecs/README.md @@ -0,0 +1,247 @@ +# AWS ECS Terraform module + +Terraform module which creates ECS (Elastic Container Service) resources on AWS. + +## Available Features + +- ECS cluster +- Fargate capacity providers +- EC2 AutoScaling Group capacity providers + +## Usage + +### Fargate Capacity Providers + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "ecs-fargate" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +### EC2 Autoscaling Capacity Providers + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "ecs-ec2" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +### Fargate & EC2 Autoscaling Capacity Providers + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "ecs-mixed" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + # Disable creation of cluster and all resources + create = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.6 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.6 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | +| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitons to create for the cluster | `any` | `{}` | no | +| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_settings](#input\_cluster\_settings) | Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `map(string)` |
{
"name": "containerInsights",
"value": "enabled"
}
| no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | +| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | + \ No newline at end of file diff --git a/modules/aws-ecs/examples/complete/README.md b/modules/aws-ecs/examples/complete/README.md new file mode 100644 index 0000000..977980b --- /dev/null +++ b/modules/aws-ecs/examples/complete/README.md @@ -0,0 +1,66 @@ +# ECS Cluster w/ EC2 Autoscaling + +Configuration in this directory creates: + +- ECS cluster using autoscaling group capacity provider +- Autoscaling groups with IAM instance profile to be used by ECS cluster +- Example ECS service + +## 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. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.6 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.6 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | +| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [ecs](#module\_ecs) | ../.. | n/a | +| [ecs\_disabled](#module\_ecs\_disabled) | ../.. | n/a | +| [hello\_world](#module\_hello\_world) | ./service-hello-world | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ssm_parameter.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | + diff --git a/modules/aws-ecs/examples/complete/main.tf b/modules/aws-ecs/examples/complete/main.tf new file mode 100644 index 0000000..9c70e5e --- /dev/null +++ b/modules/aws-ecs/examples/complete/main.tf @@ -0,0 +1,199 @@ +provider "aws" { + region = local.region +} + +locals { + region = "eu-west-1" + name = "ecs-ex-${replace(basename(path.cwd), "_", "-")}" + + user_data = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + EOF + EOT + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# ECS Module +################################################################################ + +module "ecs" { + source = "../.." + + cluster_name = local.name + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + # You can set a simple string and ECS will create the CloudWatch log group for you + # or you can create the resource yourself as shown here to better manage retetion, tagging, etc. + # Embedding it into the module is not trivial and therefore it is externalized + cloud_watch_log_group_name = aws_cloudwatch_log_group.this.name + } + } + } + + default_capacity_provider_use_fargate = false + + # Capacity provider - Fargate + fargate_capacity_providers = { + FARGATE = {} + FARGATE_SPOT = {} + } + + # Capacity provider - autoscaling groups + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = module.autoscaling["one"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = module.autoscaling["two"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = local.tags +} + +module "hello_world" { + source = "./service-hello-world" + + cluster_id = module.ecs.cluster_id +} + +module "ecs_disabled" { + source = "../.." + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + one = { + instance_type = "t3.micro" + } + two = { + instance_type = "t3.small" + } + } + + name = "${local.name}-${each.key}" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = each.value.instance_type + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(local.user_data) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 0 + max_size = 2 + desired_capacity = 1 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["https-443-tcp"] + + egress_rules = ["all-all"] + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.99.0.0/18" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + public_subnets = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"] + private_subnets = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + map_public_ip_on_launch = false + + tags = local.tags +} + +resource "aws_cloudwatch_log_group" "this" { + name = "/aws/ecs/${local.name}" + retention_in_days = 7 + + tags = local.tags +} diff --git a/modules/aws-ecs/examples/complete/outputs.tf b/modules/aws-ecs/examples/complete/outputs.tf new file mode 100644 index 0000000..4436a96 --- /dev/null +++ b/modules/aws-ecs/examples/complete/outputs.tf @@ -0,0 +1,36 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs.cluster_arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs.cluster_id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs.cluster_name +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs.cluster_capacity_providers +} + +################################################################################ +# Capacity Provider +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs.autoscaling_capacity_providers +} diff --git a/modules/aws-ecs/examples/complete/service-hello-world/main.tf b/modules/aws-ecs/examples/complete/service-hello-world/main.tf new file mode 100644 index 0000000..b9661bb --- /dev/null +++ b/modules/aws-ecs/examples/complete/service-hello-world/main.tf @@ -0,0 +1,38 @@ +resource "aws_cloudwatch_log_group" "this" { + name_prefix = "hello_world-" + retention_in_days = 1 +} + +resource "aws_ecs_task_definition" "this" { + family = "hello_world" + + container_definitions = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.6 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.6 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ecs](#module\_ecs) | ../.. | n/a | +| [ecs\_disabled](#module\_ecs\_disabled) | ../.. | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | + diff --git a/modules/aws-ecs/examples/fargate/main.tf b/modules/aws-ecs/examples/fargate/main.tf new file mode 100644 index 0000000..f800c3e --- /dev/null +++ b/modules/aws-ecs/examples/fargate/main.tf @@ -0,0 +1,70 @@ +provider "aws" { + region = local.region +} + +locals { + region = "eu-west-1" + name = "ecs-ex-${replace(basename(path.cwd), "_", "-")}" + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Ecs Module +################################################################################ + +module "ecs" { + source = "../.." + + cluster_name = local.name + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + # You can set a simple string and ECS will create the CloudWatch log group for you + # or you can create the resource yourself as shown here to better manage retetion, tagging, etc. + # Embedding it into the module is not trivial and therefore it is externalized + cloud_watch_log_group_name = aws_cloudwatch_log_group.this.name + } + } + } + + # Capacity provider + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = local.tags +} + +module "ecs_disabled" { + source = "../.." + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +resource "aws_cloudwatch_log_group" "this" { + name = "/aws/ecs/${local.name}" + retention_in_days = 7 + + tags = local.tags +} diff --git a/modules/aws-ecs/examples/fargate/outputs.tf b/modules/aws-ecs/examples/fargate/outputs.tf new file mode 100644 index 0000000..4436a96 --- /dev/null +++ b/modules/aws-ecs/examples/fargate/outputs.tf @@ -0,0 +1,36 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs.cluster_arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs.cluster_id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs.cluster_name +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs.cluster_capacity_providers +} + +################################################################################ +# Capacity Provider +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs.autoscaling_capacity_providers +} diff --git a/modules/aws-ecs/examples/fargate/variables.tf b/modules/aws-ecs/examples/fargate/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ecs/examples/fargate/versions.tf b/modules/aws-ecs/examples/fargate/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs/examples/fargate/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs/main.tf b/modules/aws-ecs/main.tf new file mode 100644 index 0000000..0ae0520 --- /dev/null +++ b/modules/aws-ecs/main.tf @@ -0,0 +1,112 @@ +################################################################################ +# Cluster +################################################################################ + +resource "aws_ecs_cluster" "this" { + count = var.create ? 1 : 0 + + name = var.cluster_name + + dynamic "configuration" { + for_each = try([var.cluster_configuration], []) + + content { + dynamic "execute_command_configuration" { + for_each = try([configuration.value.execute_command_configuration], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "setting" { + for_each = [var.cluster_settings] + + content { + name = setting.value.name + value = setting.value.value + } + } + + tags = var.tags +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +locals { + default_capacity_providers = merge( + { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, + { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } + ) +} + +resource "aws_ecs_cluster_capacity_providers" "this" { + count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 + + cluster_name = aws_ecs_cluster.this[0].name + capacity_providers = distinct(concat( + [for k, v in var.fargate_capacity_providers : try(v.name, k)], + [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] + )) + + dynamic "default_capacity_provider_strategy" { + for_each = local.default_capacity_providers + iterator = strategy + + content { + capacity_provider = try(strategy.value.name, strategy.key) + base = try(strategy.value.default_capacity_provider_strategy.base, null) + weight = try(strategy.value.default_capacity_provider_strategy.weight, null) + } + } + + depends_on = [ + aws_ecs_capacity_provider.this + ] +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +resource "aws_ecs_capacity_provider" "this" { + for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } + + name = try(each.value.name, each.key) + + auto_scaling_group_provider { + auto_scaling_group_arn = each.value.auto_scaling_group_arn + managed_termination_protection = try(each.value.managed_termination_protection, null) + + dynamic "managed_scaling" { + for_each = try([each.value.managed_scaling], []) + + content { + instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) + maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) + minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) + status = try(managed_scaling.value.status, null) + target_capacity = try(managed_scaling.value.target_capacity, null) + } + } + } + + tags = merge(var.tags, try(each.value.tags, {})) +} \ No newline at end of file diff --git a/modules/aws-ecs/modules/service/README.md b/modules/aws-ecs/modules/service/README.md new file mode 100644 index 0000000..200f096 --- /dev/null +++ b/modules/aws-ecs/modules/service/README.md @@ -0,0 +1,66 @@ +# ECS Service Module + +Configuration in this directory creates an ECS Service EKS Profile + +⚠️ Module is under active development ⚠️ + +## TODO + +- [ ] `aws_ecs_service` (one default, one with `ignore_changes` for things like `desired_count`) +- [ ] `aws_ecs_task_definition` +- [ ] `aws_ecs_task_set` +- [ ] `aws_appautoscaling_target` & `aws_appautoscaling_policy` (`for_each` over a shared map where each key = 1 target and 2 policies, 1 policy for scale up, 1 for scale down) +- [ ] Task role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) +- [ ] Task exectution role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) +- [ ] ECS CloudWatch events role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) + +## Usage + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + name = "MyService" + cluster_id = module.ecs.cluster_id + + # TODO + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### Logging + +Please refer to https://github.com/aws-samples/amazon-ecs-firelens-examples for logging architectures for FireLens on Amazon ECS and AWS Fargate. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.6 | + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/modules/aws-ecs/modules/service/main.tf b/modules/aws-ecs/modules/service/main.tf new file mode 100644 index 0000000..066066c --- /dev/null +++ b/modules/aws-ecs/modules/service/main.tf @@ -0,0 +1 @@ +locals {} diff --git a/modules/aws-ecs/modules/service/outputs.tf b/modules/aws-ecs/modules/service/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ecs/modules/service/variables.tf b/modules/aws-ecs/modules/service/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ecs/modules/service/versions.tf b/modules/aws-ecs/modules/service/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs/modules/service/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-ecs/outputs.tf b/modules/aws-ecs/outputs.tf new file mode 100644 index 0000000..6f53237 --- /dev/null +++ b/modules/aws-ecs/outputs.tf @@ -0,0 +1,43 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = try(aws_ecs_cluster.this[0].arn, null) +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = try(aws_ecs_cluster.this[0].id, null) +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = try(aws_ecs_cluster.this[0].name, null) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = { + for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v + } +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = aws_ecs_capacity_provider.this +} + +#output "aws_lb_target_group_arn" { +# description = "value of aws_lb_target_group arn" +# value = aws_lb_target_group.alb-tg.arn +#} diff --git a/modules/aws-ecs/variables.tf b/modules/aws-ecs/variables.tf new file mode 100644 index 0000000..13b2a67 --- /dev/null +++ b/modules/aws-ecs/variables.tf @@ -0,0 +1,79 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = "" +} + +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = any + default = {} +} + +variable "cluster_settings" { + description = "Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" + type = map(string) + default = { + name = "containerInsights" + value = "enabled" + } +} + +################################################################################ +# Capacity Providers +################################################################################ + +variable "default_capacity_provider_use_fargate" { + description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" + type = bool + default = true +} + +variable "fargate_capacity_providers" { + description = "Map of Fargate capacity provider definitions to use for the cluster" + type = any + default = {} +} + +variable "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity provider definitons to create for the cluster" + type = any + default = {} +} + + +variable "vpc_id" { + description = "vpc_id of the primary VPC" + default = "" +} + +variable "vpc_cidr_block" { + description = "vpc cidr block of the primary VPC" + default = [] +} +variable "alb_subnets" { + description = "subnet ids of the alb" + type = list(string) + default = [] +} + +variable "alb_name" { + description = "Alb name" + default = "" +} \ No newline at end of file diff --git a/modules/aws-ecs/versions.tf b/modules/aws-ecs/versions.tf new file mode 100644 index 0000000..35402be --- /dev/null +++ b/modules/aws-ecs/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} diff --git a/modules/aws-efs/README.md b/modules/aws-efs/README.md new file mode 100644 index 0000000..7366dfb --- /dev/null +++ b/modules/aws-efs/README.md @@ -0,0 +1,189 @@ +# AWS EFS Terraform module + +Terraform module which creates AWS EFS (elastic file system) resources. +## Usage + +See [`examples`](https://github.com/kloia/platform-modules/tree/main/aws-efs/examples) directory for working examples to reference: + +```hcl +module "efs" { + source = "terraform-aws-modules/efs/aws" + + # File system + name = "example" + creation_token = "example-token" + encrypted = true + kms_key_arn = "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" + + performance_mode = "maxIO" + throughput_mode = "provisioned" + provisioned_throughput_in_mibps = 256 + + lifecycle_policy = { + transition_to_ia = "AFTER_30_DAYS" + } + + # File system policy + attach_policy = true + bypass_policy_lockout_safety_check = false + policy_statements = [ + { + sid = "Example" + actions = ["elasticfilesystem:ClientMount"] + principals = [ + { + type = "AWS" + identifiers = ["arn:aws:iam::111122223333:role/EfsReadOnly"] + } + ] + } + ] + + # Mount targets / security group + mount_targets = { + "eu-west-1a" = { + subnet_id = "subnet-abcde012" + } + "eu-west-1b" = { + subnet_id = "subnet-bcde012a" + } + "eu-west-1c" = { + subnet_id = "subnet-fghi345a" + } + } + security_group_description = "Example EFS security group" + security_group_vpc_id = "vpc-1234556abcdef" + security_group_rules = { + vpc = { + # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) + description = "NFS ingress from VPC private subnets" + cidr_blocks = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] + } + } + + # Access point(s) + access_points = { + posix_example = { + name = "posix-example" + posix_user = { + gid = 1001 + uid = 1001 + secondary_gids = [1002] + } + + tags = { + Additionl = "yes" + } + } + root_example = { + root_directory = { + path = "/example" + creation_info = { + owner_gid = 1001 + owner_uid = 1001 + permissions = "755" + } + } + } + } + + # Backup policy + enable_backup_policy = true + + # Replication configuration + create_replication_configuration = true + replication_configuration_destination = { + region = "eu-west-2" + } + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +## Examples + +Examples codified under the [`examples`](https://github.com/kloia/platform-modules/tree/main/aws-efs/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. 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! + +- [Complete](https://github.com/kloia/platform-modules/tree/main/aws-efs/examples/complete) + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.42 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.42 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_efs_access_point.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource | +| [aws_efs_backup_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_backup_policy) | resource | +| [aws_efs_file_system.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | +| [aws_efs_file_system_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system_policy) | resource | +| [aws_efs_mount_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | +| [aws_efs_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_replication_configuration) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_iam_policy_document.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_points](#input\_access\_points) | A map of access point definitions to create | `any` | `{}` | no | +| [attach\_policy](#input\_attach\_policy) | Determines whether a policy is attached to the file system | `bool` | `true` | no | +| [availability\_zone\_name](#input\_availability\_zone\_name) | The AWS Availability Zone in which to create the file system. Used to create a file system that uses One Zone storage classes | `string` | `null` | no | +| [bypass\_policy\_lockout\_safety\_check](#input\_bypass\_policy\_lockout\_safety\_check) | A flag to indicate whether to bypass the `aws_efs_file_system_policy` lockout safety check. Defaults to `false` | `bool` | `null` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_backup\_policy](#input\_create\_backup\_policy) | Determines whether a backup policy is created | `bool` | `true` | no | +| [create\_replication\_configuration](#input\_create\_replication\_configuration) | Determines whether a replication configuration is created | `bool` | `false` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines whether a security group is created | `bool` | `true` | no | +| [creation\_token](#input\_creation\_token) | A unique name (a maximum of 64 characters are allowed) used as reference when creating the Elastic File System to ensure idempotent file system creation. By default generated by Terraform | `string` | `null` | no | +| [deny\_nonsecure\_transport](#input\_deny\_nonsecure\_transport) | Determines whether `aws:SecureTransport` is required when connecting to elastic file system | `bool` | `true` | no | +| [enable\_backup\_policy](#input\_enable\_backup\_policy) | Determines whether a backup policy is `ENABLED` or `DISABLED` | `bool` | `true` | no | +| [encrypted](#input\_encrypted) | If `true`, the disk will be encrypted | `bool` | `true` | no | +| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN for the KMS encryption key. When specifying `kms_key_arn`, encrypted needs to be set to `true` | `string` | `null` | no | +| [lifecycle\_policy](#input\_lifecycle\_policy) | A file system [lifecycle policy](https://docs.aws.amazon.com/efs/latest/ug/API_LifecyclePolicy.html) object | `any` | `{}` | no | +| [mount\_targets](#input\_mount\_targets) | A map of mount target definitions to create | `any` | `{}` | no | +| [name](#input\_name) | The name of the file system | `string` | `""` | no | +| [override\_policy\_documents](#input\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [performance\_mode](#input\_performance\_mode) | The file system performance mode. Can be either `generalPurpose` or `maxIO`. Default is `generalPurpose` | `string` | `null` | no | +| [policy\_statements](#input\_policy\_statements) | A list of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `[]` | no | +| [provisioned\_throughput\_in\_mibps](#input\_provisioned\_throughput\_in\_mibps) | The throughput, measured in MiB/s, that you want to provision for the file system. Only applicable with `throughput_mode` set to `provisioned` | `number` | `null` | no | +| [replication\_configuration\_destination](#input\_replication\_configuration\_destination) | A destination configuration block | `any` | `{}` | no | +| [security\_group\_description](#input\_security\_group\_description) | Security group description. Defaults to Managed by Terraform | `string` | `null` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to assign to the security group. If omitted, Terraform will assign a random, unique name | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | Map of security group rule definitions to create | `any` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether to use a name prefix for the security group. If `true`, the `security_group_name` value will be used as a prefix | `bool` | `false` | no | +| [security\_group\_vpc\_id](#input\_security\_group\_vpc\_id) | The VPC ID where the security group will be created | `string` | `null` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [throughput\_mode](#input\_throughput\_mode) | Throughput mode for the file system. Defaults to `bursting`. Valid values: `bursting`, `elastic`, and `provisioned`. When using `provisioned`, also set `provisioned_throughput_in_mibps` | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [access\_points](#output\_access\_points) | Map of access points created and their attributes | +| [arn](#output\_arn) | Amazon Resource Name of the file system | +| [dns\_name](#output\_dns\_name) | The DNS name for the filesystem per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html) | +| [id](#output\_id) | The ID that identifies the file system (e.g., `fs-ccfc0d65`) | +| [mount\_targets](#output\_mount\_targets) | Map of mount targets created and their attributes | +| [replication\_configuration\_destination\_file\_system\_id](#output\_replication\_configuration\_destination\_file\_system\_id) | The file system ID of the replica | +| [security\_group\_arn](#output\_security\_group\_arn) | ARN of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [size\_in\_bytes](#output\_size\_in\_bytes) | The latest known metered size (in bytes) of data stored in the file system, the value is not the exact size that the file system was at any point in time | diff --git a/modules/aws-efs/examples/complete/README.md b/modules/aws-efs/examples/complete/README.md new file mode 100644 index 0000000..ffae145 --- /dev/null +++ b/modules/aws-efs/examples/complete/README.md @@ -0,0 +1,71 @@ +# Complete AWS EFS Example + +Configuration in this directory creates: + +- A "complete" EFS file system which demonstrates the various configurations that are supported by the module +- A "default" EFS file system which demonstrates the default configurations provided by the module +- A disabled EFS file system + +## 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. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.42 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.42 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [efs](#module\_efs) | ../.. | n/a | +| [efs\_default](#module\_efs\_default) | ../.. | n/a | +| [efs\_disabled](#module\_efs\_disabled) | ../.. | n/a | +| [kms](#module\_kms) | terraform-aws-modules/kms/aws | ~> 1.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [access\_points](#output\_access\_points) | Map of access points created and their attributes | +| [arn](#output\_arn) | Amazon Resource Name of the file system | +| [dns\_name](#output\_dns\_name) | The DNS name for the filesystem per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html) | +| [id](#output\_id) | The ID that identifies the file system (e.g., `fs-ccfc0d65`) | +| [mount\_targets](#output\_mount\_targets) | Map of mount targets created and their attributes | +| [replication\_configuration\_destination\_file\_system\_id](#output\_replication\_configuration\_destination\_file\_system\_id) | The file system ID of the replica | +| [security\_group\_arn](#output\_security\_group\_arn) | ARN of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [size\_in\_bytes](#output\_size\_in\_bytes) | The latest known metered size (in bytes) of data stored in the file system, the value is not the exact size that the file system was at any point in time | + + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-efs/blob/master/LICENSE). diff --git a/modules/aws-efs/examples/complete/main.tf b/modules/aws-efs/examples/complete/main.tf new file mode 100644 index 0000000..a2b662c --- /dev/null +++ b/modules/aws-efs/examples/complete/main.tf @@ -0,0 +1,159 @@ +provider "aws" { + region = local.region +} + +locals { + region = "eu-west-1" + name = "efs-ex-${replace(basename(path.cwd), "_", "-")}" + + azs = slice(data.aws_availability_zones.available.names, 0, 3) + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/kloia/platform-modules/tree/main/aws-efs" + } + private_subnets = [""] # subnet ids for provisioning efs + vpc_id = "" # vpc id + private_subnet_cidr_blocks = [""] # private subnets' cidr blocks for ingress rules +} + +data "aws_availability_zones" "available" {} +data "aws_caller_identity" "current" {} + +################################################################################ +# EFS Module +################################################################################ + +module "efs" { + source = "../.." + + # File system + name = local.name + creation_token = local.name + encrypted = true + kms_key_arn = "kms-key-arn" + + performance_mode = "maxIO" + throughput_mode = "provisioned" + provisioned_throughput_in_mibps = 256 + + lifecycle_policy = { + transition_to_ia = "AFTER_30_DAYS" + transition_to_primary_storage_class = "AFTER_1_ACCESS" + } + + # File system policy + attach_policy = true + bypass_policy_lockout_safety_check = false + policy_statements = [ + { + sid = "Example" + actions = ["elasticfilesystem:ClientMount"] + principals = [ + { + type = "AWS" + identifiers = [data.aws_caller_identity.current.arn] + } + ] + } + ] + + # Mount targets / security group + mount_targets = { for k, v in zipmap(local.azs, local.private_subnets) : k => { subnet_id = v } } + security_group_description = "Example EFS security group" + security_group_vpc_id = local.vpc_id + security_group_rules = { + vpc = { + # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) + description = "NFS ingress from VPC private subnets" + cidr_blocks = local.private_subnet_cidr_blocks + } + } + + # Access point(s) + access_points = { + posix_example = { + name = "posix-example" + posix_user = { + gid = 1001 + uid = 1001 + secondary_gids = [1002] + } + + tags = { + Additionl = "yes" + } + } + root_example = { + root_directory = { + path = "/example" + creation_info = { + owner_gid = 1001 + owner_uid = 1001 + permissions = "755" + } + } + } + } + + # Backup policy + enable_backup_policy = true + + # Replication configuration + create_replication_configuration = true + replication_configuration_destination = { + region = "eu-west-2" + } + + tags = local.tags +} + +module "efs_default" { + source = "../.." + + name = "${local.name}-default" + + tags = local.tags +} + +module "efs_disabled" { + source = "../.." + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = "10.99.0.0/18" + + azs = local.azs + public_subnets = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"] + private_subnets = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] + + enable_nat_gateway = false + single_nat_gateway = true + map_public_ip_on_launch = false + + tags = local.tags +} + +module "kms" { + source = "terraform-aws-modules/kms/aws" + version = "~> 1.0" + + aliases = ["efs/${local.name}"] + description = "EFS customer managed key" + enable_default_policy = true + + # For example use only + deletion_window_in_days = 7 + + tags = local.tags +} diff --git a/modules/aws-efs/examples/complete/outputs.tf b/modules/aws-efs/examples/complete/outputs.tf new file mode 100644 index 0000000..e8b5e0f --- /dev/null +++ b/modules/aws-efs/examples/complete/outputs.tf @@ -0,0 +1,64 @@ +################################################################################ +# File System +################################################################################ + +output "arn" { + description = "Amazon Resource Name of the file system" + value = module.efs.arn +} + +output "id" { + description = "The ID that identifies the file system (e.g., `fs-ccfc0d65`)" + value = module.efs.id +} + +output "dns_name" { + description = "The DNS name for the filesystem per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html)" + value = module.efs.dns_name +} + +output "size_in_bytes" { + description = "The latest known metered size (in bytes) of data stored in the file system, the value is not the exact size that the file system was at any point in time" + value = module.efs.size_in_bytes +} + +################################################################################ +# Mount Target(s) +################################################################################ + +output "mount_targets" { + description = "Map of mount targets created and their attributes" + value = module.efs.mount_targets +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "ARN of the security group" + value = module.efs.security_group_arn +} + +output "security_group_id" { + description = "ID of the security group" + value = module.efs.security_group_id +} + +################################################################################ +# Access Point(s) +################################################################################ + +output "access_points" { + description = "Map of access points created and their attributes" + value = module.efs.access_points +} + +################################################################################ +# Replication Configuration +################################################################################ + +output "replication_configuration_destination_file_system_id" { + description = "The file system ID of the replica" + value = module.efs.replication_configuration_destination_file_system_id +} diff --git a/modules/aws-efs/examples/complete/variables.tf b/modules/aws-efs/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-efs/examples/complete/versions.tf b/modules/aws-efs/examples/complete/versions.tf new file mode 100644 index 0000000..b8fc66b --- /dev/null +++ b/modules/aws-efs/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.42" + } + } +} diff --git a/modules/aws-efs/main.tf b/modules/aws-efs/main.tf new file mode 100644 index 0000000..4c47191 --- /dev/null +++ b/modules/aws-efs/main.tf @@ -0,0 +1,270 @@ +################################################################################ +# File System +################################################################################ + +resource "aws_efs_file_system" "this" { + count = var.create ? 1 : 0 + + availability_zone_name = var.availability_zone_name + creation_token = var.creation_token + performance_mode = var.performance_mode + encrypted = var.encrypted + kms_key_id = var.kms_key_arn + provisioned_throughput_in_mibps = var.provisioned_throughput_in_mibps + throughput_mode = var.throughput_mode + + dynamic "lifecycle_policy" { + for_each = [for k, v in var.lifecycle_policy : { (k) = v }] + + content { + transition_to_ia = try(lifecycle_policy.value.transition_to_ia, null) + transition_to_primary_storage_class = try(lifecycle_policy.value.transition_to_primary_storage_class, null) + } + } + + tags = merge( + var.tags, + { Name = var.name }, + ) +} + +data "aws_subnets" "private_subnets_with_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } + +} + +data "aws_subnet" "cidr_blocks" { + count = length(data.aws_subnets.private_subnets_with_tag.ids) + id = data.aws_subnets.private_subnets_with_tag.ids[count.index] +} + + +output "aws_caller_identity_arn" { + value = data.aws_caller_identity.current.arn +} + + +################################################################################ +# File System Policy +################################################################################ + +data "aws_iam_policy_document" "policy" { + count = var.create && var.attach_policy ? 1 : 0 + + source_policy_documents = var.source_policy_documents + override_policy_documents = var.override_policy_documents + + dynamic "statement" { + for_each = var.policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, [aws_efs_file_system.this[0].arn], null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, statement.value.condition, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } + + dynamic "statement" { + for_each = var.deny_nonsecure_transport ? [1] : [] + + content { + sid = "NonSecureTransport" + effect = "Deny" + actions = ["*"] + resources = [aws_efs_file_system.this[0].arn] + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = ["false"] + } + } + } +} + +resource "aws_efs_file_system_policy" "this" { + count = var.create && var.attach_policy ? 1 : 0 + + file_system_id = aws_efs_file_system.this[0].id + bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check + policy = data.aws_iam_policy_document.policy[0].json +} + +################################################################################ +# Mount Target(s) +################################################################################ + +resource "aws_efs_mount_target" "this" { + for_each = { for k, v in local.mount_targets : k => v if var.create } + + file_system_id = aws_efs_file_system.this[0].id + ip_address = try(each.value.ip_address, null) + security_groups = var.create_security_group ? concat([aws_security_group.this[0].id], try(each.value.security_groups, [])) : try(each.value.security_groups, null) + subnet_id = each.value.subnet_id +} + +################################################################################ +# Security Group +################################################################################ + +locals { + security_group_name = try(coalesce(var.security_group_name, var.name), "") + mount_targets = { for k, v in zipmap(var.azs, data.aws_subnets.private_subnets_with_tag.ids) : k => { subnet_id = v } } + +} + + +data "aws_availability_zones" "available" {} +data "aws_caller_identity" "current" {} + + +resource "aws_security_group" "this" { + count = var.create && var.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + + revoke_rules_on_delete = true + vpc_id = var.security_group_vpc_id + + tags = var.tags +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if var.create && var.create_security_group } + + security_group_id = aws_security_group.this[0].id + + description = try(each.value.description, null) + type = try(each.value.type, "ingress") + from_port = try(each.value.from_port, 2049) + to_port = try(each.value.to_port, 2049) + protocol = try(each.value.protocol, "tcp") + cidr_blocks = try(each.value.cidr_blocks, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, null) + self = try(each.value.self, null) + source_security_group_id = try(each.value.source_security_group_id, null) +} + +################################################################################ +# Access Point(s) +################################################################################ + +resource "aws_efs_access_point" "this" { + for_each = { for k, v in var.access_points : k => v if var.create } + + file_system_id = aws_efs_file_system.this[0].id + + dynamic "posix_user" { + for_each = try([each.value.posix_user], []) + + content { + gid = posix_user.value.gid + uid = posix_user.value.uid + secondary_gids = try(posix_user.value.secondary_gids, null) + } + } + + dynamic "root_directory" { + for_each = try([each.value.root_directory], []) + + content { + path = try(root_directory.value.path, null) + + dynamic "creation_info" { + for_each = try([root_directory.value.creation_info], []) + + content { + owner_gid = creation_info.value.owner_gid + owner_uid = creation_info.value.owner_uid + permissions = creation_info.value.permissions + } + } + } + } + + tags = merge( + var.tags, + try(each.value.tags, {}), + { Name = try(each.value.name, each.key) }, + ) +} + +################################################################################ +# Backup Policy +################################################################################ + +resource "aws_efs_backup_policy" "this" { + count = var.create && var.create_backup_policy ? 1 : 0 + + file_system_id = aws_efs_file_system.this[0].id + + backup_policy { + status = var.enable_backup_policy ? "ENABLED" : "DISABLED" + } +} + +################################################################################ +# Replication Configuration +################################################################################ + +resource "aws_efs_replication_configuration" "this" { + count = var.create && var.create_replication_configuration ? 1 : 0 + + source_file_system_id = aws_efs_file_system.this[0].id + + dynamic "destination" { + for_each = [var.replication_configuration_destination] + + content { + availability_zone_name = try(destination.value.availability_zone_name, null) + kms_key_id = try(destination.value.kms_key_id, null) + region = try(destination.value.region, null) + } + } +} diff --git a/modules/aws-efs/outputs.tf b/modules/aws-efs/outputs.tf new file mode 100644 index 0000000..5ff5f7d --- /dev/null +++ b/modules/aws-efs/outputs.tf @@ -0,0 +1,64 @@ +################################################################################ +# File System +################################################################################ + +output "arn" { + description = "Amazon Resource Name of the file system" + value = try(aws_efs_file_system.this[0].arn, null) +} + +output "id" { + description = "The ID that identifies the file system (e.g., `fs-ccfc0d65`)" + value = try(aws_efs_file_system.this[0].id, null) +} + +output "dns_name" { + description = "The DNS name for the filesystem per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html)" + value = try(aws_efs_file_system.this[0].dns_name, null) +} + +output "size_in_bytes" { + description = "The latest known metered size (in bytes) of data stored in the file system, the value is not the exact size that the file system was at any point in time" + value = try(aws_efs_file_system.this[0].size_in_bytes, null) +} + +################################################################################ +# Mount Target(s) +################################################################################ + +output "mount_targets" { + description = "Map of mount targets created and their attributes" + value = aws_efs_mount_target.this +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "ARN of the security group" + value = try(aws_security_group.this[0].arn, null) +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, null) +} + +################################################################################ +# Access Point(s) +################################################################################ + +output "access_points" { + description = "Map of access points created and their attributes" + value = aws_efs_access_point.this +} + +################################################################################ +# Replication Configuration +################################################################################ + +output "replication_configuration_destination_file_system_id" { + description = "The file system ID of the replica" + value = try(aws_efs_replication_configuration.this[0].destination[0].file_system_id, null) +} diff --git a/modules/aws-efs/variables.tf b/modules/aws-efs/variables.tf new file mode 100644 index 0000000..5a5ed8d --- /dev/null +++ b/modules/aws-efs/variables.tf @@ -0,0 +1,219 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "name" { + description = "The name of the file system" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# File System +################################################################################ + +variable "availability_zone_name" { + description = "The AWS Availability Zone in which to create the file system. Used to create a file system that uses One Zone storage classes" + type = string + default = null +} + +variable "creation_token" { + description = "A unique name (a maximum of 64 characters are allowed) used as reference when creating the Elastic File System to ensure idempotent file system creation. By default generated by Terraform" + type = string + default = null +} + +variable "performance_mode" { + description = "The file system performance mode. Can be either `generalPurpose` or `maxIO`. Default is `generalPurpose`" + type = string + default = null +} + +variable "encrypted" { + description = "If `true`, the disk will be encrypted" + type = bool + default = true +} + +variable "kms_key_arn" { + description = "The ARN for the KMS encryption key. When specifying `kms_key_arn`, encrypted needs to be set to `true`" + type = string + default = null +} + +variable "provisioned_throughput_in_mibps" { + description = "The throughput, measured in MiB/s, that you want to provision for the file system. Only applicable with `throughput_mode` set to `provisioned`" + type = number + default = null +} + +variable "throughput_mode" { + description = "Throughput mode for the file system. Defaults to `bursting`. Valid values: `bursting`, `elastic`, and `provisioned`. When using `provisioned`, also set `provisioned_throughput_in_mibps`" + type = string + default = null +} + +variable "lifecycle_policy" { + description = "A file system [lifecycle policy](https://docs.aws.amazon.com/efs/latest/ug/API_LifecyclePolicy.html) object" + type = any + default = {} +} + +################################################################################ +# File System Policy +################################################################################ + +variable "attach_policy" { + description = "Determines whether a policy is attached to the file system" + type = bool + default = true +} + +variable "bypass_policy_lockout_safety_check" { + description = "A flag to indicate whether to bypass the `aws_efs_file_system_policy` lockout safety check. Defaults to `false`" + type = bool + default = null +} + +variable "source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "policy_statements" { + description = "A list of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = [] +} + +variable "deny_nonsecure_transport" { + description = "Determines whether `aws:SecureTransport` is required when connecting to elastic file system" + type = bool + default = true +} + +################################################################################ +# Mount Target(s) +################################################################################ + +variable "mount_targets" { + description = "A map of mount target definitions to create" + type = any + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines whether a security group is created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to assign to the security group. If omitted, Terraform will assign a random, unique name" + type = string + default = null +} + +variable "security_group_description" { + description = "Security group description. Defaults to Managed by Terraform" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether to use a name prefix for the security group. If `true`, the `security_group_name` value will be used as a prefix" + type = bool + default = false +} + +variable "security_group_vpc_id" { + description = "The VPC ID where the security group will be created" + type = string + default = null +} + +variable "security_group_rules" { + description = "Map of security group rule definitions to create" + type = any + default = {} +} + +################################################################################ +# Access Point(s) +################################################################################ + +variable "access_points" { + description = "A map of access point definitions to create" + type = any + default = {} +} + +################################################################################ +# Backup Policy +################################################################################ + +variable "create_backup_policy" { + description = "Determines whether a backup policy is created" + type = bool + default = true +} + +variable "enable_backup_policy" { + description = "Determines whether a backup policy is `ENABLED` or `DISABLED`" + type = bool + default = true +} + +################################################################################ +# Replication Configuration +################################################################################ + +variable "create_replication_configuration" { + description = "Determines whether a replication configuration is created" + type = bool + default = false +} + +variable "replication_configuration_destination" { + description = "A destination configuration block" + type = any + default = {} +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +variable "azs" { + description = "availability zones" + type = any + default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] +} + +variable "vpc_id" { + description = "vpc id" + type = string + default = "vpc-fi23tg34g3" +} \ No newline at end of file diff --git a/modules/aws-efs/versions.tf b/modules/aws-efs/versions.tf new file mode 100644 index 0000000..b8fc66b --- /dev/null +++ b/modules/aws-efs/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.42" + } + } +} diff --git a/modules/aws-eks-addons/README.md b/modules/aws-eks-addons/README.md new file mode 100644 index 0000000..392c4eb --- /dev/null +++ b/modules/aws-eks-addons/README.md @@ -0,0 +1,96 @@ +# AWS EKS Terraform module + +Terraform module which creates a service account and deploys aws-load-balancer-controller attached to that service account into the cluster. + +### External Documentation + +## Available Features +Creates an service account and deploys aws-load-balancer-controller helm chart. + +## Usage + +new: If you enable SSO, you need to provide "values.yaml.tpl" file. Example: values.yaml.tpl. It's false by default +``` +configs: + cm: + dex.config: | + logger: + level: debug + format: json + connectors: + - type: saml + id: aws + name: "AWS IAM Identity Center" + config: + ssoURL: ${ssoURL} + caData: ${caData} + redirectURI: ${redirectURI} + entityIssuer: ${entityIssuer} + groupsAttr: groups + usernameAttr: email + emailAttr: email + + rbac: + policy.csv: | + g, guest, role:readonly + # policy.default: role:readonly + scopes: '[groups,email]' +``` + +```hcl + +inputs = { + loadbalancer_irsa_arn = "${dependency.eks-irsa.outputs.iam_role_arn}" + vpc_id = "${dependency.eks-vpc.outputs.vpc_id}" + cluster_region = "${local.region}" + cluster_name = "${dependency.eks.outputs.cluster_name}" + cluster_endpoint = "${dependency.eks.outputs.cluster_endpoint}" + cluster_ca_cert= "${dependency.eks.outputs.cluster_certificate_authority_data}" + image_repository = "602401143452.dkr.ecr.eu-west-1.amazonaws.com/amazon/aws-load-balancer-controller" + // regional list of the image repositories + // https://docs.aws.amazon.com/eks/latest/userguide/add-ons-images.html +} + +dependency "eks" { + config_path = "../eks-cluster" + mock_outputs = { + cluster_name = "known after apply" + } +} + +dependency "eks-irsa" { + config_path = "../eks-irsa" + mock_outputs = { + iam_role_arn = "known after apply" + } +} + +dependency "eks-vpc" { + config_path = "../../vpc" + mock_outputs = { + vpc_id = "known after apply" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.2.9 | +| [terragrunt](#requirement\_terragrunt) | >= 0.38.9 | + +## Providers + +| Name | Version | +|------|---------| +| [kubernetes](#provider\_kubernetes) | >= 2.10 | +| [tls](#provider\_tls) | >= 2.6.0 | + +## Resources + +| Name | Type | +|------|------| +| [kubernetes_service_account.service-account](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account) | resource | +| [helm_release.lb](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | diff --git a/modules/aws-eks-addons/main.tf b/modules/aws-eks-addons/main.tf new file mode 100644 index 0000000..0a570a5 --- /dev/null +++ b/modules/aws-eks-addons/main.tf @@ -0,0 +1,991 @@ +locals { + can_connect_alb_to_nginx = var.deploy_aws_loadbalancer && (length(var.connect_hostnames_from_alb_to_nginx) > 0) + can_connect_alb_to_istio = var.deploy_aws_loadbalancer && var.deploy_rancher_istio && (length(var.connect_hostnames_from_alb_to_istio) > 0) + can_connect_nginx_to_argocd = var.deploy_aws_loadbalancer && var.deploy_argocd + caData = var.enable_sso ? data.aws_ssm_parameter.sso_ca_data_network_account[0].value : "" + ssoURL = var.enable_sso ? data.aws_ssm_parameter.sso_url_network_account[0].value : "" +} + +resource "kubernetes_service_account" "service-common-service-account" { + metadata { + name = "service-common" + namespace = "default" + annotations = { + "eks.amazonaws.com/role-arn" = var.service_common_iam_role_arn + } + } +} + +resource "kubernetes_service_account" "service-account" { + metadata { + name = "aws-load-balancer-controller" + namespace = "kube-system" + labels = { + "app.kubernetes.io/name" = "aws-load-balancer-controller" + "app.kubernetes.io/component" = "controller" + } + annotations = { + "eks.amazonaws.com/role-arn" = var.loadbalancer_irsa_arn + "eks.amazonaws.com/sts-regional-endpoints" = "true" + } + } + +} + +resource "helm_release" "aws_lb_controller" { + count = var.deploy_aws_loadbalancer ? 1 : 0 + name = "aws-load-balancer-controller" + repository = "https://aws.github.io/eks-charts" + chart = "aws-load-balancer-controller" + namespace = "kube-system" + version = var.aws_lb_controller_version + depends_on = [ + kubernetes_service_account.service-account + ] + + set { + name = "region" + value = var.cluster_region + } + + set { + name = "vpcId" + value = var.vpc_id + } + + set { + name = "image.repository" + value = var.image_repository + } + + set { + name = "serviceAccount.create" + value = "false" + } + + set { + name = "serviceAccount.name" + value = "aws-load-balancer-controller" + } + + set { + name = "clusterName" + value = var.cluster_name + } +} + +resource "helm_release" "ingress_nginx" { + name = "ingress-nginx" + repository = "https://kubernetes.github.io/ingress-nginx" + chart = "ingress-nginx" + namespace = "ingress-nginx" + version = "4.4.0" + create_namespace = true + + set { + name = "controller.service.type" + value = "NodePort" + } + + set { + name = "controller.config.use-forwarded-headers" + value = "true" + } + +} + +resource "kubernetes_ingress_v1" "alb_ingress_connect_nginx" { + count = local.can_connect_alb_to_nginx ? 1 : 0 + lifecycle { + ignore_changes = [metadata["*cattle*"]] + } + wait_for_load_balancer = true + metadata { + name = var.connect_hostnames_from_alb_ing_prefix != "" ? "${var.connect_hostnames_from_alb_ing_prefix}-nginx" : "ing-nginx" + namespace = "ingress-nginx" + + } + + spec { + ingress_class_name = "alb" + dynamic "rule" { + for_each = toset(var.connect_hostnames_from_alb_to_nginx) + content { + host = rule.key + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = "ingress-nginx-controller" + port { + number = 80 + } + } + } + } + } + } + } + } + depends_on = [ + helm_release.aws_lb_controller, helm_release.ingress_nginx + ] +} + +resource "kubernetes_annotations" "alb_ingress_connect_nginx_annotation" { + count = local.can_connect_alb_to_nginx ? 1 : 0 + api_version = "networking.k8s.io/v1" + kind = "Ingress" + force = true + metadata { + name = var.connect_hostnames_from_alb_ing_prefix != "" ? "${var.connect_hostnames_from_alb_ing_prefix}-nginx" : "ing-nginx" + namespace = "ingress-nginx" + } + annotations = { + "alb.ingress.kubernetes.io/load-balancer-name" = var.loadbalancer_name + "alb.ingress.kubernetes.io/certificate-arn" = var.acm_certificate_arn + "alb.ingress.kubernetes.io/wafv2-acl-arn" = var.waf_acl_arn + "alb.ingress.kubernetes.io/scheme" = "internet-facing" + "alb.ingress.kubernetes.io/target-type" = "instance" + "alb.ingress.kubernetes.io/group.name" = "external" + "alb.ingress.kubernetes.io/ssl-redirect" = "443" + "alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\": 80}, {\"HTTPS\": 443}]" + "alb.ingress.kubernetes.io/healthcheck-path" = "/healthz" + } +} + +resource "kubernetes_ingress_v1" "alb_ingress_connect_istio" { + count = local.can_connect_alb_to_istio ? 1 : 0 + lifecycle { + ignore_changes = [metadata["*cattle*"]] + } + wait_for_load_balancer = true + metadata { + name = var.connect_hostnames_from_alb_ing_prefix != "" ? "${var.connect_hostnames_from_alb_ing_prefix}-istio" : "ing-istio" + namespace = "istio-system" + + annotations = { + "alb.ingress.kubernetes.io/load-balancer-name" = var.loadbalancer_name + "alb.ingress.kubernetes.io/certificate-arn" = var.acm_certificate_arn + "alb.ingress.kubernetes.io/scheme" = "internet-facing" + "alb.ingress.kubernetes.io/target-type" = "instance" + "alb.ingress.kubernetes.io/group.name" = "external" + "alb.ingress.kubernetes.io/ssl-redirect" = "443" + "alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\": 80}, {\"HTTPS\": 443}]" + "alb.ingress.kubernetes.io/shield-advanced-protection" = tostring(var.enable_shield_advanced_protection_from_alb_to_istio) + } + } + + spec { + ingress_class_name = "alb" + dynamic "rule" { + for_each = toset(var.connect_hostnames_from_alb_to_istio) + content { + host = rule.key + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = "istio-ingressgateway" + port { + number = 80 + } + } + } + } + } + } + } + } + depends_on = [ + helm_release.aws_lb_controller + ] +} + + +data "aws_ssm_parameter" "sso_ca_data_network_account" { + provider = aws.network_infra + count = var.enable_sso ? 1 : 0 + name = var.sso_ca_data_network_account +} + +data "aws_ssm_parameter" "sso_url_network_account" { + provider = aws.network_infra + count = var.enable_sso ? 1 : 0 + name = var.sso_url_network_account +} + + +resource "helm_release" "argocd" { + count = var.deploy_argocd ? 1 : 0 + name = "argocd" + repository = "https://argoproj.github.io/argo-helm" + chart = "argo-cd" + namespace = "argocd" + create_namespace = true + + set { + name = "global.domain" + value = var.argocd_ingress_host + } + + set { + name = "server.ingress.ingressClassName" + value = "nginx" + } + + set { + name = "server.ingress.hosts[0]" + value = var.argocd_ingress_host + } + + set { + name = "server.ingress.enabled" + value = "true" + } + + set { + name = "configs.secret.argocdServerAdminPassword" + value = "password" + } + + set { + name = "server.ingress.pathType" + value = "Prefix" + } + set { + name = "server.ingress.annotations.\"ingress\\.kubernetes\\.io/rewrite-target\"" + value = "/" + } + set { + name = "server.ingress.annotations.\"nginx\\.ingress\\.kubernetes\\.io/backend-protocol\"" + value = "HTTPS" + } + + set { + name = "server.ingress.paths[0]" + value = "/" + } + set { + name = "server.service.type" + value = "ClusterIP" + } + set { + name = "server.serviceAccount.annotations.\"eks\\.amazonaws\\.com/role-arn\"" + value = var.argocd_iam_role_arn + } + set { + name = "controller.serviceAccount.annotations.\"eks\\.amazonaws\\.com/role-arn\"" + value = var.argocd_iam_role_arn + } + set { + name = "repoServer.serviceAccount.annotations.\"eks\\.amazonaws\\.com/role-arn\"" + value = var.argocd_iam_role_arn + } + set { + name = "repoServer.initContainers[0].name" + value = "download-tools" + } + set { + name = "repoServer.initContainers[0].image" + value = "alpine:3.8" + } + set { + name = "repoServer.initContainers[0].command[0]" + value = "sh" + } + set { + name = "repoServer.initContainers[0].command[1]" + value = "-c" + } + set { + name = "repoServer.initContainers[0].args[0]" + value = "wget -qO- https://github.com/codacy/helm-ssm/releases/download/3.1.9/helm-ssm-linux.tgz | tar -xvzf - && mv helm-ssm /custom-tools/" + } + set { + name = "repoServer.initContainers[0].volumeMounts[0].mountPath" + value = "/custom-tools" + } + set { + name = "repoServer.initContainers[0].volumeMounts[0].name" + value = "custom-tools" + } + set { + name = "repoServer.volumeMounts[0].mountPath" + value = "/usr/local/bin/helm-ssm" + } + set { + name = "repoServer.volumeMounts[0].name" + value = "custom-tools" + } + set { + name = "repoServer.volumeMounts[0].subPath" + value = "helm-ssm" + } + set { + name = "repoServer.volumes[0].name" + value = "custom-tools" + } + set { + name = "repoServer.volumes[0].emptyDir" + value = "" + } + + values = var.enable_sso || var.enable_template_file ? [templatefile("${path.module}/values.yaml.tpl", { + caData = local.caData, + ssoURL = local.ssoURL, + redirectURI = "${var.sso_callback_url}", + entityIssuer = "${var.sso_callback_url}", + currentEnvironment = "${var.current_environment}" + }) + ] : [] + + // SSO Values + // configmap url + dynamic "set" { + for_each = var.enable_sso ? [1] : [] + content { + name = "configs.cm.url" + value = var.gitops_url + } + } + + depends_on = [ + kubernetes_ingress_v1.alb_ingress_connect_nginx + ] + +} + +resource "helm_release" "crossplane" { + count = var.deploy_crossplane ? 1 : 0 + name = "crossplane" + repository = "https://charts.crossplane.io/stable" + chart = "crossplane" + namespace = "crossplane-system" + create_namespace = true +} + + +resource "kubectl_manifest" "crossplane_aws_controller" { + count = var.deploy_crossplane ? 1 : 0 + yaml_body = <.kubernetes_manifest.cluster_provisioning` + +2. **Apply aws-eks-rancher-joiner module** + + This will fetch the Rancher generated join manifest (a Rancher Agent setup manifest) + from the upstream cluster, and apply it to the **downstream**. + + If downstream has already been joined to the upstream and the terraform state is up-to-date, + it will not try to re-join the cluster. + + When importing already joined clusters to terraform, there is a chance terraform can re-apply agent setup + manifest, which may result in restart of the Rancher agent. This behaviour has not been + verified yet. diff --git a/modules/aws-eks-rancher-importer/main.tf b/modules/aws-eks-rancher-importer/main.tf new file mode 100644 index 0000000..8671a40 --- /dev/null +++ b/modules/aws-eks-rancher-importer/main.tf @@ -0,0 +1,58 @@ +# Collect the creds from AWS SSM +data "aws_ssm_parameter" "upstream_cluster_name" { + provider = aws.shared_infra + name = var.upstream_cluster_name_ssm_key +} +data "aws_ssm_parameter" "upstream_cluster_endpoint" { + provider = aws.shared_infra + name = var.upstream_cluster_endpoint_ssm_key +} +data "aws_ssm_parameter" "upstream_cluster_ca_cert" { + provider = aws.shared_infra + name = var.upstream_cluster_ca_cert_ssm_key +} + +provider "kubernetes" { + alias = "upstream" + + host = data.aws_ssm_parameter.upstream_cluster_endpoint.value + cluster_ca_certificate = base64decode(data.aws_ssm_parameter.upstream_cluster_ca_cert.value) + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = ["eks", "get-token", "--role-arn", var.upstream_assume_role_arn, "--cluster-name", data.aws_ssm_parameter.upstream_cluster_name.value] + command = "aws" + } +} + +locals { + rancher_cluster_name = var.rancher_cluster_name != "" ? var.rancher_cluster_name : var.downstream_cluster_name +} + +# This only creates the provisioning. The cluster will be imported +# after `aws-eks-rancher-joiner` applies the join manifests from the +# resulting import. +resource "kubernetes_manifest" "cluster_provisioning" { + provider = kubernetes.upstream + + manifest = { + apiVersion = "provisioning.cattle.io/v1" + kind = "Cluster" + metadata = { + # TODO: namespace can be non-default + namespace = "fleet-default" + name = local.rancher_cluster_name + } + # The empty spec has been lifted straight from + # Rancher's UI when importing an existing cluster. + spec = { + localClusterAuthEndpoint = {} + } + } + + wait { + fields = { + "status.clusterName" = "*" + } + } +} + diff --git a/modules/aws-eks-rancher-importer/variables.tf b/modules/aws-eks-rancher-importer/variables.tf new file mode 100644 index 0000000..4840087 --- /dev/null +++ b/modules/aws-eks-rancher-importer/variables.tf @@ -0,0 +1,41 @@ +variable "rancher_cluster_name" { + type = string + description = "Name of the rancher cluster resource representing the downstream cluster. If not set, will default to `var.downstream_cluster_name`" + default = "" +} + +variable "upstream_cluster_name_ssm_key" { + type = string + description = "SSM Key pointing to existing upstream cluster name" + validation { + condition = length(var.upstream_cluster_name_ssm_key) > 0 + error_message = "upstream_cluster_name cannot be an empty string" + } +} + +variable "upstream_cluster_endpoint_ssm_key" { + description = "SSM Key pointing to the endpoint of the upstream cluster" + default = "" + type = string +} + +variable "upstream_cluster_ca_cert_ssm_key" { + description = "SSM Key pointing to the CA of the upstream cluster" + default = "" + type = string +} + +variable "upstream_assume_role_arn" { + description = "Assume role arn for upstream cluster access" + type = string +} + +variable "downstream_cluster_name" { + type = string + description = "Name of the existing downstream cluster" + validation { + condition = length(var.downstream_cluster_name) > 0 + error_message = "downstream_cluster_name cannot be an empty string" + } +} + diff --git a/modules/aws-eks-rancher-importer/versions.tf b/modules/aws-eks-rancher-importer/versions.tf new file mode 100644 index 0000000..3da6e4c --- /dev/null +++ b/modules/aws-eks-rancher-importer/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.22.0" + } + aws = { + source = "hashicorp/aws" + } + } +} + diff --git a/modules/aws-eks-rancher-joiner/README.md b/modules/aws-eks-rancher-joiner/README.md new file mode 100644 index 0000000..5e604df --- /dev/null +++ b/modules/aws-eks-rancher-joiner/README.md @@ -0,0 +1,64 @@ +# AWS EKS Rancher Joiner + +Module for joining downstream EKS clusters to when, +without the use of the Rancher terraform provider. + +**IMPORTANT:** Joiner module is meant to be used together with +[aws-eks-rancher-importer](https://github.com/kloia/platform-modules/tree/main/aws-eks-rancher-importer), +with two separate applies. + +## Dependencies +- [aws-eks-rancher-importer](https://github.com/kloia/platform-modules/tree/main/aws-eks-rancher-importer) +- Available Rancher installation on upstream EKS cluster +- Upstream EKS Cluster connection information stored on Parameter Store, valid Role ARN to assume +- Downstream EKS Cluster connection + +## Critical Providers + +- Kubernetes provider, aliased to `upstream` + +## Variables + +Joiner module is dependent on having upstream connection information stored on AWS SSM Parameter Store, and +downstream connection information being readily available (through .tfvars, terraform_remote_state data resources, or wrappers like terragrunt) + +| name | description | +|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| rancher_agent_already_installed | Set if downstream cluster already has the Rancher Agent installed. This injects a few env vars to the agent deployment that Rancher injects in first time agent installations, but never afterwards. | +| rancher_cluster_name | Name of the rancher cluster resource representing the downstream cluster name. If not set, will default to `var.downstream_cluster_name` | +| downstream_cluster_name | Name of the existing downstream cluster | +| downstream_cluster_endpoint | Endpoint of the downstream cluster | +| downstream_cluster_ca_cert | Certificate Authority of the downstream cluster | +| downstream_assume_role_arn | Assume role arn for downstream cluster access | +| upstream_cluster_name_ssm_key | SSM Key pointing to existing upstream cluster name | +| upstream_cluster_endpoint_ssm_key | SSM Key pointing to the endpoint of the upstream cluster | +| upstream_cluster_ca_cert_ssm_key | SSM Key pointing to the CA of the upstream cluster | +| upstream_assume_role_arn | ARN of the Role to assume for upstream cluster access | + +## Usage + +1. **Apply aws-eks-rancher-importer module** + + This will create the appropriate Custom Resource that declares the Cluster definition on **upstream**. + + **If the cluster has already been joined to Rancher**, make sure to `terraform import` import the kubernetes resource. + + ``` + $ terraform import kubernetes_manifest.cluster_provisioning "apiVersion=provisioning.cattle.io/v1,kind=Cluster,namespace=fleet-default,name={ClusterName}" + ``` + + If you are using the module source code directly, the terraform resource id is `kubernetes_manifest.cluster_provisioning`, + if not, prefix it with `module..kubernetes_manifest.cluster_provisioning` + +2. **Apply aws-eks-rancher-joiner module** + + This will fetch the Rancher generated join manifest (a Rancher Agent setup manifest) + from the upstream cluster, and apply it to the **downstream**. + + If downstream already has the `cattle-cluster-agent` installed, make sure to set `var.rancher_agent_already_installed`. + + If downstream has already been joined to the upstream and the terraform state is up-to-date, + it will not try to re-join the cluster. + + When importing already joined clusters to terraform, there is a chance terraform can re-apply agent setup + manifest, which may result in restart of the Rancher agent. \ No newline at end of file diff --git a/modules/aws-eks-rancher-joiner/main.tf b/modules/aws-eks-rancher-joiner/main.tf new file mode 100644 index 0000000..9974beb --- /dev/null +++ b/modules/aws-eks-rancher-joiner/main.tf @@ -0,0 +1,144 @@ +# Collect the creds from AWS SSM +data "aws_ssm_parameter" "upstream_cluster_name" { + provider = aws.shared_infra + name = var.upstream_cluster_name_ssm_key +} +data "aws_ssm_parameter" "upstream_cluster_endpoint" { + provider = aws.shared_infra + name = var.upstream_cluster_endpoint_ssm_key +} +data "aws_ssm_parameter" "upstream_cluster_ca_cert" { + provider = aws.shared_infra + name = var.upstream_cluster_ca_cert_ssm_key +} + +provider "kubernetes" { + alias = "upstream" + + host = data.aws_ssm_parameter.upstream_cluster_endpoint.value + cluster_ca_certificate = base64decode(data.aws_ssm_parameter.upstream_cluster_ca_cert.value) + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = [ + "eks", "get-token", "--role-arn", var.upstream_assume_role_arn, "--cluster-name", + data.aws_ssm_parameter.upstream_cluster_name.value + ] + command = "aws" + } +} + +provider "kubernetes" { + alias = "downstream" + + host = var.downstream_cluster_endpoint + cluster_ca_certificate = base64decode(var.downstream_cluster_ca_cert) + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = [ + "eks", "get-token", "--role-arn", var.downstream_assume_role_arn, "--cluster-name", + var.downstream_cluster_name + ] + command = "aws" + } +} + +# Alternative kubernetes provider that plays a bit nicer with arbitrary manifests +provider "kubectl" { + alias = "downstream" + + host = var.downstream_cluster_endpoint + cluster_ca_certificate = base64decode(var.downstream_cluster_ca_cert) + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = [ + "eks", "get-token", "--role-arn", var.downstream_assume_role_arn, "--cluster-name", + var.downstream_cluster_name + ] + command = "aws" + } +} + +locals { + rancher_cluster_name = var.rancher_cluster_name != "" ? var.rancher_cluster_name : var.downstream_cluster_name +} + +# Fetch the internal cluster name from the resource created in `aws-eks-rancher-cluster-joiner` +data "kubernetes_resource" "cluster_provisioning" { + provider = kubernetes.upstream + + api_version = "provisioning.cattle.io/v1" + kind = "Cluster" + metadata { + namespace = "fleet-default" + name = local.rancher_cluster_name + } +} + +locals { + rancher_internal_cluster_name = data.kubernetes_resource.cluster_provisioning.object["status"]["clusterName"] +} + +# Rancher created resource, which includes the manifest that needs to be applied to the downstream cluster +data "kubernetes_resource" "cluster_registration_token" { + provider = kubernetes.upstream + depends_on = [ + data.kubernetes_resource.cluster_provisioning, + ] + + api_version = "management.cattle.io/v3" + kind = "ClusterRegistrationToken" + metadata { + # quirk of rancher, this resource is created under a namespace with the cluster's internal name (e.g. c-m-3fd34h) + namespace = local.rancher_internal_cluster_name + name = "default-token" + } +} + +# The manifest that sets up the Rancher agent on the downstream cluster +data "http" "agent_registration_manifest" { + depends_on = [ + data.kubernetes_resource.cluster_registration_token, + ] + + url = data.kubernetes_resource.cluster_registration_token.object["status"]["manifestUrl"] +} + +# separate yaml documents +data "kubectl_file_documents" "agent_registration_manifest" { + content = data.http.agent_registration_manifest.response_body +} + +# Apply the registration manifest to downstream, which finally makes it join upstream. +resource "kubectl_manifest" "agent_registration" { + for_each = data.kubectl_file_documents.agent_registration_manifest.manifests + provider = kubectl.downstream + + yaml_body = each.value + + depends_on = [data.http.agent_registration_manifest] + lifecycle { + # No need to apply again if downstream is already joined + ignore_changes = [yaml_body] + } +} + +resource "kubernetes_env" "cattle_features" { + count = var.rancher_agent_already_installed ? 1 : 0 + provider = kubernetes.downstream + + api_version = "apps/v1" + kind = "Deployment" + metadata { + name = "cattle-cluster-agent" + namespace = "cattle-system" + } + + container = "cluster-register" + + env { + name = "CATTLE_FEATURES" + value = "embedded-cluster-api=false,fleet=false,monitoringv1=false,multi-cluster-management=false,multi-cluster-management-agent=true,provisioningv2=false,rke2=false" + } + + depends_on = [kubectl_manifest.agent_registration] +} diff --git a/modules/aws-eks-rancher-joiner/variables.tf b/modules/aws-eks-rancher-joiner/variables.tf new file mode 100644 index 0000000..e743bee --- /dev/null +++ b/modules/aws-eks-rancher-joiner/variables.tf @@ -0,0 +1,71 @@ +variable "rancher_agent_already_installed" { + type = bool + description = "Set if downstream cluster already has the Rancher Agent installed. This injects a few env vars to the agent deployment that Rancher injects in first time agent installations, but never afterwards." + default = false +} + +variable "rancher_cluster_name" { + type = string + description = "Name of the rancher cluster resource representing the downstream cluster. If not set, will default to `var.downstream_cluster_name`" + default = "" +} + +variable "upstream_cluster_name_ssm_key" { + type = string + description = "SSM Key pointing to existing upstream cluster name" + validation { + condition = length(var.upstream_cluster_name_ssm_key) > 0 + error_message = "cannot be an empty string" + } +} + +variable "upstream_cluster_endpoint_ssm_key" { + description = "SSM Key pointing to the endpoint of the upstream cluster" + type = string + validation { + condition = length(var.upstream_cluster_endpoint_ssm_key) > 0 + error_message = "cannot be an empty string" + } +} + +variable "upstream_cluster_ca_cert_ssm_key" { + description = "SSM Key pointing to the CA of the upstream cluster" + type = string + validation { + condition = length(var.upstream_cluster_ca_cert_ssm_key) > 0 + error_message = "cannot be an empty string" + } +} + +variable "upstream_assume_role_arn" { + description = "Assume role arn for upstream cluster access" + type = string + validation { + condition = length(var.upstream_assume_role_arn) > 0 + error_message = "cannot be an empty string" + } +} + +variable "downstream_cluster_name" { + type = string + description = "Name of the existing downstream cluster" + validation { + condition = length(var.downstream_cluster_name) > 0 + error_message = "downstream_cluster_name cannot be an empty string" + } +} + +variable "downstream_cluster_endpoint" { + description = "Endpoint of the downstream cluster" + type = string +} + +variable "downstream_cluster_ca_cert" { + description = "Certificate Authority of the downstream cluster" + type = string +} + +variable "downstream_assume_role_arn" { + description = "Assume role arn for downstream cluster access" + type = string +} diff --git a/modules/aws-eks-rancher-joiner/versions.tf b/modules/aws-eks-rancher-joiner/versions.tf new file mode 100644 index 0000000..cb81ed1 --- /dev/null +++ b/modules/aws-eks-rancher-joiner/versions.tf @@ -0,0 +1,23 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.22.0" + } + http = { + source = "hashicorp/http" + version = ">= 3.4.0" + } + aws = { + source = "hashicorp/aws" + } + kubectl = { + source = "gavinbunney/kubectl" + version = ">= 1.7.0" + } + kustomization = { + source = "kbst/kustomization" + version = ">= 0.9.4" + } + } +} diff --git a/modules/aws-eks/README.md b/modules/aws-eks/README.md new file mode 100644 index 0000000..79f3b72 --- /dev/null +++ b/modules/aws-eks/README.md @@ -0,0 +1,371 @@ +# AWS EKS Terraform module + +Terraform module which creates AWS EKS (Kubernetes) resources + +## [Documentation](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs) + +- [Frequently Asked Questions](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/faq.md) +- [Compute Resources](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/compute_resources.md) +- [IRSA Integration](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/irsa_integration.md) +- [User Data](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/user_data.md) +- [Network Connectivity](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/network_connectivity.md) + +### External Documentation + +Please note that we strive to provide a comprehensive suite of documentation for __*configuring and utilizing the module(s)*__ defined here, and that documentation regarding EKS (including EKS managed node group, self managed node group, and Fargate profile) and/or Kubernetes features, usage, etc. are better left up to their respective sources: +- [AWS EKS Documentation](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) +- [Kubernetes Documentation](https://kubernetes.io/docs/home/) + +## Available Features + +- AWS EKS Cluster Addons +- AWS EKS Identity Provider Configuration +- All [node types](https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html) are supported: + - [EKS Managed Node Group](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) + - [Self Managed Node Group](https://docs.aws.amazon.com/eks/latest/userguide/worker.html) + - [Fargate Profile](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html) +- Support for custom AMI, custom launch template, and custom user data including custom user data template +- Support for Amazon Linux 2 EKS Optimized AMI and Bottlerocket nodes + - Windows based node support is limited to a default user data template that is provided due to the lack of Windows support and manual steps required to provision Windows based EKS nodes +- Support for module created security group, bring your own security groups, as well as adding additional security group rules to the module created security group(s) +- Support for creating node groups/profiles separate from the cluster through the use of sub-modules (same as what is used by root module) +- Support for node group/profile "default" settings - useful for when creating multiple node groups/Fargate profiles where you want to set a common set of configurations once, and then individually control only select features on certain node groups/profiles + +Some of the addon/controller policies that are currently supported include: + +- [Cluster Autoscaler](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md) +- [External DNS](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy) +- [EBS CSI Driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/example-iam-policy.json) +- [VPC CNI](https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html) +- [Node Termination Handler](https://github.com/aws/aws-node-termination-handler#5-create-an-iam-role-for-the-pods) +- [Karpenter](https://karpenter.sh/preview/getting-started/getting-started-with-terraform/#create-the-karpentercontroller-iam-role) +- [Load Balancer Controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/install/iam_policy.json) + + +## Usage + +```hcl +module "eks" { + source = "terraform-modules/eks/aws" + version = "~> 18.0" + + cluster_name = "my-cluster" + cluster_version = "1.22" + + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + cluster_addons = { + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + } + } + + cluster_encryption_config = [{ + provider_key_arn = "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" + resources = ["secrets"] + }] + + vpc_id = "vpc-1234556abcdef" + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + + # Self Managed Node Group(s) + self_managed_node_group_defaults = { + instance_type = "m6i.large" + update_launch_template_default_version = true + iam_role_additional_policies = [ + "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + ] + } + + self_managed_node_groups = { + one = { + name = "mixed-1" + max_size = 5 + desired_size = 2 + + use_mixed_instances_policy = true + mixed_instances_policy = { + instances_distribution = { + on_demand_base_capacity = 0 + on_demand_percentage_above_base_capacity = 10 + spot_allocation_strategy = "capacity-optimized" + } + + override = [ + { + instance_type = "m5.large" + weighted_capacity = "1" + }, + { + instance_type = "m6i.large" + weighted_capacity = "2" + }, + ] + } + } + } + + # EKS Managed Node Group(s) + eks_managed_node_group_defaults = { + disk_size = 50 + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + } + + eks_managed_node_groups = { + blue = {} + green = { + min_size = 1 + max_size = 10 + desired_size = 1 + + instance_types = ["t3.large"] + capacity_type = "SPOT" + } + } + + # Fargate Profile(s) + fargate_profiles = { + default = { + name = "default" + selectors = [ + { + namespace = "default" + } + ] + } + } + + # aws-auth configmap + manage_aws_auth_configmap = true + + aws_auth_roles = [ + { + rolearn = "arn:aws:iam::66666666666:role/role1" + username = "role1" + groups = ["system:masters"] + }, + ] + + aws_auth_users = [ + { + userarn = "arn:aws:iam::66666666666:user/user1" + username = "user1" + groups = ["system:masters"] + }, + { + userarn = "arn:aws:iam::66666666666:user/user2" + username = "user2" + groups = ["system:masters"] + }, + ] + + aws_auth_accounts = [ + "777777777777", + "888888888888", + ] + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Examples + +- [Complete](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/complete): EKS Cluster using all available node group types in various combinations demonstrating many of the supported features and configurations +- [EKS Managed Node Group](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/eks_managed_node_group): EKS Cluster using EKS managed node groups +- [Fargate Profile](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/fargate_profile): EKS cluster using [Fargate Profiles](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html) +- [Karpenter](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/karpenter): EKS Cluster with [Karpenter](https://karpenter.sh/) provisioned for managing compute resource scaling +- [Self Managed Node Group](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/self_managed_node_group): EKS Cluster using self-managed node groups +- [User Data](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples/user_data): Various supported methods of providing necessary bootstrap scripts and configuration settings via user data + + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | +| [tls](#requirement\_tls) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | +| [kubernetes](#provider\_kubernetes) | >= 2.10 | +| [tls](#provider\_tls) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks\_managed\_node\_group](#module\_eks\_managed\_node\_group) | ./modules/eks-managed-node-group | n/a | +| [fargate\_profile](#module\_fargate\_profile) | ./modules/fargate-profile | n/a | +| [kms](#module\_kms) | terraform-aws-modules/kms/aws | 1.0.2 | +| [self\_managed\_node\_group](#module\_self\_managed\_node\_group) | ./modules/self-managed-node-group | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ec2_tag.cluster_primary_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_eks_addon.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_addon) | resource | +| [aws_eks_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster) | resource | +| [aws_eks_identity_provider_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_identity_provider_config) | resource | +| [aws_iam_openid_connect_provider.oidc_provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | +| [aws_iam_policy.cluster_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.cni_ipv6_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.cluster_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [kubernetes_config_map.aws_auth](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map) | resource | +| [kubernetes_config_map_v1_data.aws_auth](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map_v1_data) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cni_ipv6_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [tls_certificate.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [attach\_cluster\_encryption\_policy](#input\_attach\_cluster\_encryption\_policy) | Indicates whether or not to attach an additional policy for the cluster IAM role to utilize the encryption key provided | `bool` | `true` | no | +| [aws\_auth\_accounts](#input\_aws\_auth\_accounts) | List of account maps to add to the aws-auth configmap | `list(any)` | `[]` | no | +| [aws\_auth\_fargate\_profile\_pod\_execution\_role\_arns](#input\_aws\_auth\_fargate\_profile\_pod\_execution\_role\_arns) | List of Fargate profile pod execution role ARNs to add to the aws-auth configmap | `list(string)` | `[]` | no | +| [aws\_auth\_node\_iam\_role\_arns\_non\_windows](#input\_aws\_auth\_node\_iam\_role\_arns\_non\_windows) | List of non-Windows based node IAM role ARNs to add to the aws-auth configmap | `list(string)` | `[]` | no | +| [aws\_auth\_node\_iam\_role\_arns\_windows](#input\_aws\_auth\_node\_iam\_role\_arns\_windows) | List of Windows based node IAM role ARNs to add to the aws-auth configmap | `list(string)` | `[]` | no | +| [aws\_auth\_roles](#input\_aws\_auth\_roles) | List of role maps to add to the aws-auth configmap | `list(any)` | `[]` | no | +| [aws\_auth\_users](#input\_aws\_auth\_users) | List of user maps to add to the aws-auth configmap | `list(any)` | `[]` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default retention - 90 days | `number` | `90` | no | +| [cluster\_additional\_security\_group\_ids](#input\_cluster\_additional\_security\_group\_ids) | List of additional, externally created security group IDs to attach to the cluster control plane | `list(string)` | `[]` | no | +| [cluster\_addons](#input\_cluster\_addons) | Map of cluster addon configurations to enable for the cluster. Addon name can be the map keys or set with `name` | `any` | `{}` | no | +| [cluster\_enabled\_log\_types](#input\_cluster\_enabled\_log\_types) | A list of the desired control plane logs to enable. For more information, see Amazon EKS Control Plane Logging documentation (https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html) | `list(string)` |
[
"audit",
"api",
"authenticator"
]
| no | +| [cluster\_encryption\_config](#input\_cluster\_encryption\_config) | Configuration block with encryption configuration for the cluster | `list(any)` | `[]` | no | +| [cluster\_encryption\_policy\_description](#input\_cluster\_encryption\_policy\_description) | Description of the cluster encryption policy created | `string` | `"Cluster encryption policy to allow cluster role to utilize CMK provided"` | no | +| [cluster\_encryption\_policy\_name](#input\_cluster\_encryption\_policy\_name) | Name to use on cluster encryption policy created | `string` | `null` | no | +| [cluster\_encryption\_policy\_path](#input\_cluster\_encryption\_policy\_path) | Cluster encryption policy path | `string` | `null` | no | +| [cluster\_encryption\_policy\_tags](#input\_cluster\_encryption\_policy\_tags) | A map of additional tags to add to the cluster encryption policy created | `map(string)` | `{}` | no | +| [cluster\_encryption\_policy\_use\_name\_prefix](#input\_cluster\_encryption\_policy\_use\_name\_prefix) | Determines whether cluster encryption policy name (`cluster_encryption_policy_name`) is used as a prefix | `bool` | `true` | no | +| [cluster\_endpoint\_private\_access](#input\_cluster\_endpoint\_private\_access) | Indicates whether or not the Amazon EKS private API server endpoint is enabled | `bool` | `false` | no | +| [cluster\_endpoint\_public\_access](#input\_cluster\_endpoint\_public\_access) | Indicates whether or not the Amazon EKS public API server endpoint is enabled | `bool` | `true` | no | +| [cluster\_endpoint\_public\_access\_cidrs](#input\_cluster\_endpoint\_public\_access\_cidrs) | List of CIDR blocks which can access the Amazon EKS public API server endpoint | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [cluster\_iam\_role\_dns\_suffix](#input\_cluster\_iam\_role\_dns\_suffix) | Base DNS domain name for the current partition (e.g., amazonaws.com in AWS Commercial, amazonaws.com.cn in AWS China) | `string` | `null` | no | +| [cluster\_identity\_providers](#input\_cluster\_identity\_providers) | Map of cluster identity provider configurations to enable for the cluster. Note - this is different/separate from IRSA | `any` | `{}` | no | +| [cluster\_ip\_family](#input\_cluster\_ip\_family) | The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`. You can only specify an IP family when you create a cluster, changing this value will force a new cluster to be created | `string` | `null` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the EKS cluster | `string` | `""` | no | +| [cluster\_security\_group\_additional\_rules](#input\_cluster\_security\_group\_additional\_rules) | List of additional security group rules to add to the cluster security group created. Set `source_node_security_group = true` inside rules to set the `node_security_group` as source | `any` | `{}` | no | +| [cluster\_security\_group\_description](#input\_cluster\_security\_group\_description) | Description of the cluster security group created | `string` | `"EKS cluster security group"` | no | +| [cluster\_security\_group\_id](#input\_cluster\_security\_group\_id) | Existing security group ID to be attached to the cluster. Required if `create_cluster_security_group` = `false` | `string` | `""` | no | +| [cluster\_security\_group\_name](#input\_cluster\_security\_group\_name) | Name to use on cluster security group created | `string` | `null` | no | +| [cluster\_security\_group\_tags](#input\_cluster\_security\_group\_tags) | A map of additional tags to add to the cluster security group created | `map(string)` | `{}` | no | +| [cluster\_security\_group\_use\_name\_prefix](#input\_cluster\_security\_group\_use\_name\_prefix) | Determines whether cluster security group name (`cluster_security_group_name`) is used as a prefix | `bool` | `true` | no | +| [cluster\_service\_ipv4\_cidr](#input\_cluster\_service\_ipv4\_cidr) | The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks | `string` | `null` | no | +| [cluster\_tags](#input\_cluster\_tags) | A map of additional tags to add to the cluster | `map(string)` | `{}` | no | +| [cluster\_timeouts](#input\_cluster\_timeouts) | Create, update, and delete timeout configurations for the cluster | `map(string)` | `{}` | no | +| [cluster\_version](#input\_cluster\_version) | Kubernetes `.` version to use for the EKS cluster (i.e.: `1.22`) | `string` | `null` | no | +| [control\_plane\_subnet\_ids](#input\_control\_plane\_subnet\_ids) | A list of subnet IDs where the EKS cluster control plane (ENIs) will be provisioned. Used for expanding the pool of subnets used by nodes/node groups without replacing the EKS control plane | `list(string)` | `[]` | no | +| [create](#input\_create) | Controls if EKS resources should be created (affects nearly all resources) | `bool` | `true` | no | +| [create\_aws\_auth\_configmap](#input\_create\_aws\_auth\_configmap) | Determines whether to create the aws-auth configmap. NOTE - this is only intended for scenarios where the configmap does not exist (i.e. - when using only self-managed node groups). Most users should use `manage_aws_auth_configmap` | `bool` | `false` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_cluster\_primary\_security\_group\_tags](#input\_create\_cluster\_primary\_security\_group\_tags) | Indicates whether or not to tag the cluster's primary security group. This security group is created by the EKS service, not the module, and therefore tagging is handled after cluster creation | `bool` | `true` | no | +| [create\_cluster\_security\_group](#input\_create\_cluster\_security\_group) | Determines if a security group is created for the cluster or use the existing `cluster_security_group_id` | `bool` | `true` | no | +| [create\_cni\_ipv6\_iam\_policy](#input\_create\_cni\_ipv6\_iam\_policy) | Determines whether to create an [`AmazonEKS_CNI_IPv6_Policy`](https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy) | `bool` | `false` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether a an IAM role is created or to use an existing IAM role | `bool` | `true` | no | +| [create\_kms\_key](#input\_create\_kms\_key) | Controls if a KMS key for cluster encryption should be created | `bool` | `false` | no | +| [create\_node\_security\_group](#input\_create\_node\_security\_group) | Determines whether to create a security group for the node groups or use the existing `node_security_group_id` | `bool` | `true` | no | +| [custom\_oidc\_thumbprints](#input\_custom\_oidc\_thumbprints) | Additional list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s) | `list(string)` | `[]` | no | +| [eks\_managed\_node\_group\_defaults](#input\_eks\_managed\_node\_group\_defaults) | Map of EKS managed node group default configurations | `any` | `{}` | no | +| [eks\_managed\_node\_groups](#input\_eks\_managed\_node\_groups) | Map of EKS managed node group definitions to create | `any` | `{}` | no | +| [enable\_irsa](#input\_enable\_irsa) | Determines whether to create an OpenID Connect Provider for EKS to enable IRSA | `bool` | `true` | no | +| [enable\_kms\_key\_rotation](#input\_enable\_kms\_key\_rotation) | Specifies whether key rotation is enabled. Defaults to `true` | `bool` | `true` | no | +| [fargate\_profile\_defaults](#input\_fargate\_profile\_defaults) | Map of Fargate Profile default configurations | `any` | `{}` | no | +| [fargate\_profiles](#input\_fargate\_profiles) | Map of Fargate Profile definitions to create | `any` | `{}` | no | +| [iam\_role\_additional\_policies](#input\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `list(string)` | `[]` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN for the cluster. Required if `create_iam_role` is set to `false` | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | Cluster IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [kms\_key\_administrators](#input\_kms\_key\_administrators) | A list of IAM ARNs for [key administrators](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators). If no value is provided, the current caller identity is used to ensure at least one key admin is available | `list(string)` | `[]` | no | +| [kms\_key\_aliases](#input\_kms\_key\_aliases) | A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values | `list(string)` | `[]` | no | +| [kms\_key\_deletion\_window\_in\_days](#input\_kms\_key\_deletion\_window\_in\_days) | The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30` | `number` | `null` | no | +| [kms\_key\_description](#input\_kms\_key\_description) | The description of the key as viewed in AWS console | `string` | `null` | no | +| [kms\_key\_enable\_default\_policy](#input\_kms\_key\_enable\_default\_policy) | Specifies whether to enable the default key policy. Defaults to `false` | `bool` | `false` | no | +| [kms\_key\_override\_policy\_documents](#input\_kms\_key\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [kms\_key\_owners](#input\_kms\_key\_owners) | A list of IAM ARNs for those who will have full key permissions (`kms:*`) | `list(string)` | `[]` | no | +| [kms\_key\_service\_users](#input\_kms\_key\_service\_users) | A list of IAM ARNs for [key service users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration) | `list(string)` | `[]` | no | +| [kms\_key\_source\_policy\_documents](#input\_kms\_key\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [kms\_key\_users](#input\_kms\_key\_users) | A list of IAM ARNs for [key users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users) | `list(string)` | `[]` | no | +| [manage\_aws\_auth\_configmap](#input\_manage\_aws\_auth\_configmap) | Determines whether to manage the aws-auth configmap | `bool` | `false` | no | +| [node\_security\_group\_additional\_rules](#input\_node\_security\_group\_additional\_rules) | List of additional security group rules to add to the node security group created. Set `source_cluster_security_group = true` inside rules to set the `cluster_security_group` as source | `any` | `{}` | no | +| [node\_security\_group\_description](#input\_node\_security\_group\_description) | Description of the node security group created | `string` | `"EKS node shared security group"` | no | +| [node\_security\_group\_id](#input\_node\_security\_group\_id) | ID of an existing security group to attach to the node groups created | `string` | `""` | no | +| [node\_security\_group\_name](#input\_node\_security\_group\_name) | Name to use on node security group created | `string` | `null` | no | +| [node\_security\_group\_ntp\_ipv4\_cidr\_block](#input\_node\_security\_group\_ntp\_ipv4\_cidr\_block) | IPv4 CIDR block to allow NTP egress. Default is public IP space, but [Amazon Time Sync Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html) can be used as well with `["169.254.169.123/32"]` | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [node\_security\_group\_ntp\_ipv6\_cidr\_block](#input\_node\_security\_group\_ntp\_ipv6\_cidr\_block) | IPv4 CIDR block to allow NTP egress. Default is public IP space, but [Amazon Time Sync Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html) can be used as well with `["fd00:ec2::123/128"]` | `list(string)` |
[
"::/0"
]
| no | +| [node\_security\_group\_tags](#input\_node\_security\_group\_tags) | A map of additional tags to add to the node security group created | `map(string)` | `{}` | no | +| [node\_security\_group\_use\_name\_prefix](#input\_node\_security\_group\_use\_name\_prefix) | Determines whether node security group name (`node_security_group_name`) is used as a prefix | `bool` | `true` | no | +| [openid\_connect\_audiences](#input\_openid\_connect\_audiences) | List of OpenID Connect audience client IDs to add to the IRSA provider | `list(string)` | `[]` | no | +| [prefix\_separator](#input\_prefix\_separator) | The separator to use between the prefix and the generated timestamp for resource names | `string` | `"-"` | no | + +| [self\_managed\_node\_group\_defaults](#input\_self\_managed\_node\_group\_defaults) | Map of self-managed node group default configurations | `any` | `{}` | no | +| [self\_managed\_node\_groups](#input\_self\_managed\_node\_groups) | Map of self-managed node group definitions to create | `any` | `{}` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of subnet IDs where the nodes/node groups will be provisioned. If `control_plane_subnet_ids` is not provided, the EKS cluster control plane (ENIs) will be provisioned in these subnets | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where the cluster and its nodes will be provisioned | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_auth\_configmap\_yaml](#output\_aws\_auth\_configmap\_yaml) | [DEPRECATED - use `var.manage_aws_auth_configmap`] Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | +| [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | +| [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | +| [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | IAM role ARN of the EKS cluster | +| [cluster\_iam\_role\_name](#output\_cluster\_iam\_role\_name) | IAM role name of the EKS cluster | +| [cluster\_iam\_role\_unique\_id](#output\_cluster\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [cluster\_id](#output\_cluster\_id) | The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready | +| [cluster\_identity\_providers](#output\_cluster\_identity\_providers) | Map of attribute maps for all EKS identity providers enabled | +| [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | +| [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | +| [cluster\_primary\_security\_group\_id](#output\_cluster\_primary\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console | +| [cluster\_security\_group\_arn](#output\_cluster\_security\_group\_arn) | Amazon Resource Name (ARN) of the cluster security group | +| [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | ID of the cluster security group | +| [cluster\_status](#output\_cluster\_status) | Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED` | +| [cluster\_version](#output\_cluster\_version) | The Kubernetes version for the cluster | +| [eks\_managed\_node\_groups](#output\_eks\_managed\_node\_groups) | Map of attribute maps for all EKS managed node groups created | +| [eks\_managed\_node\_groups\_autoscaling\_group\_names](#output\_eks\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by EKS managed node groups | +| [fargate\_profiles](#output\_fargate\_profiles) | Map of attribute maps for all EKS Fargate Profiles created | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [kms\_key\_id](#output\_kms\_key\_id) | The globally unique identifier for the key | +| [kms\_key\_policy](#output\_kms\_key\_policy) | The IAM resource policy set on the key | +| [node\_security\_group\_arn](#output\_node\_security\_group\_arn) | Amazon Resource Name (ARN) of the node shared security group | +| [node\_security\_group\_id](#output\_node\_security\_group\_id) | ID of the node shared security group | +| [oidc\_provider](#output\_oidc\_provider) | The OpenID Connect identity provider (issuer URL without leading `https://`) | +| [oidc\_provider\_arn](#output\_oidc\_provider\_arn) | The ARN of the OIDC Provider if `enable_irsa = true` | +| [self\_managed\_node\_groups](#output\_self\_managed\_node\_groups) | Map of attribute maps for all self managed node groups created | +| [self\_managed\_node\_groups\_autoscaling\_group\_names](#output\_self\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by self-managed node groups | + \ No newline at end of file diff --git a/modules/aws-eks/docs/compute_resources.md b/modules/aws-eks/docs/compute_resources.md new file mode 100644 index 0000000..5f75632 --- /dev/null +++ b/modules/aws-eks/docs/compute_resources.md @@ -0,0 +1,199 @@ +# Compute Resources + +## Table of Contents + +ℹ️ Only the pertinent attributes are shown below for brevity + +### EKS Managed Node Groups + +1. The module creates a custom launch template by default to ensure settings such as tags are propagated to instances. To use the default template provided by the AWS EKS managed node group service, disable the launch template creation and set the `launch_template_name` to an empty string: + +```hcl + eks_managed_node_groups = { + default = { + create_launch_template = false + launch_template_name = "" + } + } +``` + +2. Native support for Bottlerocket OS is provided by providing the respective AMI type: + +```hcl + eks_managed_node_groups = { + bottlerocket_default = { + create_launch_template = false + launch_template_name = "" + + ami_type = "BOTTLEROCKET_x86_64" + platform = "bottlerocket" + } + } +``` + +3. Users have limited support to extend the user data that is pre-pended to the user data provided by the AWS EKS Managed Node Group service: + +```hcl + eks_managed_node_groups = { + prepend_userdata = { + # See issue https://github.com/awslabs/amazon-eks-ami/issues/844 + pre_bootstrap_user_data = <<-EOT + #!/bin/bash + set -ex + cat <<-EOF > /etc/profile.d/bootstrap.sh + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + export KUBELET_EXTRA_ARGS="--max-pods=110" + EOF + # Source extra environment variables in bootstrap script + sed -i '/^set -o errexit/a\\nsource /etc/profile.d/bootstrap.sh' /etc/eks/bootstrap.sh + EOT + } + } +``` + +4. Bottlerocket OS is supported in a similar manner. However, note that the user data for Bottlerocket OS uses the TOML format: + +```hcl + eks_managed_node_groups = { + bottlerocket_prepend_userdata = { + ami_type = "BOTTLEROCKET_x86_64" + platform = "bottlerocket" + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT + } + } +``` + +5. When using a custom AMI, the AWS EKS Managed Node Group service will NOT inject the necessary bootstrap script into the supplied user data. Users can elect to provide their own user data to bootstrap and connect or opt in to use the module provided user data: + +```hcl + eks_managed_node_groups = { + custom_ami = { + ami_id = "ami-0caf35bc73450c396" + + # By default, EKS managed node groups will not append bootstrap script; + # this adds it back in using the default template provided by the module + # Note: this assumes the AMI provided is an EKS optimized AMI derivative + enable_bootstrap_user_data = true + + bootstrap_extra_args = "--container-runtime containerd --kubelet-extra-args '--max-pods=20'" + + pre_bootstrap_user_data = <<-EOT + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + EOT + + # Because we have full control over the user data supplied, we can also run additional + # scripts/configuration changes after the bootstrap script has been run + post_bootstrap_user_data = <<-EOT + echo "you are free little kubelet!" + EOT + } + } +``` + +6. There is similar support for Bottlerocket OS: + +```hcl + eks_managed_node_groups = { + bottlerocket_custom_ami = { + ami_id = "ami-0ff61e0bcfc81dc94" + platform = "bottlerocket" + + # use module user data template to bootstrap + enable_bootstrap_user_data = true + # this will get added to the template + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + + [settings.kubernetes.node-labels] + "label1" = "foo" + "label2" = "bar" + + [settings.kubernetes.node-taints] + "dedicated" = "experimental:PreferNoSchedule" + "special" = "true:NoSchedule" + EOT + } + } +``` + + +### Self Managed Node Groups + + +1. The `self-managed-node-group` uses the latest AWS EKS Optimized AMI (Linux) for the given Kubernetes version by default: + +```hcl + cluster_version = "1.22" + + # This self managed node group will use the latest AWS EKS Optimized AMI for Kubernetes 1.22 + self_managed_node_groups = { + default = {} + } +``` + +2. To use Bottlerocket, specify the `platform` as `bottlerocket` and supply a Bottlerocket OS AMI: + +```hcl + cluster_version = "1.22" + + self_managed_node_groups = { + bottlerocket = { + platform = "bottlerocket" + ami_id = data.aws_ami.bottlerocket_ami.id + } + } +``` + + +### Fargate Profiles + +Fargate profiles are straightforward to use and therefore no further details are provided here. See the for a working example of various configurations. + +### Default Configurations + +Each type of compute resource (EKS managed node group, self managed node group, or Fargate profile) provides the option for users to specify a default configuration. These default configurations can be overridden from within the compute resource's individual definition. The order of precedence for configurations (from highest to least precedence): + +- Compute resource individual configuration + - Compute resource family default configuration (`eks_managed_node_group_defaults`, `self_managed_node_group_defaults`, `fargate_profile_defaults`) + - Module default configuration (see `variables.tf` and `node_groups.tf`) + +For example, the following creates 4 AWS EKS Managed Node Groups: + +```hcl + eks_managed_node_group_defaults = { + ami_type = "AL2_x86_64" + disk_size = 50 + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + } + + eks_managed_node_groups = { + # Uses module default configurations overridden by configuration above + default = {} + + # This further overrides the instance types used + compute = { + instance_types = ["c5.large", "c6i.large", "c6d.large"] + } + + # This further overrides the instance types and disk size used + persistent = { + disk_size = 1024 + instance_types = ["r5.xlarge", "r6i.xlarge", "r5b.xlarge"] + } + + # This overrides the OS used + bottlerocket = { + ami_type = "BOTTLEROCKET_x86_64" + platform = "bottlerocket" + } + } +``` diff --git a/modules/aws-eks/docs/faq.md b/modules/aws-eks/docs/faq.md new file mode 100644 index 0000000..70c456b --- /dev/null +++ b/modules/aws-eks/docs/faq.md @@ -0,0 +1,137 @@ +# Frequently Asked Questions + +- [I received an error: `expect exactly one securityGroup tagged with kubernetes.io/cluster/ ...`](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#i-received-an-error-expect-exactly-one-securitygroup-tagged-with-kubernetesioclustername-) +- [I received an error: `Error: Invalid for_each argument ...`](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#i-received-an-error-error-invalid-for_each-argument-) +- [Why are nodes not being registered?](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#why-are-nodes-not-being-registered) +- [Why are there no changes when a node group's `desired_size` is modified?](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#why-are-there-no-changes-when-a-node-groups-desired_size-is-modified) +- [How can I deploy Windows based nodes?](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#how-can-i-deploy-windows-based-nodes) +- [How do I access compute resource attributes?](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/docs/docs/faq.md#how-do-i-access-compute-resource-attributes) + +### I received an error: `expect exactly one securityGroup tagged with kubernetes.io/cluster/ ...` + +By default, EKS creates a cluster primary security group that is created outside of the module and the EKS service adds the tag `{ "kubernetes.io/cluster/" = "owned" }`. This on its own does not cause any conflicts for addons such as the AWS Load Balancer Controller until users decide to attach both the cluster primary security group and the shared node security group created by the module (by setting `attach_cluster_primary_security_group = true`). The issue is not with having multiple security groups in your account with this tag key:value combination, but having multiple security groups with this tag key:value combination attached to nodes in the same cluster. There are a few ways to resolve this depending on your use case/intentions: + +⚠️ `` below needs to be replaced with the name of your cluster + +1. If you want to use the cluster primary security group, you can disable the creation of the shared node security group with: + +```hcl + create_node_security_group = false # default is true + attach_cluster_primary_security_group = true # default is false +``` + +2. If you want to use the cluster primary security group, you can disable the tag passed to the node security group by overriding the tag expected value like: + +```hcl + attach_cluster_primary_security_group = true # default is false + + node_security_group_tags = { + "kubernetes.io/cluster/" = "" # or any other value other than "owned" + } +``` + +3. By overriding the tag expected value on the cluster primary security group like: + +```hcl + attach_cluster_primary_security_group = true # default is false + + cluster_tags = { + "kubernetes.io/cluster/" = "" # or any other value other than "owned" + } +``` + +4. By not attaching the cluster primary security group. The cluster primary security group has quite broad access and the module has instead provided a security group with the minimum amount of access to launch an empty EKS cluster successfully and users are encouraged to open up access when necessary to support their workload. + +```hcl + attach_cluster_primary_security_group = false # this is the default for the module +``` + +In theory, if you are attaching the cluster primary security group, you shouldn't need to use the shared node security group created by the module. However, this is left up to users to decide for their requirements and use case. + +### I received an error: `Error: Invalid for_each argument ...` + +Users may encounter an error such as `Error: Invalid for_each argument - The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply ...` + +This error is due to an upstream issue with [Terraform core](https://github.com/hashicorp/terraform/issues/4149). There are two potential options you can take to help mitigate this issue: + +1. Create the dependent resources before the cluster => `terraform apply -target ` and then `terraform apply` for the cluster (or other similar means to just ensure the referenced resources exist before creating the cluster) + +- Note: this is the route users will have to take for adding additional security groups to nodes since there isn't a separate "security group attachment" resource + +2. For additional IAM policies, users can attach the policies outside of the cluster definition as demonstrated below + +```hcl +resource "aws_iam_role_policy_attachment" "additional" { + for_each = module.eks.eks_managed_node_groups + # you could also do the following or any combination: + # for_each = merge( + # module.eks.eks_managed_node_groups, + # module.eks.self_managed_node_group, + # module.eks.fargate_profile, + # ) + + # This policy does not have to exist at the time of cluster creation. Terraform can + # deduce the proper order of its creation to avoid errors during creation + policy_arn = aws_iam_policy.node_additional.arn + role = each.value.iam_role_name +} +``` + +TL;DR - Terraform resource passed into the modules map definition _must_ be known before you can apply the EKS module. The variables this potentially affects are: + +- `cluster_security_group_additional_rules` (i.e. - referencing an external security group resource in a rule) +- `node_security_group_additional_rules` (i.e. - referencing an external security group resource in a rule) +- `iam_role_additional_policies` (i.e. - referencing an external policy resource) + +### Why are nodes not being registered? + +Nodes not being able to register with the EKS control plane is generally due to networking mis-configurations. + +1. At least one of the cluster endpoints (public or private) must be enabled. + +If you require a public endpoint, setting up both (public and private) and restricting the public endpoint via setting `cluster_endpoint_public_access_cidrs` is recommended. More info regarding communication with an endpoint is available [here](https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html). + +2. Nodes need to be able to contact the EKS cluster endpoint. By default, the module only creates a public endpoint. To access the endpoint, the nodes need outgoing internet access: + +- Nodes in private subnets: via a NAT gateway or instance along with the appropriate routing rules +- Nodes in public subnets: ensure that nodes are launched with public IPs (enable through either the module here or your subnet setting defaults) + +**Important: If you apply only the public endpoint and configure the `cluster_endpoint_public_access_cidrs` to restrict access, know that EKS nodes will also use the public endpoint and you must allow access to the endpoint. If not, then your nodes will fail to work correctly.** + +3. The private endpoint can also be enabled by setting `cluster_endpoint_private_access = true`. Ensure that VPC DNS resolution and hostnames are also enabled for your VPC when the private endpoint is enabled. + +4. Nodes need to be able to connect to other AWS services to function (download container images, make API calls to assume roles, etc.). If for some reason you cannot enable public internet access for nodes you can add VPC endpoints to the relevant services: EC2 API, ECR API, ECR DKR and S3. + +### Why are there no changes when a node group's `desired_size` is modified? + +The module is configured to ignore this value. Unfortunately, Terraform does not support variables within the `lifecycle` block. The setting is ignored to allow autoscaling via controllers such as cluster autoscaler or Karpenter to work properly and without interference by Terraform. Changing the desired count must be handled outside of Terraform once the node group is created. + +### How can I deploy Windows based nodes? + +To enable Windows support for your EKS cluster, you will need to apply some configuration manually. See the [Enabling Windows Support (Windows/MacOS/Linux)](https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html#enable-windows-support). + +In addition, Windows based nodes require an additional cluster RBAC role (`eks:kube-proxy-windows`). + +Note: Windows based node support is limited to a default user data template that is provided due to the lack of Windows support and manual steps required to provision Windows based EKS nodes. + +### How do I access compute resource attributes? + +Examples of accessing the attributes of the compute resource(s) created by the root module are shown below. Note - the assumption is that your cluster module definition is named `eks` as in `module "eks" { ... }`: + +- EKS Managed Node Group attributes + +```hcl +eks_managed_role_arns = [for group in module.eks_managed_node_group : group.iam_role_arn] +```` + +- Self Managed Node Group attributes + +```hcl +self_managed_role_arns = [for group in module.self_managed_node_group : group.iam_role_arn] +``` + +- Fargate Profile attributes + +```hcl +fargate_profile_pod_execution_role_arns = [for group in module.fargate_profile : group.fargate_profile_pod_execution_role_arn] +``` diff --git a/modules/aws-eks/docs/irsa_integration.md b/modules/aws-eks/docs/irsa_integration.md new file mode 100644 index 0000000..dbf0b73 --- /dev/null +++ b/modules/aws-eks/docs/irsa_integration.md @@ -0,0 +1,84 @@ + +### IRSA Integration + +An [IAM role for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) module has been created to work in conjunction with this module.Check [`policy.tf`](https://github.com/kloia/platform-modules/tree/main/terraform-aws-iam/modules/iam-role-for-service-accounts-eks/policies.tf) for a list of the policies currently supported. One example of this integration is shown below, and more can be found in the [`iam-role-for-service-accounts`](https://github.com/kloia/platform-modules/tree/add-eks/terraform-aws-iam/examples/iam-role-for-service-accounts-eks/main.tf) example directory: + +```hcl +module "eks" { + source = "terraform-aws-modules/eks/aws" + + cluster_name = "example" + cluster_version = "1.22" + + cluster_addons = { + vpc-cni = { + resolve_conflicts = "OVERWRITE" + service_account_role_arn = module.vpc_cni_irsa.iam_role_arn + } + } + + vpc_id = "vpc-1234556abcdef" + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + + eks_managed_node_group_defaults = { + # We are using the IRSA created below for permissions + # However, we have to provision a new cluster with the policy attached FIRST + # before we can disable. Without this initial policy, + # the VPC CNI fails to assign IPs and nodes cannot join the new cluster + iam_role_attach_cni_policy = true + } + + eks_managed_node_groups = { + default = {} + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +module "vpc_cni_irsa" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + + role_name = "vpc_cni" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-node"] + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +module "karpenter_irsa" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + + role_name = "karpenter_controller" + attach_karpenter_controller_policy = true + + karpenter_controller_cluster_id = module.eks.cluster_id + karpenter_controller_node_iam_role_arns = [ + module.eks.eks_managed_node_groups["default"].iam_role_arn + ] + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["karpenter:karpenter"] + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` diff --git a/modules/aws-eks/docs/network_connectivity.md b/modules/aws-eks/docs/network_connectivity.md new file mode 100644 index 0000000..bb44773 --- /dev/null +++ b/modules/aws-eks/docs/network_connectivity.md @@ -0,0 +1,63 @@ +# Network Connectivity + +## Cluster Endpoint + +### Public Endpoint w/ Restricted CIDRs + +When restricting the clusters public endpoint to only the CIDRs specified by users, it is recommended that you also enable the private endpoint, or ensure that the CIDR blocks that you specify include the addresses that nodes and Fargate pods (if you use them) access the public endpoint from. + +Please refer to the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html) for further information + +## Security Groups + +- Cluster Security Group + - This module by default creates a cluster security group ("additional" security group when viewed from the console) in addition to the default security group created by the AWS EKS service. This "additional" security group allows users to customize inbound and outbound rules via the module as they see fit + - The default inbound/outbound rules provided by the module are derived from the [AWS minimum recommendations](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) in addition to NTP and HTTPS public internet egress rules (without, these show up in VPC flow logs as rejects - they are used for clock sync and downloading necessary packages/updates) + - The minimum inbound/outbound rules are provided for cluster and node creation to succeed without errors, but users will most likely need to add the necessary port and protocol for node-to-node communication (this is user specific based on how nodes are configured to communicate across the cluster) + - Users have the ability to opt out of the security group creation and instead provide their own externally created security group if so desired + - The security group that is created is designed to handle the bare minimum communication necessary between the control plane and the nodes, as well as any external egress to allow the cluster to successfully launch without error + - Users also have the option to supply additional, externally created security groups to the cluster as well via the `cluster_additional_security_group_ids` variable + - Lastly, users are able to opt in to attaching the primary security group automatically created by the EKS service by setting `attach_cluster_primary_security_group` = `true` from the root module for the respective node group (or set it within the node group defaults). This security group is not managed by the module; it is created by the EKS service. It permits all traffic within the domain of the security group as well as all egress traffic to the internet. + +- Node Group Security Group(s) + - Each node group (EKS Managed Node Group and Self Managed Node Group) by default creates its own security group. By default, this security group does not contain any additional security group rules. It is merely an "empty container" that offers users the ability to opt into any addition inbound our outbound rules as necessary + - Users also have the option to supply their own, and/or additional, externally created security group(s) to the node group as well via the `vpc_security_group_ids` variable + +See the example snippet below which adds additional security group rules to the cluster security group as well as the shared node security group (for node-to-node access). Users can use this extensibility to open up network access as they see fit using the security groups provided by the module: + +```hcl + ... + # Extend cluster security group rules + cluster_security_group_additional_rules = { + egress_nodes_ephemeral_ports_tcp = { + description = "To node 1025-65535" + protocol = "tcp" + from_port = 1025 + to_port = 65535 + type = "egress" + source_node_security_group = true + } + } + + # Extend node-to-node security group rules + node_security_group_additional_rules = { + ingress_self_all = { + description = "Node to node all ports/protocols" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "ingress" + self = true + } + egress_all = { + description = "Node all egress" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + } + ... +``` diff --git a/modules/aws-eks/docs/user_data.md b/modules/aws-eks/docs/user_data.md new file mode 100644 index 0000000..607d352 --- /dev/null +++ b/modules/aws-eks/docs/user_data.md @@ -0,0 +1,97 @@ +# User Data & Bootstrapping + +Users can see the various methods of using and providing user data through the [user data examples](https://github.com/kloia/platform-modules/tree/add-eks/terraform-aws-eks/examples/_user_data) as well more detailed information on the design and possible configurations via the [user data module itself](https://github.com/kloia/platform-modules/tree/add-eks/terraform-aws-eks/modules/_user_data) + +## Summary + +- AWS EKS Managed Node Groups + - By default, any supplied user data is pre-pended to the user data supplied by the EKS Managed Node Group service + - If users supply an `ami_id`, the service no longers supplies user data to bootstrap nodes; users can enable `enable_bootstrap_user_data` and use the module provided user data template, or provide their own user data template + - `bottlerocket` platform user data must be in TOML format +- Self Managed Node Groups + - `linux` platform (default) -> the user data template (bash/shell script) provided by the module is used as the default; users are able to provide their own user data template + - `bottlerocket` platform -> the user data template (TOML file) provided by the module is used as the default; users are able to provide their own user data template + - `windows` platform -> the user data template (powershell/PS1 script) provided by the module is used as the default; users are able to provide their own user data template + +The templates provided by the module can be found under the [templates directory](https://github.com/kloia/platform-modules/tree/add-eks/terraform-aws-eks/templates) + +## EKS Managed Node Group + +When using an EKS managed node group, users have 2 primary routes for interacting with the bootstrap user data: + +1. If a value for `ami_id` is not provided, users can supply additional user data that is pre-pended before the EKS Managed Node Group bootstrap user data. You can read more about this process from the [AWS supplied documentation](https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-user-data) + + - Users can use the following variables to facilitate this process: + + ```hcl + pre_bootstrap_user_data = "..." + ``` + +2. If a custom AMI is used, then per the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-custom-ami), users will need to supply the necessary user data to bootstrap and register nodes with the cluster when launched. There are two routes to facilitate this bootstrapping process: + - If the AMI used is a derivative of the [AWS EKS Optimized AMI ](https://github.com/awslabs/amazon-eks-ami), users can opt in to using a template provided by the module that provides the minimum necessary configuration to bootstrap the node when launched: + - Users can use the following variables to facilitate this process: + ```hcl + enable_bootstrap_user_data = true # to opt in to using the module supplied bootstrap user data template + pre_bootstrap_user_data = "..." + bootstrap_extra_args = "..." + post_bootstrap_user_data = "..." + ``` + - If the AMI is **NOT** an AWS EKS Optimized AMI derivative, or if users wish to have more control over the user data that is supplied to the node when launched, users have the ability to supply their own user data template that will be rendered instead of the module supplied template. Note - only the variables that are supplied to the `templatefile()` for the respective platform/OS are available for use in the supplied template, otherwise users will need to pre-render/pre-populate the template before supplying the final template to the module for rendering as user data. + - Users can use the following variables to facilitate this process: + ```hcl + user_data_template_path = "./your/user_data.sh" # user supplied bootstrap user data template + pre_bootstrap_user_data = "..." + bootstrap_extra_args = "..." + post_bootstrap_user_data = "..." + ``` + +| ℹ️ When using bottlerocket as the desired platform, since the user data for bottlerocket is TOML, all configurations are merged in the one file supplied as user data. Therefore, `pre_bootstrap_user_data` and `post_bootstrap_user_data` are not valid since the bottlerocket OS handles when various settings are applied. If you wish to supply additional configuration settings when using bottlerocket, supply them via the `bootstrap_extra_args` variable. For the linux platform, `bootstrap_extra_args` are settings that will be supplied to the [AWS EKS Optimized AMI bootstrap script](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh#L14) such as kubelet extra args, etc. See the [bottlerocket GitHub repository documentation](https://github.com/bottlerocket-os/bottlerocket#description-of-settings) for more details on what settings can be supplied via the `bootstrap_extra_args` variable. | +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + +#### ⚠️ Caveat + +Since the EKS Managed Node Group service provides the necessary bootstrap user data to nodes (unless an `ami_id` is provided), users do not have direct access to settings/variables provided by the EKS optimized AMI [`bootstrap.sh` script](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh). Currently, users must employ work-arounds to influence the `bootstrap.sh` script. For example, to enable `containerd` on EKS Managed Node Groups, users can supply the following user data. You can learn more about this issue [here](https://github.com/awslabs/amazon-eks-ami/issues/844): + +```hcl + # See issue https://github.com/awslabs/amazon-eks-ami/issues/844 + pre_bootstrap_user_data = <<-EOT + #!/bin/bash + set -ex + cat <<-EOF > /etc/profile.d/bootstrap.sh + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + export KUBELET_EXTRA_ARGS="--max-pods=110" + EOF + # Source extra environment variables in bootstrap script + sed -i '/^set -o errexit/a\\nsource /etc/profile.d/bootstrap.sh' /etc/eks/bootstrap.sh + EOT +``` + +### Self Managed Node Group + +Self managed node groups require users to provide the necessary bootstrap user data. Users can elect to use the user data template provided by the module for their platform/OS or provide their own user data template for rendering by the module. + +- If the AMI used is a derivative of the [AWS EKS Optimized AMI ](https://github.com/awslabs/amazon-eks-ami), users can opt in to using a template provided by the module that provides the minimum necessary configuration to bootstrap the node when launched: + - Users can use the following variables to facilitate this process: + ```hcl + enable_bootstrap_user_data = true # to opt in to using the module supplied bootstrap user data template + pre_bootstrap_user_data = "..." + bootstrap_extra_args = "..." + post_bootstrap_user_data = "..." + ``` + - If the AMI is **NOT** an AWS EKS Optimized AMI derivative, or if users wish to have more control over the user data that is supplied to the node when launched, users have the ability to supply their own user data template that will be rendered instead of the module supplied template. Note - only the variables that are supplied to the `templatefile()` for the respective platform/OS are available for use in the supplied template, otherwise users will need to pre-render/pre-populate the template before supplying the final template to the module for rendering as user data. + - Users can use the following variables to facilitate this process: + ```hcl + user_data_template_path = "./your/user_data.sh" # user supplied bootstrap user data template + pre_bootstrap_user_data = "..." + bootstrap_extra_args = "..." + post_bootstrap_user_data = "..." + ``` + +### Logic Diagram + +The rough flow of logic that is encapsulated within the `_user_data` module can be represented by the following diagram to better highlight the various manners in which user data can be populated. + +

+ User Data +

diff --git a/modules/aws-eks/examples/README.md b/modules/aws-eks/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/modules/aws-eks/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/modules/aws-eks/examples/complete/README.md b/modules/aws-eks/examples/complete/README.md new file mode 100644 index 0000000..7aa52f0 --- /dev/null +++ b/modules/aws-eks/examples/complete/README.md @@ -0,0 +1,101 @@ +# Complete AWS EKS Cluster + +Configuration in this directory creates an AWS EKS cluster with a broad mix of various features and settings provided by this module: + +- AWS EKS cluster +- Disabled EKS cluster +- Self managed node group +- Externally attached self managed node group +- Disabled self managed node group +- EKS managed node group +- Externally attached EKS managed node group +- Disabled self managed node group +- Fargate profile +- Externally attached Fargate profile +- Disabled Fargate profile +- Cluster addons: CoreDNS, Kube-Proxy, and VPC-CNI +- IAM roles for service accounts + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [disabled\_eks](#module\_disabled\_eks) | ../.. | n/a | +| [disabled\_eks\_managed\_node\_group](#module\_disabled\_eks\_managed\_node\_group) | ../../modules/eks-managed-node-group | n/a | +| [disabled\_fargate\_profile](#module\_disabled\_fargate\_profile) | ../../modules/fargate-profile | n/a | +| [disabled\_self\_managed\_node\_group](#module\_disabled\_self\_managed\_node\_group) | ../../modules/self-managed-node-group | n/a | +| [eks](#module\_eks) | ../.. | n/a | +| [eks\_managed\_node\_group](#module\_eks\_managed\_node\_group) | ../../modules/eks-managed-node-group | n/a | +| [fargate\_profile](#module\_fargate\_profile) | ../../modules/fargate-profile | n/a | +| [self\_managed\_node\_group](#module\_self\_managed\_node\_group) | ../../modules/self-managed-node-group | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_security_group.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_auth\_configmap\_yaml](#output\_aws\_auth\_configmap\_yaml) | Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | +| [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | +| [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | +| [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | IAM role ARN of the EKS cluster | +| [cluster\_iam\_role\_name](#output\_cluster\_iam\_role\_name) | IAM role name of the EKS cluster | +| [cluster\_iam\_role\_unique\_id](#output\_cluster\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [cluster\_id](#output\_cluster\_id) | The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready | +| [cluster\_identity\_providers](#output\_cluster\_identity\_providers) | Map of attribute maps for all EKS identity providers enabled | +| [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | +| [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | +| [cluster\_security\_group\_arn](#output\_cluster\_security\_group\_arn) | Amazon Resource Name (ARN) of the cluster security group | +| [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console | +| [cluster\_status](#output\_cluster\_status) | Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED` | +| [eks\_managed\_node\_groups](#output\_eks\_managed\_node\_groups) | Map of attribute maps for all EKS managed node groups created | +| [eks\_managed\_node\_groups\_autoscaling\_group\_names](#output\_eks\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by EKS managed node groups | +| [fargate\_profiles](#output\_fargate\_profiles) | Map of attribute maps for all EKS Fargate Profiles created | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [kms\_key\_id](#output\_kms\_key\_id) | The globally unique identifier for the key | +| [kms\_key\_policy](#output\_kms\_key\_policy) | The IAM resource policy set on the key | +| [oidc\_provider](#output\_oidc\_provider) | The OpenID Connect identity provider (issuer URL without leading `https://`) | +| [oidc\_provider\_arn](#output\_oidc\_provider\_arn) | The ARN of the OIDC Provider if `enable_irsa = true` | +| [self\_managed\_node\_groups](#output\_self\_managed\_node\_groups) | Map of attribute maps for all self managed node groups created | +| [self\_managed\_node\_groups\_autoscaling\_group\_names](#output\_self\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by self-managed node groups | + diff --git a/modules/aws-eks/examples/complete/main.tf b/modules/aws-eks/examples/complete/main.tf new file mode 100644 index 0000000..112e3b5 --- /dev/null +++ b/modules/aws-eks/examples/complete/main.tf @@ -0,0 +1,385 @@ +provider "aws" { + region = local.region + + default_tags { + tags = { + ExampleDefaultTag = "ExampleDefaultValue" + } + } +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + # This requires the awscli to be installed locally where Terraform is executed + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_id] + } +} + +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# EKS Module +################################################################################ + +module "eks" { + source = "../.." + + cluster_name = local.name + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + cluster_addons = { + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + } + } + + # Encryption key + create_kms_key = true + cluster_encryption_config = [{ + resources = ["secrets"] + }] + kms_key_deletion_window_in_days = 7 + enable_kms_key_rotation = true + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + control_plane_subnet_ids = module.vpc.intra_subnets + + # Extend cluster security group rules + cluster_security_group_additional_rules = { + egress_nodes_ephemeral_ports_tcp = { + description = "To node 1025-65535" + protocol = "tcp" + from_port = 1025 + to_port = 65535 + type = "egress" + source_node_security_group = true + } + } + + # Extend node-to-node security group rules + node_security_group_ntp_ipv4_cidr_block = ["169.254.169.123/32"] + node_security_group_additional_rules = { + ingress_self_all = { + description = "Node to node all ports/protocols" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "ingress" + self = true + } + egress_all = { + description = "Node all egress" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + } + + # Self Managed Node Group(s) + self_managed_node_group_defaults = { + vpc_security_group_ids = [aws_security_group.additional.id] + iam_role_additional_policies = ["arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"] + } + + self_managed_node_groups = { + spot = { + instance_type = "m5.large" + instance_market_options = { + market_type = "spot" + } + + pre_bootstrap_user_data = <<-EOT + echo "foo" + export FOO=bar + EOT + + bootstrap_extra_args = "--kubelet-extra-args '--node-labels=node.kubernetes.io/lifecycle=spot'" + + post_bootstrap_user_data = <<-EOT + cd /tmp + sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + EOT + } + } + + # EKS Managed Node Group(s) + eks_managed_node_group_defaults = { + ami_type = "AL2_x86_64" + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + + attach_cluster_primary_security_group = true + vpc_security_group_ids = [aws_security_group.additional.id] + } + + eks_managed_node_groups = { + blue = {} + green = { + min_size = 1 + max_size = 10 + desired_size = 1 + + instance_types = ["t3.large"] + capacity_type = "SPOT" + labels = { + Environment = "test" + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } + + taints = { + dedicated = { + key = "dedicated" + value = "gpuGroup" + effect = "NO_SCHEDULE" + } + } + + update_config = { + max_unavailable_percentage = 50 # or set `max_unavailable` + } + + tags = { + ExtraTag = "example" + } + } + } + + # Fargate Profile(s) + fargate_profiles = { + default = { + name = "default" + selectors = [ + { + namespace = "kube-system" + labels = { + k8s-app = "kube-dns" + } + }, + { + namespace = "default" + } + ] + + tags = { + Owner = "test" + } + + timeouts = { + create = "20m" + delete = "20m" + } + } + } + + # OIDC Identity provider + cluster_identity_providers = { + sts = { + client_id = "sts.amazonaws.com" + } + } + + # aws-auth configmap + manage_aws_auth_configmap = true + + aws_auth_node_iam_role_arns_non_windows = [ + module.eks_managed_node_group.iam_role_arn, + module.self_managed_node_group.iam_role_arn, + ] + aws_auth_fargate_profile_pod_execution_role_arns = [ + module.fargate_profile.fargate_profile_pod_execution_role_arn + ] + + aws_auth_roles = [ + { + rolearn = "arn:aws:iam::66666666666:role/role1" + username = "role1" + groups = ["system:masters"] + }, + ] + + aws_auth_users = [ + { + userarn = "arn:aws:iam::66666666666:user/user1" + username = "user1" + groups = ["system:masters"] + }, + { + userarn = "arn:aws:iam::66666666666:user/user2" + username = "user2" + groups = ["system:masters"] + }, + ] + + aws_auth_accounts = [ + "777777777777", + "888888888888", + ] + + tags = local.tags +} + +################################################################################ +# Sub-Module Usage on Existing/Separate Cluster +################################################################################ + +module "eks_managed_node_group" { + source = "../../modules/eks-managed-node-group" + + name = "separate-eks-mng" + cluster_name = module.eks.cluster_id + cluster_version = module.eks.cluster_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + cluster_primary_security_group_id = module.eks.cluster_primary_security_group_id + vpc_security_group_ids = [ + module.eks.cluster_security_group_id, + ] + + tags = merge(local.tags, { Separate = "eks-managed-node-group" }) +} + +module "self_managed_node_group" { + source = "../../modules/self-managed-node-group" + + name = "separate-self-mng" + cluster_name = module.eks.cluster_id + cluster_version = module.eks.cluster_version + cluster_endpoint = module.eks.cluster_endpoint + cluster_auth_base64 = module.eks.cluster_certificate_authority_data + + instance_type = "m5.large" + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + vpc_security_group_ids = [ + module.eks.cluster_primary_security_group_id, + module.eks.cluster_security_group_id, + ] + + use_default_tags = true + + tags = merge(local.tags, { Separate = "self-managed-node-group" }) +} + +module "fargate_profile" { + source = "../../modules/fargate-profile" + + name = "separate-fargate-profile" + cluster_name = module.eks.cluster_id + + subnet_ids = module.vpc.private_subnets + selectors = [{ + namespace = "kube-system" + }] + + tags = merge(local.tags, { Separate = "fargate-profile" }) +} + +################################################################################ +# Disabled creation +################################################################################ + +module "disabled_eks" { + source = "../.." + + create = false +} + +module "disabled_fargate_profile" { + source = "../../modules/fargate-profile" + + create = false +} + +module "disabled_eks_managed_node_group" { + source = "../../modules/eks-managed-node-group" + + create = false +} + +module "disabled_self_managed_node_group" { + source = "../../modules/self-managed-node-group" + + create = false +} + +################################################################################ +# Supporting resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + intra_subnets = ["10.0.7.0/28", "10.0.7.16/28", "10.0.7.32/28"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +resource "aws_security_group" "additional" { + name_prefix = "${local.name}-additional" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + ] + } + + tags = local.tags +} diff --git a/modules/aws-eks/examples/complete/outputs.tf b/modules/aws-eks/examples/complete/outputs.tf new file mode 100644 index 0000000..c612b0f --- /dev/null +++ b/modules/aws-eks/examples/complete/outputs.tf @@ -0,0 +1,182 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = module.eks.cluster_certificate_authority_data +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = module.eks.cluster_endpoint +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = module.eks.cluster_id +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = module.eks.cluster_platform_version +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = module.eks.cluster_status +} + +output "cluster_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = module.eks.cluster_security_group_id +} + +################################################################################ +# KMS Key +################################################################################ + +output "kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.eks.kms_key_arn +} + +output "kms_key_id" { + description = "The globally unique identifier for the key" + value = module.eks.kms_key_id +} + +output "kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.eks.kms_key_policy +} + +################################################################################ +# Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = module.eks.cluster_security_group_arn +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider" { + description = "The OpenID Connect identity provider (issuer URL without leading `https://`)" + value = module.eks.oidc_provider +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = module.eks.cluster_iam_role_name +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = module.eks.cluster_iam_role_arn +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.eks.cluster_iam_role_unique_id +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = module.eks.cluster_addons +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = module.eks.cluster_identity_providers +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_arn +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.eks.fargate_profiles +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +output "eks_managed_node_groups" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks.eks_managed_node_groups +} + +output "eks_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by EKS managed node groups" + value = module.eks.eks_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +output "self_managed_node_groups" { + description = "Map of attribute maps for all self managed node groups created" + value = module.eks.self_managed_node_groups +} + +output "self_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by self-managed node groups" + value = module.eks.self_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = module.eks.aws_auth_configmap_yaml +} diff --git a/modules/aws-eks/examples/complete/variables.tf b/modules/aws-eks/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-eks/examples/complete/versions.tf b/modules/aws-eks/examples/complete/versions.tf new file mode 100644 index 0000000..6d6dc45 --- /dev/null +++ b/modules/aws-eks/examples/complete/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + } +} diff --git a/modules/aws-eks/examples/eks_managed_node_group/README.md b/modules/aws-eks/examples/eks_managed_node_group/README.md new file mode 100644 index 0000000..654a116 --- /dev/null +++ b/modules/aws-eks/examples/eks_managed_node_group/README.md @@ -0,0 +1,138 @@ +# EKS Managed Node Group Example + +Configuration in this directory creates an AWS EKS cluster with various EKS Managed Node Groups demonstrating the various methods of configuring/customizing: + +- A default, "out of the box" EKS managed node group as supplied by AWS EKS +- A default, "out of the box" Bottlerocket EKS managed node group as supplied by AWS EKS +- A Bottlerocket EKS managed node group that supplies additional bootstrap settings +- A Bottlerocket EKS managed node group that demonstrates many of the configuration/customizations offered by the `eks-managed-node-group` sub-module for the Bottlerocket OS +- An EKS managed node group created from a launch template created outside of the module +- An EKS managed node group that utilizes a custom AMI that is an EKS optimized AMI derivative +- An EKS managed node group that demonstrates nearly all of the configurations/customizations offered by the `eks-managed-node-group` sub-module + +See the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) for further details. + +## Container Runtime & User Data + +When using the default AMI provided by the EKS Managed Node Group service (i.e. - not specifying a value for `ami_id`), users should be aware of the limitations of configuring the node bootstrap process via user data. Due to not having direct access to the bootrap.sh script invocation and therefore its configuration flags (this is provided by the EKS Managed Node Group service in the node user data), a workaround for ensuring the appropriate configuration settings is shown below. The following example shows how to inject configuration variables ahead of the merged user data provided by the EKS Managed Node Group service as well as how to enable the containerd runtime using this approach. More details can be found [here](https://github.com/awslabs/amazon-eks-ami/issues/844). + +```hcl + ... + # Demo of containerd usage when not specifying a custom AMI ID + # (merged into user data before EKS MNG provided user data) + containerd = { + name = "containerd" + + # See issue https://github.com/awslabs/amazon-eks-ami/issues/844 + pre_bootstrap_user_data = <<-EOT + #!/bin/bash + set -ex + cat <<-EOF > /etc/profile.d/bootstrap.sh + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + export KUBELET_EXTRA_ARGS="--max-pods=110" + EOF + # Source extra environment variables in bootstrap script + sed -i '/^set -o errexit/a\\nsource /etc/profile.d/bootstrap.sh' /etc/eks/bootstrap.sh + sed -i 's/KUBELET_EXTRA_ARGS=$2/KUBELET_EXTRA_ARGS="$2 $KUBELET_EXTRA_ARGS"/' /etc/eks/bootstrap.sh + EOT + } + ... +``` + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | +| [tls](#requirement\_tls) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | +| [tls](#provider\_tls) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks](#module\_eks) | ../.. | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc\_cni\_irsa](#module\_vpc\_cni\_irsa) | terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks | ~> 4.12 | + +## Resources + +| Name | Type | +|------|------| +| [aws_autoscaling_group_tag.cluster_autoscaler_label_tags](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group_tag) | resource | +| [aws_iam_policy.node_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role_policy_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_key_pair.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | +| [aws_kms_key.ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_kms_key.eks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_launch_template.external](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | +| [aws_security_group.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.remote_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [aws_ami.eks_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_ami.eks_default_arm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_ami.eks_default_bottlerocket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_auth\_configmap\_yaml](#output\_aws\_auth\_configmap\_yaml) | Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | +| [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | +| [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | +| [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | IAM role ARN of the EKS cluster | +| [cluster\_iam\_role\_name](#output\_cluster\_iam\_role\_name) | IAM role name of the EKS cluster | +| [cluster\_iam\_role\_unique\_id](#output\_cluster\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [cluster\_id](#output\_cluster\_id) | The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready | +| [cluster\_identity\_providers](#output\_cluster\_identity\_providers) | Map of attribute maps for all EKS identity providers enabled | +| [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | +| [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | +| [cluster\_primary\_security\_group\_id](#output\_cluster\_primary\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console | +| [cluster\_security\_group\_arn](#output\_cluster\_security\_group\_arn) | Amazon Resource Name (ARN) of the cluster security group | +| [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | ID of the cluster security group | +| [cluster\_status](#output\_cluster\_status) | Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED` | +| [eks\_managed\_node\_groups](#output\_eks\_managed\_node\_groups) | Map of attribute maps for all EKS managed node groups created | +| [eks\_managed\_node\_groups\_autoscaling\_group\_names](#output\_eks\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by EKS managed node groups | +| [fargate\_profiles](#output\_fargate\_profiles) | Map of attribute maps for all EKS Fargate Profiles created | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [kms\_key\_id](#output\_kms\_key\_id) | The globally unique identifier for the key | +| [kms\_key\_policy](#output\_kms\_key\_policy) | The IAM resource policy set on the key | +| [node\_security\_group\_arn](#output\_node\_security\_group\_arn) | Amazon Resource Name (ARN) of the node shared security group | +| [node\_security\_group\_id](#output\_node\_security\_group\_id) | ID of the node shared security group | +| [oidc\_provider](#output\_oidc\_provider) | The OpenID Connect identity provider (issuer URL without leading `https://`) | +| [oidc\_provider\_arn](#output\_oidc\_provider\_arn) | The ARN of the OIDC Provider if `enable_irsa = true` | +| [self\_managed\_node\_groups](#output\_self\_managed\_node\_groups) | Map of attribute maps for all self managed node groups created | +| [self\_managed\_node\_groups\_autoscaling\_group\_names](#output\_self\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by self-managed node groups | + diff --git a/modules/aws-eks/examples/eks_managed_node_group/main.tf b/modules/aws-eks/examples/eks_managed_node_group/main.tf new file mode 100644 index 0000000..deb1426 --- /dev/null +++ b/modules/aws-eks/examples/eks_managed_node_group/main.tf @@ -0,0 +1,718 @@ +provider "aws" { + region = local.region +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + # This requires the awscli to be installed locally where Terraform is executed + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_id] + } +} + +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + cluster_version = "1.22" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } +} + +data "aws_caller_identity" "current" {} + +################################################################################ +# EKS Module +################################################################################ + +module "eks" { + source = "../.." + + cluster_name = local.name + cluster_version = local.cluster_version + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + # IPV6 + cluster_ip_family = "ipv6" + + # We are using the IRSA created below for permissions + # However, we have to deploy with the policy attached FIRST (when creating a fresh cluster) + # and then turn this off after the cluster/node group is created. Without this initial policy, + # the VPC CNI fails to assign IPs and nodes cannot join the cluster + # See https://github.com/aws/containers-roadmap/issues/1666 for more context + # TODO - remove this policy once AWS releases a managed version similar to AmazonEKS_CNI_Policy (IPv4) + create_cni_ipv6_iam_policy = true + + cluster_addons = { + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + service_account_role_arn = module.vpc_cni_irsa.iam_role_arn + } + } + + cluster_encryption_config = [{ + provider_key_arn = aws_kms_key.eks.arn + resources = ["secrets"] + }] + + cluster_tags = { + # This should not affect the name of the cluster primary security group + Name = local.name + } + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + manage_aws_auth_configmap = true + + # Extend cluster security group rules + cluster_security_group_additional_rules = { + egress_nodes_ephemeral_ports_tcp = { + description = "To node 1025-65535" + protocol = "tcp" + from_port = 1025 + to_port = 65535 + type = "egress" + source_node_security_group = true + } + } + + # Extend node-to-node security group rules + node_security_group_ntp_ipv6_cidr_block = ["fd00:ec2::123/128"] + node_security_group_additional_rules = { + ingress_self_all = { + description = "Node to node all ports/protocols" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "ingress" + self = true + } + egress_all = { + description = "Node all egress" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + } + + eks_managed_node_group_defaults = { + ami_type = "AL2_x86_64" + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + + # We are using the IRSA created below for permissions + # However, we have to deploy with the policy attached FIRST (when creating a fresh cluster) + # and then turn this off after the cluster/node group is created. Without this initial policy, + # the VPC CNI fails to assign IPs and nodes cannot join the cluster + # See https://github.com/aws/containers-roadmap/issues/1666 for more context + iam_role_attach_cni_policy = true + } + + eks_managed_node_groups = { + # Default node group - as provided by AWS EKS + default_node_group = { + # By default, the module creates a launch template to ensure tags are propagated to instances, etc., + # so we need to disable it to use the default template provided by the AWS EKS managed node group service + create_launch_template = false + launch_template_name = "" + + disk_size = 50 + + # Remote access cannot be specified with a launch template + remote_access = { + ec2_ssh_key = aws_key_pair.this.key_name + source_security_group_ids = [aws_security_group.remote_access.id] + } + } + + # Default node group - as provided by AWS EKS using Bottlerocket + bottlerocket_default = { + # By default, the module creates a launch template to ensure tags are propagated to instances, etc., + # so we need to disable it to use the default template provided by the AWS EKS managed node group service + create_launch_template = false + launch_template_name = "" + + ami_type = "BOTTLEROCKET_x86_64" + platform = "bottlerocket" + } + + # Adds to the AWS provided user data + bottlerocket_add = { + ami_type = "BOTTLEROCKET_x86_64" + platform = "bottlerocket" + + # this will get added to what AWS provides + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT + } + + # Custom AMI, using module provided bootstrap data + bottlerocket_custom = { + # Current bottlerocket AMI + ami_id = data.aws_ami.eks_default_bottlerocket.image_id + platform = "bottlerocket" + + # use module user data template to boostrap + enable_bootstrap_user_data = true + # this will get added to the template + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + + [settings.kubernetes.node-labels] + "label1" = "foo" + "label2" = "bar" + + [settings.kubernetes.node-taints] + "dedicated" = "experimental:PreferNoSchedule" + "special" = "true:NoSchedule" + EOT + } + + # Use existing/external launch template + external_lt = { + create_launch_template = false + launch_template_name = aws_launch_template.external.name + launch_template_version = aws_launch_template.external.default_version + } + + # Use a custom AMI + custom_ami = { + ami_type = "AL2_ARM_64" + # Current default AMI used by managed node groups - pseudo "custom" + ami_id = data.aws_ami.eks_default_arm.image_id + + # This will ensure the boostrap user data is used to join the node + # By default, EKS managed node groups will not append bootstrap script; + # this adds it back in using the default template provided by the module + # Note: this assumes the AMI provided is an EKS optimized AMI derivative + enable_bootstrap_user_data = true + + instance_types = ["t4g.medium"] + } + + # Demo of containerd usage when not specifying a custom AMI ID + # (merged into user data before EKS MNG provided user data) + containerd = { + name = "containerd" + + # See issue https://github.com/awslabs/amazon-eks-ami/issues/844 + pre_bootstrap_user_data = <<-EOT + #!/bin/bash + set -ex + cat <<-EOF > /etc/profile.d/bootstrap.sh + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + export KUBELET_EXTRA_ARGS="--max-pods=110" + EOF + # Source extra environment variables in bootstrap script + sed -i '/^set -o errexit/a\\nsource /etc/profile.d/bootstrap.sh' /etc/eks/bootstrap.sh + EOT + } + + # Complete + complete = { + name = "complete-eks-mng" + use_name_prefix = true + + subnet_ids = module.vpc.private_subnets + + min_size = 1 + max_size = 7 + desired_size = 1 + + ami_id = data.aws_ami.eks_default.image_id + enable_bootstrap_user_data = true + bootstrap_extra_args = "--container-runtime containerd --kubelet-extra-args '--max-pods=20'" + + pre_bootstrap_user_data = <<-EOT + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + EOT + + post_bootstrap_user_data = <<-EOT + echo "you are free little kubelet!" + EOT + + capacity_type = "SPOT" + force_update_version = true + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + labels = { + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } + + taints = [ + { + key = "dedicated" + value = "gpuGroup" + effect = "NO_SCHEDULE" + } + ] + + update_config = { + max_unavailable_percentage = 50 # or set `max_unavailable` + } + + description = "EKS managed node group example launch template" + + ebs_optimized = true + vpc_security_group_ids = [aws_security_group.additional.id] + disable_api_termination = false + enable_monitoring = true + + block_device_mappings = { + xvda = { + device_name = "/dev/xvda" + ebs = { + volume_size = 75 + volume_type = "gp3" + iops = 3000 + throughput = 150 + encrypted = true + kms_key_id = aws_kms_key.ebs.arn + delete_on_termination = true + } + } + } + + metadata_options = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + instance_metadata_tags = "disabled" + } + + create_iam_role = true + iam_role_name = "eks-managed-node-group-complete-example" + iam_role_use_name_prefix = false + iam_role_description = "EKS managed node group complete example role" + iam_role_tags = { + Purpose = "Protector of the kubelet" + } + iam_role_additional_policies = [ + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + + create_security_group = true + security_group_name = "eks-managed-node-group-complete-example" + security_group_use_name_prefix = false + security_group_description = "EKS managed node group complete example security group" + security_group_rules = { + phoneOut = { + description = "Hello CloudFlare" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "egress" + cidr_blocks = ["1.1.1.1/32"] + } + phoneHome = { + description = "Hello cluster" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "egress" + source_cluster_security_group = true # bit of reflection lookup + } + } + security_group_tags = { + Purpose = "Protector of the kubelet" + } + + tags = { + ExtraTag = "EKS managed node group complete example" + } + } + } + + tags = local.tags +} + +# References to resources that do not exist yet when creating a cluster will cause a plan failure due to https://github.com/hashicorp/terraform/issues/4149 +# There are two options users can take +# 1. Create the dependent resources before the cluster => `terraform apply -target and then `terraform apply` +# Note: this is the route users will have to take for adding additonal security groups to nodes since there isn't a separate "security group attachment" resource +# 2. For addtional IAM policies, users can attach the policies outside of the cluster definition as demonstrated below +resource "aws_iam_role_policy_attachment" "additional" { + for_each = module.eks.eks_managed_node_groups + + policy_arn = aws_iam_policy.node_additional.arn + role = each.value.iam_role_name +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_ipv6 = true + assign_ipv6_address_on_creation = true + create_egress_only_igw = true + + public_subnet_ipv6_prefixes = [0, 1, 2] + private_subnet_ipv6_prefixes = [3, 4, 5] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +module "vpc_cni_irsa" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + version = "~> 4.12" + + role_name_prefix = "VPC-CNI-IRSA" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv6 = true + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-node"] + } + } + + tags = local.tags +} + +resource "aws_security_group" "additional" { + name_prefix = "${local.name}-additional" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + ] + } + + tags = local.tags +} + +resource "aws_kms_key" "eks" { + description = "EKS Secret Encryption Key" + deletion_window_in_days = 7 + enable_key_rotation = true + + tags = local.tags +} + +resource "aws_kms_key" "ebs" { + description = "Customer managed key to encrypt EKS managed node group volumes" + deletion_window_in_days = 7 + policy = data.aws_iam_policy_document.ebs.json +} + +# This policy is required for the KMS key used for EKS root volumes, so the cluster is allowed to enc/dec/attach encrypted EBS volumes +data "aws_iam_policy_document" "ebs" { + # Copy of default KMS policy that lets you manage it + statement { + sid = "Enable IAM User Permissions" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + } + + # Required for EKS + statement { + sid = "Allow service-linked role use of the CMK" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", # required for the ASG to manage encrypted volumes for nodes + module.eks.cluster_iam_role_arn, # required for the cluster / persistentvolume-controller to create encrypted PVCs + ] + } + } + + statement { + sid = "Allow attachment of persistent resources" + actions = ["kms:CreateGrant"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", # required for the ASG to manage encrypted volumes for nodes + module.eks.cluster_iam_role_arn, # required for the cluster / persistentvolume-controller to create encrypted PVCs + ] + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = ["true"] + } + } +} + +# This is based on the LT that EKS would create if no custom one is specified (aws ec2 describe-launch-template-versions --launch-template-id xxx) +# there are several more options one could set but you probably dont need to modify them +# you can take the default and add your custom AMI and/or custom tags +# +# Trivia: AWS transparently creates a copy of your LaunchTemplate and actually uses that copy then for the node group. If you DONT use a custom AMI, +# then the default user-data for bootstrapping a cluster is merged in the copy. + +resource "aws_launch_template" "external" { + name_prefix = "external-eks-ex-" + description = "EKS managed node group external launch template" + update_default_version = true + + block_device_mappings { + device_name = "/dev/xvda" + + ebs { + volume_size = 100 + volume_type = "gp2" + delete_on_termination = true + } + } + + monitoring { + enabled = true + } + + # Disabling due to https://github.com/hashicorp/terraform-provider-aws/issues/23766 + # network_interfaces { + # associate_public_ip_address = false + # delete_on_termination = true + # } + + # if you want to use a custom AMI + # image_id = var.ami_id + + # If you use a custom AMI, you need to supply via user-data, the bootstrap script as EKS DOESNT merge its managed user-data then + # you can add more than the minimum code you see in the template, e.g. install SSM agent, see https://github.com/aws/containers-roadmap/issues/593#issuecomment-577181345 + # (optionally you can use https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/cloudinit_config to render the script, example:) + # user_data = base64encode(data.template_file.launch_template_userdata.rendered) + + tag_specifications { + resource_type = "instance" + + tags = { + Name = "external_lt" + CustomTag = "Instance custom tag" + } + } + + tag_specifications { + resource_type = "volume" + + tags = { + CustomTag = "Volume custom tag" + } + } + + tag_specifications { + resource_type = "network-interface" + + tags = { + CustomTag = "EKS example" + } + } + + tags = { + CustomTag = "Launch template custom tag" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "tls_private_key" "this" { + algorithm = "RSA" +} + +resource "aws_key_pair" "this" { + key_name_prefix = local.name + public_key = tls_private_key.this.public_key_openssh + + tags = local.tags +} + +resource "aws_security_group" "remote_access" { + name_prefix = "${local.name}-remote-access" + description = "Allow remote SSH access" + vpc_id = module.vpc.vpc_id + + ingress { + description = "SSH access" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = local.tags +} + +resource "aws_iam_policy" "node_additional" { + name = "${local.name}-additional" + description = "Example usage of node additional policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) + + tags = local.tags +} + +data "aws_ami" "eks_default" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amazon-eks-node-${local.cluster_version}-v*"] + } +} + +data "aws_ami" "eks_default_arm" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amazon-eks-arm64-node-${local.cluster_version}-v*"] + } +} + +data "aws_ami" "eks_default_bottlerocket" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["bottlerocket-aws-k8s-${local.cluster_version}-x86_64-*"] + } +} + +################################################################################ +# Tags for the ASG to support cluster-autoscaler scale up from 0 +################################################################################ + +locals { + + # We need to lookup K8s taint effect from the AWS API value + taint_effects = { + NO_SCHEDULE = "NoSchedule" + NO_EXECUTE = "NoExecute" + PREFER_NO_SCHEDULE = "PreferNoSchedule" + } + + cluster_autoscaler_label_tags = merge([ + for name, group in module.eks.eks_managed_node_groups : { + for label_name, label_value in coalesce(group.node_group_labels, {}) : "${name}|label|${label_name}" => { + autoscaling_group = group.node_group_autoscaling_group_names[0], + key = "k8s.io/cluster-autoscaler/node-template/label/${label_name}", + value = label_value, + } + } + ]...) + + cluster_autoscaler_taint_tags = merge([ + for name, group in module.eks.eks_managed_node_groups : { + for taint in coalesce(group.node_group_taints, []) : "${name}|taint|${taint.key}" => { + autoscaling_group = group.node_group_autoscaling_group_names[0], + key = "k8s.io/cluster-autoscaler/node-template/taint/${taint.key}" + value = "${taint.value}:${local.taint_effects[taint.effect]}" + } + } + ]...) + + cluster_autoscaler_asg_tags = merge(local.cluster_autoscaler_label_tags, local.cluster_autoscaler_taint_tags) +} + +resource "aws_autoscaling_group_tag" "cluster_autoscaler_label_tags" { + for_each = local.cluster_autoscaler_asg_tags + + autoscaling_group_name = each.value.autoscaling_group + + tag { + key = each.value.key + value = each.value.value + + propagate_at_launch = false + } +} diff --git a/modules/aws-eks/examples/eks_managed_node_group/outputs.tf b/modules/aws-eks/examples/eks_managed_node_group/outputs.tf new file mode 100644 index 0000000..3e9e8dd --- /dev/null +++ b/modules/aws-eks/examples/eks_managed_node_group/outputs.tf @@ -0,0 +1,201 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = module.eks.cluster_certificate_authority_data +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = module.eks.cluster_endpoint +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = module.eks.cluster_id +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = module.eks.cluster_platform_version +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = module.eks.cluster_status +} + +output "cluster_primary_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = module.eks.cluster_primary_security_group_id +} + +################################################################################ +# KMS Key +################################################################################ + +output "kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.eks.kms_key_arn +} + +output "kms_key_id" { + description = "The globally unique identifier for the key" + value = module.eks.kms_key_id +} + +output "kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.eks.kms_key_policy +} + +################################################################################ +# Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = module.eks.cluster_security_group_arn +} + +output "cluster_security_group_id" { + description = "ID of the cluster security group" + value = module.eks.cluster_security_group_id +} + +################################################################################ +# Node Security Group +################################################################################ + +output "node_security_group_arn" { + description = "Amazon Resource Name (ARN) of the node shared security group" + value = module.eks.node_security_group_arn +} + +output "node_security_group_id" { + description = "ID of the node shared security group" + value = module.eks.node_security_group_id +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider" { + description = "The OpenID Connect identity provider (issuer URL without leading `https://`)" + value = module.eks.oidc_provider +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = module.eks.cluster_iam_role_name +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = module.eks.cluster_iam_role_arn +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.eks.cluster_iam_role_unique_id +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = module.eks.cluster_addons +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = module.eks.cluster_identity_providers +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_arn +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.eks.fargate_profiles +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +output "eks_managed_node_groups" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks.eks_managed_node_groups +} + +output "eks_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by EKS managed node groups" + value = module.eks.eks_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +output "self_managed_node_groups" { + description = "Map of attribute maps for all self managed node groups created" + value = module.eks.self_managed_node_groups +} + +output "self_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by self-managed node groups" + value = module.eks.self_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = module.eks.aws_auth_configmap_yaml +} diff --git a/modules/aws-eks/examples/eks_managed_node_group/variables.tf b/modules/aws-eks/examples/eks_managed_node_group/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-eks/examples/eks_managed_node_group/versions.tf b/modules/aws-eks/examples/eks_managed_node_group/versions.tf new file mode 100644 index 0000000..fde7af0 --- /dev/null +++ b/modules/aws-eks/examples/eks_managed_node_group/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + } +} diff --git a/modules/aws-eks/examples/fargate_profile/README.md b/modules/aws-eks/examples/fargate_profile/README.md new file mode 100644 index 0000000..81f0366 --- /dev/null +++ b/modules/aws-eks/examples/fargate_profile/README.md @@ -0,0 +1,83 @@ +# AWS EKS Cluster with Fargate profiles + +Configuration in this directory creates an AWS EKS cluster utilizing Fargate profiles. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks](#module\_eks) | ../.. | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_key.eks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_auth\_configmap\_yaml](#output\_aws\_auth\_configmap\_yaml) | Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | +| [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | +| [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | +| [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | IAM role ARN of the EKS cluster | +| [cluster\_iam\_role\_name](#output\_cluster\_iam\_role\_name) | IAM role name of the EKS cluster | +| [cluster\_iam\_role\_unique\_id](#output\_cluster\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [cluster\_id](#output\_cluster\_id) | The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready | +| [cluster\_identity\_providers](#output\_cluster\_identity\_providers) | Map of attribute maps for all EKS identity providers enabled | +| [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | +| [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | +| [cluster\_primary\_security\_group\_id](#output\_cluster\_primary\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console | +| [cluster\_security\_group\_arn](#output\_cluster\_security\_group\_arn) | Amazon Resource Name (ARN) of the cluster security group | +| [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | ID of the cluster security group | +| [cluster\_status](#output\_cluster\_status) | Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED` | +| [eks\_managed\_node\_groups](#output\_eks\_managed\_node\_groups) | Map of attribute maps for all EKS managed node groups created | +| [eks\_managed\_node\_groups\_autoscaling\_group\_names](#output\_eks\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by EKS managed node groups | +| [fargate\_profiles](#output\_fargate\_profiles) | Map of attribute maps for all EKS Fargate Profiles created | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [kms\_key\_id](#output\_kms\_key\_id) | The globally unique identifier for the key | +| [kms\_key\_policy](#output\_kms\_key\_policy) | The IAM resource policy set on the key | +| [node\_security\_group\_arn](#output\_node\_security\_group\_arn) | Amazon Resource Name (ARN) of the node shared security group | +| [node\_security\_group\_id](#output\_node\_security\_group\_id) | ID of the node shared security group | +| [oidc\_provider](#output\_oidc\_provider) | The OpenID Connect identity provider (issuer URL without leading `https://`) | +| [oidc\_provider\_arn](#output\_oidc\_provider\_arn) | The ARN of the OIDC Provider if `enable_irsa = true` | +| [self\_managed\_node\_groups](#output\_self\_managed\_node\_groups) | Map of attribute maps for all self managed node groups created | +| [self\_managed\_node\_groups\_autoscaling\_group\_names](#output\_self\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by self-managed node groups | + diff --git a/modules/aws-eks/examples/fargate_profile/main.tf b/modules/aws-eks/examples/fargate_profile/main.tf new file mode 100644 index 0000000..6c80e22 --- /dev/null +++ b/modules/aws-eks/examples/fargate_profile/main.tf @@ -0,0 +1,162 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + cluster_version = "1.22" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# EKS Module +################################################################################ + +module "eks" { + source = "../.." + + cluster_name = local.name + cluster_version = local.cluster_version + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + cluster_addons = { + # Note: https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html#fargate-gs-coredns + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + } + } + + cluster_encryption_config = [{ + provider_key_arn = aws_kms_key.eks.arn + resources = ["secrets"] + }] + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + # You require a node group to schedule coredns which is critical for running correctly internal DNS. + # If you want to use only fargate you must follow docs `(Optional) Update CoreDNS` + # available under https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html + eks_managed_node_groups = { + example = { + desired_size = 1 + + instance_types = ["t3.large"] + labels = { + Example = "managed_node_groups" + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } + tags = { + ExtraTag = "example" + } + } + } + + fargate_profiles = { + default = { + name = "default" + selectors = [ + { + namespace = "backend" + labels = { + Application = "backend" + } + }, + { + namespace = "default" + labels = { + WorkerType = "fargate" + } + } + ] + + tags = { + Owner = "default" + } + + timeouts = { + create = "20m" + delete = "20m" + } + } + + secondary = { + name = "secondary" + selectors = [ + { + namespace = "default" + labels = { + Environment = "test" + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } + } + ] + + # Using specific subnets instead of the subnets supplied for the cluster itself + subnet_ids = [module.vpc.private_subnets[1]] + + tags = { + Owner = "secondary" + } + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +resource "aws_kms_key" "eks" { + description = "EKS Secret Encryption Key" + deletion_window_in_days = 7 + enable_key_rotation = true + + tags = local.tags +} diff --git a/modules/aws-eks/examples/fargate_profile/outputs.tf b/modules/aws-eks/examples/fargate_profile/outputs.tf new file mode 100644 index 0000000..3e9e8dd --- /dev/null +++ b/modules/aws-eks/examples/fargate_profile/outputs.tf @@ -0,0 +1,201 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = module.eks.cluster_certificate_authority_data +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = module.eks.cluster_endpoint +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = module.eks.cluster_id +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = module.eks.cluster_platform_version +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = module.eks.cluster_status +} + +output "cluster_primary_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = module.eks.cluster_primary_security_group_id +} + +################################################################################ +# KMS Key +################################################################################ + +output "kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.eks.kms_key_arn +} + +output "kms_key_id" { + description = "The globally unique identifier for the key" + value = module.eks.kms_key_id +} + +output "kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.eks.kms_key_policy +} + +################################################################################ +# Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = module.eks.cluster_security_group_arn +} + +output "cluster_security_group_id" { + description = "ID of the cluster security group" + value = module.eks.cluster_security_group_id +} + +################################################################################ +# Node Security Group +################################################################################ + +output "node_security_group_arn" { + description = "Amazon Resource Name (ARN) of the node shared security group" + value = module.eks.node_security_group_arn +} + +output "node_security_group_id" { + description = "ID of the node shared security group" + value = module.eks.node_security_group_id +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider" { + description = "The OpenID Connect identity provider (issuer URL without leading `https://`)" + value = module.eks.oidc_provider +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = module.eks.cluster_iam_role_name +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = module.eks.cluster_iam_role_arn +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.eks.cluster_iam_role_unique_id +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = module.eks.cluster_addons +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = module.eks.cluster_identity_providers +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_arn +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.eks.fargate_profiles +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +output "eks_managed_node_groups" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks.eks_managed_node_groups +} + +output "eks_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by EKS managed node groups" + value = module.eks.eks_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +output "self_managed_node_groups" { + description = "Map of attribute maps for all self managed node groups created" + value = module.eks.self_managed_node_groups +} + +output "self_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by self-managed node groups" + value = module.eks.self_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = module.eks.aws_auth_configmap_yaml +} diff --git a/modules/aws-eks/examples/fargate_profile/variables.tf b/modules/aws-eks/examples/fargate_profile/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-eks/examples/fargate_profile/versions.tf b/modules/aws-eks/examples/fargate_profile/versions.tf new file mode 100644 index 0000000..6d6dc45 --- /dev/null +++ b/modules/aws-eks/examples/fargate_profile/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + } +} diff --git a/modules/aws-eks/examples/self_managed_node_group/README.md b/modules/aws-eks/examples/self_managed_node_group/README.md new file mode 100644 index 0000000..7831b49 --- /dev/null +++ b/modules/aws-eks/examples/self_managed_node_group/README.md @@ -0,0 +1,100 @@ +# Self Managed Node Groups Example + +Configuration in this directory creates an AWS EKS cluster with various Self Managed Node Groups (AutoScaling Groups) demonstrating the various methods of configuring/customizing: + +- A default, "out of the box" self managed node group as supplied by the `self-managed-node-group` sub-module +- A Bottlerocket self managed node group that demonstrates many of the configuration/customizations offered by the `self-manged-node-group` sub-module for the Bottlerocket OS +- A self managed node group that demonstrates nearly all of the configurations/customizations offered by the `self-managed-node-group` sub-module + +See the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) for further details. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | +| [tls](#requirement\_tls) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | +| [tls](#provider\_tls) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks](#module\_eks) | ../.. | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_ec2_capacity_reservation.targeted](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_capacity_reservation) | resource | +| [aws_key_pair.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | +| [aws_kms_key.ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_kms_key.eks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_security_group.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [aws_ami.eks_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_ami.eks_default_bottlerocket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_auth\_configmap\_yaml](#output\_aws\_auth\_configmap\_yaml) | Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | +| [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | +| [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | +| [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | IAM role ARN of the EKS cluster | +| [cluster\_iam\_role\_name](#output\_cluster\_iam\_role\_name) | IAM role name of the EKS cluster | +| [cluster\_iam\_role\_unique\_id](#output\_cluster\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [cluster\_id](#output\_cluster\_id) | The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready | +| [cluster\_identity\_providers](#output\_cluster\_identity\_providers) | Map of attribute maps for all EKS identity providers enabled | +| [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | +| [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | +| [cluster\_primary\_security\_group\_id](#output\_cluster\_primary\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console | +| [cluster\_security\_group\_arn](#output\_cluster\_security\_group\_arn) | Amazon Resource Name (ARN) of the cluster security group | +| [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | ID of the cluster security group | +| [cluster\_status](#output\_cluster\_status) | Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED` | +| [eks\_managed\_node\_groups](#output\_eks\_managed\_node\_groups) | Map of attribute maps for all EKS managed node groups created | +| [eks\_managed\_node\_groups\_autoscaling\_group\_names](#output\_eks\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by EKS managed node groups | +| [fargate\_profiles](#output\_fargate\_profiles) | Map of attribute maps for all EKS Fargate Profiles created | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [kms\_key\_id](#output\_kms\_key\_id) | The globally unique identifier for the key | +| [kms\_key\_policy](#output\_kms\_key\_policy) | The IAM resource policy set on the key | +| [node\_security\_group\_arn](#output\_node\_security\_group\_arn) | Amazon Resource Name (ARN) of the node shared security group | +| [node\_security\_group\_id](#output\_node\_security\_group\_id) | ID of the node shared security group | +| [oidc\_provider](#output\_oidc\_provider) | The OpenID Connect identity provider (issuer URL without leading `https://`) | +| [oidc\_provider\_arn](#output\_oidc\_provider\_arn) | The ARN of the OIDC Provider if `enable_irsa = true` | +| [self\_managed\_node\_groups](#output\_self\_managed\_node\_groups) | Map of attribute maps for all self managed node groups created | +| [self\_managed\_node\_groups\_autoscaling\_group\_names](#output\_self\_managed\_node\_groups\_autoscaling\_group\_names) | List of the autoscaling group names created by self-managed node groups | + diff --git a/modules/aws-eks/examples/self_managed_node_group/main.tf b/modules/aws-eks/examples/self_managed_node_group/main.tf new file mode 100644 index 0000000..116dbc2 --- /dev/null +++ b/modules/aws-eks/examples/self_managed_node_group/main.tf @@ -0,0 +1,474 @@ +provider "aws" { + region = local.region +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + # This requires the awscli to be installed locally where Terraform is executed + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_id] + } +} + +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + cluster_version = "1.22" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } +} + +data "aws_caller_identity" "current" {} + +################################################################################ +# EKS Module +################################################################################ + +module "eks" { + source = "../.." + + cluster_name = local.name + cluster_version = local.cluster_version + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + cluster_addons = { + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + } + } + + cluster_encryption_config = [{ + provider_key_arn = aws_kms_key.eks.arn + resources = ["secrets"] + }] + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + # Self managed node groups will not automatically create the aws-auth configmap so we need to + create_aws_auth_configmap = true + manage_aws_auth_configmap = true + + # Extend cluster security group rules + cluster_security_group_additional_rules = { + egress_nodes_ephemeral_ports_tcp = { + description = "To node 1025-65535" + protocol = "tcp" + from_port = 1025 + to_port = 65535 + type = "egress" + source_node_security_group = true + } + } + + # Extend node-to-node security group rules + node_security_group_additional_rules = { + ingress_self_all = { + description = "Node to node all ports/protocols" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "ingress" + self = true + } + egress_all = { + description = "Node all egress" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + } + + self_managed_node_group_defaults = { + create_security_group = false + + # enable discovery of autoscaling groups by cluster-autoscaler + autoscaling_group_tags = { + "k8s.io/cluster-autoscaler/enabled" : true, + "k8s.io/cluster-autoscaler/${local.name}" : "owned", + } + } + + self_managed_node_groups = { + # Default node group - as provisioned by the module defaults + default_node_group = {} + + # Bottlerocket node group + bottlerocket = { + name = "bottlerocket-self-mng" + + platform = "bottlerocket" + ami_id = data.aws_ami.eks_default_bottlerocket.id + instance_type = "m5.large" + desired_size = 2 + key_name = aws_key_pair.this.key_name + + iam_role_additional_policies = ["arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"] + + bootstrap_extra_args = <<-EOT + # The admin host container provides SSH access and runs with "superpowers". + # It is disabled by default, but can be disabled explicitly. + [settings.host-containers.admin] + enabled = false + + # The control host container provides out-of-band access via SSM. + # It is enabled by default, and can be disabled if you do not expect to use SSM. + # This could leave you with no way to access the API and change settings on an existing node! + [settings.host-containers.control] + enabled = true + + [settings.kubernetes.node-labels] + ingress = "allowed" + EOT + } + + mixed = { + name = "mixed" + + min_size = 1 + max_size = 5 + desired_size = 2 + + bootstrap_extra_args = "--kubelet-extra-args '--node-labels=node.kubernetes.io/lifecycle=spot'" + + use_mixed_instances_policy = true + mixed_instances_policy = { + instances_distribution = { + on_demand_base_capacity = 0 + on_demand_percentage_above_base_capacity = 20 + spot_allocation_strategy = "capacity-optimized" + } + + override = [ + { + instance_type = "m5.large" + weighted_capacity = "1" + }, + { + instance_type = "m6i.large" + weighted_capacity = "2" + }, + ] + } + } + + efa = { + min_size = 1 + max_size = 2 + desired_size = 1 + + # aws ec2 describe-instance-types --region eu-west-1 --filters Name=network-info.efa-supported,Values=true --query "InstanceTypes[*].[InstanceType]" --output text | sort + instance_type = "c5n.9xlarge" + + post_bootstrap_user_data = <<-EOT + + # Install EFA + curl -O https://efa-installer.amazonaws.com/aws-efa-installer-latest.tar.gz + tar -xf aws-efa-installer-latest.tar.gz && cd aws-efa-installer + ./efa_installer.sh -y --minimal + fi_info -p efa -t FI_EP_RDM + + # Disable ptrace + sysctl -w kernel.yama.ptrace_scope=0 + EOT + + network_interfaces = [ + { + description = "EFA interface example" + delete_on_termination = true + device_index = 0 + associate_public_ip_address = false + interface_type = "efa" + } + ] + } + + # Complete + complete = { + name = "complete-self-mng" + use_name_prefix = false + + subnet_ids = module.vpc.public_subnets + + min_size = 1 + max_size = 7 + desired_size = 1 + + ami_id = data.aws_ami.eks_default.id + bootstrap_extra_args = "--kubelet-extra-args '--max-pods=110'" + + pre_bootstrap_user_data = <<-EOT + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + EOT + + post_bootstrap_user_data = <<-EOT + echo "you are free little kubelet!" + EOT + + instance_type = "m6i.large" + + launch_template_name = "self-managed-ex" + launch_template_use_name_prefix = true + launch_template_description = "Self managed node group example launch template" + + ebs_optimized = true + vpc_security_group_ids = [aws_security_group.additional.id] + enable_monitoring = true + + block_device_mappings = { + xvda = { + device_name = "/dev/xvda" + ebs = { + volume_size = 75 + volume_type = "gp3" + iops = 3000 + throughput = 150 + encrypted = true + kms_key_id = aws_kms_key.ebs.arn + delete_on_termination = true + } + } + } + + metadata_options = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + instance_metadata_tags = "disabled" + } + + capacity_reservation_specification = { + capacity_reservation_target = { + capacity_reservation_id = aws_ec2_capacity_reservation.targeted.id + } + } + + create_iam_role = true + iam_role_name = "self-managed-node-group-complete-example" + iam_role_use_name_prefix = false + iam_role_description = "Self managed node group complete example role" + iam_role_tags = { + Purpose = "Protector of the kubelet" + } + iam_role_additional_policies = [ + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + + create_security_group = true + security_group_name = "self-managed-node-group-complete-example" + security_group_use_name_prefix = false + security_group_description = "Self managed node group complete example security group" + security_group_rules = { + phoneOut = { + description = "Hello CloudFlare" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "egress" + cidr_blocks = ["1.1.1.1/32"] + } + phoneHome = { + description = "Hello cluster" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "egress" + source_cluster_security_group = true # bit of reflection lookup + } + } + security_group_tags = { + Purpose = "Protector of the kubelet" + } + + timeouts = { + create = "80m" + update = "80m" + delete = "80m" + } + + tags = { + ExtraTag = "Self managed node group complete example" + } + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +resource "aws_security_group" "additional" { + name_prefix = "${local.name}-additional" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + ] + } + + tags = local.tags +} + +resource "aws_kms_key" "eks" { + description = "EKS Secret Encryption Key" + deletion_window_in_days = 7 + enable_key_rotation = true + + tags = local.tags +} + +data "aws_ami" "eks_default" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amazon-eks-node-${local.cluster_version}-v*"] + } +} + +data "aws_ami" "eks_default_bottlerocket" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["bottlerocket-aws-k8s-${local.cluster_version}-x86_64-*"] + } +} + +resource "tls_private_key" "this" { + algorithm = "RSA" +} + +resource "aws_key_pair" "this" { + key_name = local.name + public_key = tls_private_key.this.public_key_openssh +} + +resource "aws_kms_key" "ebs" { + description = "Customer managed key to encrypt self managed node group volumes" + deletion_window_in_days = 7 + policy = data.aws_iam_policy_document.ebs.json +} + +resource "aws_ec2_capacity_reservation" "targeted" { + instance_type = "m6i.large" + instance_platform = "Linux/UNIX" + availability_zone = "${local.region}a" + instance_count = 1 + instance_match_criteria = "targeted" +} + +# This policy is required for the KMS key used for EKS root volumes, so the cluster is allowed to enc/dec/attach encrypted EBS volumes +data "aws_iam_policy_document" "ebs" { + # Copy of default KMS policy that lets you manage it + statement { + sid = "Enable IAM User Permissions" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + } + + # Required for EKS + statement { + sid = "Allow service-linked role use of the CMK" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", # required for the ASG to manage encrypted volumes for nodes + module.eks.cluster_iam_role_arn, # required for the cluster / persistentvolume-controller to create encrypted PVCs + ] + } + } + + statement { + sid = "Allow attachment of persistent resources" + actions = ["kms:CreateGrant"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", # required for the ASG to manage encrypted volumes for nodes + module.eks.cluster_iam_role_arn, # required for the cluster / persistentvolume-controller to create encrypted PVCs + ] + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = ["true"] + } + } +} diff --git a/modules/aws-eks/examples/self_managed_node_group/outputs.tf b/modules/aws-eks/examples/self_managed_node_group/outputs.tf new file mode 100644 index 0000000..3e9e8dd --- /dev/null +++ b/modules/aws-eks/examples/self_managed_node_group/outputs.tf @@ -0,0 +1,201 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = module.eks.cluster_certificate_authority_data +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = module.eks.cluster_endpoint +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = module.eks.cluster_id +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = module.eks.cluster_platform_version +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = module.eks.cluster_status +} + +output "cluster_primary_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = module.eks.cluster_primary_security_group_id +} + +################################################################################ +# KMS Key +################################################################################ + +output "kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.eks.kms_key_arn +} + +output "kms_key_id" { + description = "The globally unique identifier for the key" + value = module.eks.kms_key_id +} + +output "kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.eks.kms_key_policy +} + +################################################################################ +# Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = module.eks.cluster_security_group_arn +} + +output "cluster_security_group_id" { + description = "ID of the cluster security group" + value = module.eks.cluster_security_group_id +} + +################################################################################ +# Node Security Group +################################################################################ + +output "node_security_group_arn" { + description = "Amazon Resource Name (ARN) of the node shared security group" + value = module.eks.node_security_group_arn +} + +output "node_security_group_id" { + description = "ID of the node shared security group" + value = module.eks.node_security_group_id +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider" { + description = "The OpenID Connect identity provider (issuer URL without leading `https://`)" + value = module.eks.oidc_provider +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = module.eks.cluster_iam_role_name +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = module.eks.cluster_iam_role_arn +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.eks.cluster_iam_role_unique_id +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = module.eks.cluster_addons +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = module.eks.cluster_identity_providers +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_arn +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.eks.fargate_profiles +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +output "eks_managed_node_groups" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks.eks_managed_node_groups +} + +output "eks_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by EKS managed node groups" + value = module.eks.eks_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +output "self_managed_node_groups" { + description = "Map of attribute maps for all self managed node groups created" + value = module.eks.self_managed_node_groups +} + +output "self_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by self-managed node groups" + value = module.eks.self_managed_node_groups_autoscaling_group_names +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = module.eks.aws_auth_configmap_yaml +} diff --git a/modules/aws-eks/examples/self_managed_node_group/variables.tf b/modules/aws-eks/examples/self_managed_node_group/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-eks/examples/self_managed_node_group/versions.tf b/modules/aws-eks/examples/self_managed_node_group/versions.tf new file mode 100644 index 0000000..fde7af0 --- /dev/null +++ b/modules/aws-eks/examples/self_managed_node_group/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + } +} diff --git a/modules/aws-eks/examples/user_data/README.md b/modules/aws-eks/examples/user_data/README.md new file mode 100644 index 0000000..54cd9ec --- /dev/null +++ b/modules/aws-eks/examples/user_data/README.md @@ -0,0 +1,78 @@ +# Internal User Data Module + +Configuration in this directory render various user data outputs used for testing and validating the internal `_user-data` sub-module. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks\_mng\_bottlerocket\_additional](#module\_eks\_mng\_bottlerocket\_additional) | ../../modules/_user_data | n/a | +| [eks\_mng\_bottlerocket\_custom\_ami](#module\_eks\_mng\_bottlerocket\_custom\_ami) | ../../modules/_user_data | n/a | +| [eks\_mng\_bottlerocket\_custom\_template](#module\_eks\_mng\_bottlerocket\_custom\_template) | ../../modules/_user_data | n/a | +| [eks\_mng\_bottlerocket\_no\_op](#module\_eks\_mng\_bottlerocket\_no\_op) | ../../modules/_user_data | n/a | +| [eks\_mng\_linux\_additional](#module\_eks\_mng\_linux\_additional) | ../../modules/_user_data | n/a | +| [eks\_mng\_linux\_custom\_ami](#module\_eks\_mng\_linux\_custom\_ami) | ../../modules/_user_data | n/a | +| [eks\_mng\_linux\_custom\_template](#module\_eks\_mng\_linux\_custom\_template) | ../../modules/_user_data | n/a | +| [eks\_mng\_linux\_no\_op](#module\_eks\_mng\_linux\_no\_op) | ../../modules/_user_data | n/a | +| [self\_mng\_bottlerocket\_bootstrap](#module\_self\_mng\_bottlerocket\_bootstrap) | ../../modules/_user_data | n/a | +| [self\_mng\_bottlerocket\_custom\_template](#module\_self\_mng\_bottlerocket\_custom\_template) | ../../modules/_user_data | n/a | +| [self\_mng\_bottlerocket\_no\_op](#module\_self\_mng\_bottlerocket\_no\_op) | ../../modules/_user_data | n/a | +| [self\_mng\_linux\_bootstrap](#module\_self\_mng\_linux\_bootstrap) | ../../modules/_user_data | n/a | +| [self\_mng\_linux\_custom\_template](#module\_self\_mng\_linux\_custom\_template) | ../../modules/_user_data | n/a | +| [self\_mng\_linux\_no\_op](#module\_self\_mng\_linux\_no\_op) | ../../modules/_user_data | n/a | +| [self\_mng\_windows\_bootstrap](#module\_self\_mng\_windows\_bootstrap) | ../../modules/_user_data | n/a | +| [self\_mng\_windows\_custom\_template](#module\_self\_mng\_windows\_custom\_template) | ../../modules/_user_data | n/a | +| [self\_mng\_windows\_no\_op](#module\_self\_mng\_windows\_no\_op) | ../../modules/_user_data | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [eks\_mng\_bottlerocket\_additional](#output\_eks\_mng\_bottlerocket\_additional) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_bottlerocket\_custom\_ami](#output\_eks\_mng\_bottlerocket\_custom\_ami) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_bottlerocket\_custom\_template](#output\_eks\_mng\_bottlerocket\_custom\_template) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_bottlerocket\_no\_op](#output\_eks\_mng\_bottlerocket\_no\_op) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_linux\_additional](#output\_eks\_mng\_linux\_additional) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_linux\_custom\_ami](#output\_eks\_mng\_linux\_custom\_ami) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_linux\_custom\_template](#output\_eks\_mng\_linux\_custom\_template) | Base64 decoded user data rendered for the provided inputs | +| [eks\_mng\_linux\_no\_op](#output\_eks\_mng\_linux\_no\_op) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_bottlerocket\_bootstrap](#output\_self\_mng\_bottlerocket\_bootstrap) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_bottlerocket\_custom\_template](#output\_self\_mng\_bottlerocket\_custom\_template) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_bottlerocket\_no\_op](#output\_self\_mng\_bottlerocket\_no\_op) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_linux\_bootstrap](#output\_self\_mng\_linux\_bootstrap) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_linux\_custom\_template](#output\_self\_mng\_linux\_custom\_template) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_linux\_no\_op](#output\_self\_mng\_linux\_no\_op) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_windows\_bootstrap](#output\_self\_mng\_windows\_bootstrap) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_windows\_custom\_template](#output\_self\_mng\_windows\_custom\_template) | Base64 decoded user data rendered for the provided inputs | +| [self\_mng\_windows\_no\_op](#output\_self\_mng\_windows\_no\_op) | Base64 decoded user data rendered for the provided inputs | + diff --git a/modules/aws-eks/examples/user_data/main.tf b/modules/aws-eks/examples/user_data/main.tf new file mode 100644 index 0000000..5c5b126 --- /dev/null +++ b/modules/aws-eks/examples/user_data/main.tf @@ -0,0 +1,282 @@ +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + + cluster_endpoint = "https://012345678903AB2BAE5D1E0BFE0E2B50.gr7.us-east-1.eks.amazonaws.com" + cluster_auth_base64 = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKbXFqQ1VqNGdGR2w3ZW5PeWthWnZ2RjROOTVOUEZCM2o0cGhVZUsrWGFtN2ZSQnZya0d6OGxKZmZEZWF2b2plTwpQK2xOZFlqdHZncmxCUEpYdHZIZmFzTzYxVzdIZmdWQ2EvamdRM2w3RmkvL1dpQmxFOG9oWUZkdWpjc0s1SXM2CnNkbk5KTTNYUWN2TysrSitkV09NT2ZlNzlsSWdncmdQLzgvRU9CYkw3eUY1aU1hS3lsb1RHL1V3TlhPUWt3ZUcKblBNcjdiUmdkQ1NCZTlXYXowOGdGRmlxV2FOditsTDhsODBTdFZLcWVNVlUxbjQyejVwOVpQRTd4T2l6L0xTNQpYV2lXWkVkT3pMN0xBWGVCS2gzdkhnczFxMkI2d1BKZnZnS1NzWllQRGFpZTloT1NNOUJkNFNPY3JrZTRYSVBOCkVvcXVhMlYrUDRlTWJEQzhMUkVWRDdCdVZDdWdMTldWOTBoL3VJUy9WU2VOcEdUOGVScE5DakszSjc2aFlsWm8KWjNGRG5QWUY0MWpWTHhiOXF0U1ROdEp6amYwWXBEYnFWci9xZzNmQWlxbVorMzd3YWM1eHlqMDZ4cmlaRUgzZgpUM002d2lCUEVHYVlGeWN5TmNYTk5aYW9DWDJVL0N1d2JsUHAKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==" + cluster_service_ipv4_cidr = "172.16.0.0/16" +} + +################################################################################ +# User Data Module +################################################################################ + +# EKS managed node group - linux +module "eks_mng_linux_no_op" { + source = "../../modules/_user_data" +} + +module "eks_mng_linux_additional" { + source = "../../modules/_user_data" + + pre_bootstrap_user_data = <<-EOT + export CONTAINER_RUNTIME="containerd" + EOT +} + +module "eks_mng_linux_custom_ami" { + source = "../../modules/_user_data" + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + cluster_service_ipv4_cidr = local.cluster_service_ipv4_cidr + + enable_bootstrap_user_data = true + + pre_bootstrap_user_data = <<-EOT + export CONTAINER_RUNTIME="containerd" + export USE_MAX_PODS=false + EOT + + bootstrap_extra_args = "--container-runtime containerd --kubelet-extra-args '--max-pods=20 --instance-type t3a.large'" + + post_bootstrap_user_data = <<-EOT + echo "All done" + EOT +} + + +module "eks_mng_linux_custom_template" { + source = "../../modules/_user_data" + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + user_data_template_path = "${path.module}/templates/linux_custom.tpl" + + pre_bootstrap_user_data = <<-EOT + echo "foo" + export FOO=bar + EOT + + bootstrap_extra_args = "--kubelet-extra-args '--node-labels=node.kubernetes.io/lifecycle=spot'" + + post_bootstrap_user_data = <<-EOT + echo "All done" + EOT +} + +# EKS managed node group - bottlerocket +module "eks_mng_bottlerocket_no_op" { + source = "../../modules/_user_data" + + platform = "bottlerocket" +} + +module "eks_mng_bottlerocket_additional" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT +} + +module "eks_mng_bottlerocket_custom_ami" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + enable_bootstrap_user_data = true + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT +} + +module "eks_mng_bottlerocket_custom_template" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + user_data_template_path = "${path.module}/templates/bottlerocket_custom.tpl" + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT +} + +# Self managed node group - linux +module "self_mng_linux_no_op" { + source = "../../modules/_user_data" + + is_eks_managed_node_group = false +} + +module "self_mng_linux_bootstrap" { + source = "../../modules/_user_data" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + pre_bootstrap_user_data = <<-EOT + echo "foo" + export FOO=bar + EOT + + bootstrap_extra_args = "--kubelet-extra-args '--node-labels=node.kubernetes.io/lifecycle=spot'" + + post_bootstrap_user_data = <<-EOT + echo "All done" + EOT +} + +module "self_mng_linux_custom_template" { + source = "../../modules/_user_data" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + user_data_template_path = "${path.module}/templates/linux_custom.tpl" + + pre_bootstrap_user_data = <<-EOT + echo "foo" + export FOO=bar + EOT + + bootstrap_extra_args = "--kubelet-extra-args '--node-labels=node.kubernetes.io/lifecycle=spot'" + + post_bootstrap_user_data = <<-EOT + echo "All done" + EOT +} + +# Self managed node group - bottlerocket +module "self_mng_bottlerocket_no_op" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + is_eks_managed_node_group = false +} + +module "self_mng_bottlerocket_bootstrap" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT +} + +module "self_mng_bottlerocket_custom_template" { + source = "../../modules/_user_data" + + platform = "bottlerocket" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + user_data_template_path = "${path.module}/templates/bottlerocket_custom.tpl" + + bootstrap_extra_args = <<-EOT + # extra args added + [settings.kernel] + lockdown = "integrity" + EOT +} + +# Self managed node group - windows +module "self_mng_windows_no_op" { + source = "../../modules/_user_data" + + platform = "windows" + + is_eks_managed_node_group = false +} + +module "self_mng_windows_bootstrap" { + source = "../../modules/_user_data" + + platform = "windows" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + pre_bootstrap_user_data = <<-EOT + [string]$Something = 'IDoNotKnowAnyPowerShell ¯\_(ツ)_/¯' + EOT + # I don't know if this is the right way on WindowsOS, but its just a string check here anyways + bootstrap_extra_args = "-KubeletExtraArgs --node-labels=node.kubernetes.io/lifecycle=spot" + + post_bootstrap_user_data = <<-EOT + [string]$Something = 'IStillDoNotKnowAnyPowerShell ¯\_(ツ)_/¯' + EOT +} + +module "self_mng_windows_custom_template" { + source = "../../modules/_user_data" + + platform = "windows" + + enable_bootstrap_user_data = true + is_eks_managed_node_group = false + + cluster_name = local.name + cluster_endpoint = local.cluster_endpoint + cluster_auth_base64 = local.cluster_auth_base64 + + user_data_template_path = "${path.module}/templates/windows_custom.tpl" + + pre_bootstrap_user_data = <<-EOT + [string]$Something = 'IDoNotKnowAnyPowerShell ¯\_(ツ)_/¯' + EOT + # I don't know if this is the right way on WindowsOS, but its just a string check here anyways + bootstrap_extra_args = "-KubeletExtraArgs --node-labels=node.kubernetes.io/lifecycle=spot" + + post_bootstrap_user_data = <<-EOT + [string]$Something = 'IStillDoNotKnowAnyPowerShell ¯\_(ツ)_/¯' + EOT +} diff --git a/modules/aws-eks/examples/user_data/outputs.tf b/modules/aws-eks/examples/user_data/outputs.tf new file mode 100644 index 0000000..dd2c340 --- /dev/null +++ b/modules/aws-eks/examples/user_data/outputs.tf @@ -0,0 +1,89 @@ +# EKS managed node group - linux +output "eks_mng_linux_no_op" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_linux_no_op.user_data) +} + +output "eks_mng_linux_additional" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_linux_additional.user_data) +} + +output "eks_mng_linux_custom_ami" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_linux_custom_ami.user_data) +} + +output "eks_mng_linux_custom_template" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_linux_custom_template.user_data) +} + +# EKS managed node group - bottlerocket +output "eks_mng_bottlerocket_no_op" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_bottlerocket_no_op.user_data) +} + +output "eks_mng_bottlerocket_additional" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_bottlerocket_additional.user_data) +} + +output "eks_mng_bottlerocket_custom_ami" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_bottlerocket_custom_ami.user_data) +} + +output "eks_mng_bottlerocket_custom_template" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.eks_mng_bottlerocket_custom_template.user_data) +} + +# Self managed node group - linux +output "self_mng_linux_no_op" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_linux_no_op.user_data) +} + +output "self_mng_linux_bootstrap" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_linux_bootstrap.user_data) +} + +output "self_mng_linux_custom_template" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_linux_custom_template.user_data) +} + +# Self managed node group - bottlerocket +output "self_mng_bottlerocket_no_op" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_bottlerocket_no_op.user_data) +} + +output "self_mng_bottlerocket_bootstrap" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_bottlerocket_bootstrap.user_data) +} + +output "self_mng_bottlerocket_custom_template" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_bottlerocket_custom_template.user_data) +} + +# Self managed node group - windows +output "self_mng_windows_no_op" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_windows_no_op.user_data) +} + +output "self_mng_windows_bootstrap" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_windows_bootstrap.user_data) +} + +output "self_mng_windows_custom_template" { + description = "Base64 decoded user data rendered for the provided inputs" + value = base64decode(module.self_mng_windows_custom_template.user_data) +} diff --git a/modules/aws-eks/examples/user_data/templates/bottlerocket_custom.tpl b/modules/aws-eks/examples/user_data/templates/bottlerocket_custom.tpl new file mode 100644 index 0000000..6c4d943 --- /dev/null +++ b/modules/aws-eks/examples/user_data/templates/bottlerocket_custom.tpl @@ -0,0 +1,7 @@ +# Custom user data template provided for rendering +[settings.kubernetes] +"cluster-name" = "${cluster_name}" +"api-server" = "${cluster_endpoint}" +"cluster-certificate" = "${cluster_auth_base64}" + +${bootstrap_extra_args~} diff --git a/modules/aws-eks/examples/user_data/templates/linux_custom.tpl b/modules/aws-eks/examples/user_data/templates/linux_custom.tpl new file mode 100644 index 0000000..bfe21f1 --- /dev/null +++ b/modules/aws-eks/examples/user_data/templates/linux_custom.tpl @@ -0,0 +1,10 @@ +#!/bin/bash +set -ex + +${pre_bootstrap_user_data ~} + +# Custom user data template provided for rendering +B64_CLUSTER_CA=${cluster_auth_base64} +API_SERVER_URL=${cluster_endpoint} +/etc/eks/bootstrap.sh ${cluster_name} ${bootstrap_extra_args} --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL +${post_bootstrap_user_data ~} diff --git a/modules/aws-eks/examples/user_data/templates/windows_custom.tpl b/modules/aws-eks/examples/user_data/templates/windows_custom.tpl new file mode 100644 index 0000000..3c1ca70 --- /dev/null +++ b/modules/aws-eks/examples/user_data/templates/windows_custom.tpl @@ -0,0 +1,10 @@ +# Custom user data template provided for rendering + +${pre_bootstrap_user_data ~} +[string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS" +[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1' +[string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName" +& $EKSBootstrapScriptFile -EKSClusterName ${cluster_name} -APIServerEndpoint ${cluster_endpoint} -Base64ClusterCA ${cluster_auth_base64} ${bootstrap_extra_args} 3>&1 4>&1 5>&1 6>&1 +$LastError = if ($?) { 0 } else { $Error[0].Exception.HResult } +${post_bootstrap_user_data ~} + diff --git a/modules/aws-eks/examples/user_data/variables.tf b/modules/aws-eks/examples/user_data/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-eks/examples/user_data/versions.tf b/modules/aws-eks/examples/user_data/versions.tf new file mode 100644 index 0000000..22e8d72 --- /dev/null +++ b/modules/aws-eks/examples/user_data/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + } +} diff --git a/modules/aws-eks/main.tf b/modules/aws-eks/main.tf new file mode 100644 index 0000000..7775da5 --- /dev/null +++ b/modules/aws-eks/main.tf @@ -0,0 +1,495 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + create = var.create + + cluster_role = try(aws_iam_role.this[0].arn, var.iam_role_arn) +} + +################################################################################ +# Cluster +################################################################################ + +data "aws_subnets" "private_subnets_with_eks_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } + +} + + +resource "aws_eks_cluster" "this" { + count = local.create ? 1 : 0 + + name = var.cluster_name + role_arn = local.cluster_role + version = var.cluster_version + enabled_cluster_log_types = var.cluster_enabled_log_types + + vpc_config { + security_group_ids = compact(distinct(concat(var.cluster_additional_security_group_ids, [local.cluster_security_group_id]))) + subnet_ids = try(data.aws_subnets.private_subnets_with_eks_tag.ids, null) + endpoint_private_access = var.cluster_endpoint_private_access + endpoint_public_access = var.cluster_endpoint_public_access + public_access_cidrs = var.cluster_endpoint_public_access_cidrs + } + + kubernetes_network_config { + ip_family = var.cluster_ip_family + service_ipv4_cidr = var.cluster_service_ipv4_cidr + } + + dynamic "encryption_config" { + for_each = toset(var.cluster_encryption_config) + + content { + provider { + key_arn = var.create_kms_key ? module.kms.key_arn : encryption_config.value.provider_key_arn + } + resources = encryption_config.value.resources + } + } + + tags = merge( + var.tags, + var.cluster_tags, + ) + + timeouts { + create = lookup(var.cluster_timeouts, "create", null) + update = lookup(var.cluster_timeouts, "update", null) + delete = lookup(var.cluster_timeouts, "delete", null) + } + + depends_on = [ + aws_iam_role_policy_attachment.this, + aws_security_group_rule.cluster, + aws_security_group_rule.node, + aws_cloudwatch_log_group.this + ] +} + +resource "aws_ec2_tag" "cluster_primary_security_group" { + for_each = { for k, v in merge(var.tags, var.cluster_tags) : k => v if local.create && k != "Name" && var.create_cluster_primary_security_group_tags } + + resource_id = aws_eks_cluster.this[0].vpc_config[0].cluster_security_group_id + key = each.key + value = each.value +} + +resource "aws_cloudwatch_log_group" "this" { + count = local.create && var.create_cloudwatch_log_group ? 1 : 0 + + name = "/aws/eks/${var.cluster_name}/cluster" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = var.tags +} + +################################################################################ +# KMS Key +################################################################################ + +module "kms" { + source = "terraform-aws-modules/kms/aws" + version = "1.0.2" # Note - be mindful of Terraform/provider version compatibility between modules + + create = local.create && var.create_kms_key + + description = coalesce(var.kms_key_description, "${var.cluster_name} cluster encryption key") + key_usage = "ENCRYPT_DECRYPT" + deletion_window_in_days = var.kms_key_deletion_window_in_days + enable_key_rotation = var.enable_kms_key_rotation + + # Policy + enable_default_policy = var.kms_key_enable_default_policy + key_owners = var.kms_key_owners + key_administrators = coalescelist(var.kms_key_administrators, [data.aws_caller_identity.current.arn]) + key_users = concat([local.cluster_role], var.kms_key_users) + key_service_users = var.kms_key_service_users + source_policy_documents = var.kms_key_source_policy_documents + override_policy_documents = var.kms_key_override_policy_documents + + # Aliases + aliases = concat(["eks/${var.cluster_name}"], var.kms_key_aliases) + + tags = var.tags +} + +################################################################################ +# Cluster Security Group +# Defaults follow https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html +################################################################################ + +locals { + cluster_sg_name = coalesce(var.cluster_security_group_name, "${var.cluster_name}-cluster") + create_cluster_sg = local.create && var.create_cluster_security_group + + cluster_security_group_id = local.create_cluster_sg ? aws_security_group.cluster[0].id : var.cluster_security_group_id + + cluster_security_group_rules = { + ingress_nodes_443 = { + description = "Node groups to cluster API" + protocol = "tcp" + from_port = 443 + to_port = 443 + type = "ingress" + source_node_security_group = true + } + egress_nodes_443 = { + description = "Cluster API to node groups" + protocol = "tcp" + from_port = 443 + to_port = 443 + type = "egress" + source_node_security_group = true + } + egress_nodes_kubelet = { + description = "Cluster API to node kubelets" + protocol = "tcp" + from_port = 10250 + to_port = 10250 + type = "egress" + source_node_security_group = true + } + } +} + +resource "aws_security_group" "cluster" { + count = local.create_cluster_sg ? 1 : 0 + + name = var.cluster_security_group_use_name_prefix ? null : local.cluster_sg_name + name_prefix = var.cluster_security_group_use_name_prefix ? "${local.cluster_sg_name}${var.prefix_separator}" : null + description = var.cluster_security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { "Name" = local.cluster_sg_name }, + var.cluster_security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "cluster" { + for_each = { for k, v in merge(local.cluster_security_group_rules, var.cluster_security_group_additional_rules) : k => v if local.create_cluster_sg } + + # Required + security_group_id = aws_security_group.cluster[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = try(each.value.description, null) + cidr_blocks = try(each.value.cidr_blocks, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, []) + self = try(each.value.self, null) + source_security_group_id = try( + each.value.source_security_group_id, + try(each.value.source_node_security_group, false) ? local.node_security_group_id : null + ) +} + +################################################################################ +# IRSA +# Note - this is different from EKS identity provider +################################################################################ + +data "tls_certificate" "this" { + count = local.create && var.enable_irsa ? 1 : 0 + + url = aws_eks_cluster.this[0].identity[0].oidc[0].issuer +} + +resource "aws_iam_openid_connect_provider" "oidc_provider" { + count = local.create && var.enable_irsa ? 1 : 0 + + client_id_list = distinct(compact(concat(["sts.${local.dns_suffix}"], var.openid_connect_audiences))) + thumbprint_list = concat([data.tls_certificate.this[0].certificates[0].sha1_fingerprint], var.custom_oidc_thumbprints) + url = aws_eks_cluster.this[0].identity[0].oidc[0].issuer + + tags = merge( + { Name = "${var.cluster_name}-eks-irsa" }, + var.tags + ) +} + +################################################################################ +# IAM Role +################################################################################ + +locals { + create_iam_role = local.create && var.create_iam_role + iam_role_name = coalesce(var.iam_role_name, "${var.cluster_name}-cluster") + policy_arn_prefix = "arn:${data.aws_partition.current.partition}:iam::aws:policy" + + cluster_encryption_policy_name = coalesce(var.cluster_encryption_policy_name, "${local.iam_role_name}-ClusterEncryption") + + + dns_suffix = coalesce(var.cluster_iam_role_dns_suffix, data.aws_partition.current.dns_suffix) +} + +data "aws_iam_policy_document" "assume_role_policy" { + count = local.create && var.create_iam_role ? 1 : 0 + + statement { + sid = "EKSClusterAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["eks.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}${var.prefix_separator}" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + # Resources running on the cluster are still generaring logs when destroying the module resources + # which results in the log group being re-created even after Terraform destroys it. Removing the + # ability for the cluster role to create the log group prevents this log group from being re-created + # outside of Terraform due to services still generating logs during destroy process + dynamic "inline_policy" { + for_each = var.create_cloudwatch_log_group ? [1] : [] + content { + name = local.iam_role_name + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["logs:CreateLogGroup"] + Effect = "Deny" + Resource = aws_cloudwatch_log_group.this[0].arn + }, + ] + }) + } + } + + tags = merge(var.tags, var.iam_role_tags) +} + +# Policies attached ref https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html +resource "aws_iam_role_policy_attachment" "this" { + for_each = local.create_iam_role ? toset(compact(distinct(concat([ + "${local.policy_arn_prefix}/AmazonEKSClusterPolicy", + "${local.policy_arn_prefix}/AmazonEKSVPCResourceController", + ], var.iam_role_additional_policies)))) : toset([]) + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +# Using separate attachment due to `The "for_each" value depends on resource attributes that cannot be determined until apply` +resource "aws_iam_role_policy_attachment" "cluster_encryption" { + count = local.create_iam_role && var.attach_cluster_encryption_policy && length(var.cluster_encryption_config) > 0 ? 1 : 0 + + policy_arn = aws_iam_policy.cluster_encryption[0].arn + role = aws_iam_role.this[0].name +} + +resource "aws_iam_policy" "cluster_encryption" { + count = local.create_iam_role && var.attach_cluster_encryption_policy && length(var.cluster_encryption_config) > 0 ? 1 : 0 + + name = var.cluster_encryption_policy_use_name_prefix ? null : local.cluster_encryption_policy_name + name_prefix = var.cluster_encryption_policy_use_name_prefix ? local.cluster_encryption_policy_name : null + description = var.cluster_encryption_policy_description + path = var.cluster_encryption_policy_path + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ListGrants", + "kms:DescribeKey", + ] + Effect = "Allow" + Resource = var.create_kms_key ? [module.kms.key_arn] : [for config in var.cluster_encryption_config : config.provider_key_arn] + }, + ] + }) + + tags = merge(var.tags, var.cluster_encryption_policy_tags) +} + +################################################################################ +# EKS Addons +################################################################################ + +resource "aws_eks_addon" "this" { + for_each = { for k, v in var.cluster_addons : k => v if local.create } + + cluster_name = aws_eks_cluster.this[0].name + addon_name = try(each.value.name, each.key) + + addon_version = lookup(each.value, "addon_version", null) + resolve_conflicts = lookup(each.value, "resolve_conflicts", null) + service_account_role_arn = lookup(each.value, "service_account_role_arn", null) + + depends_on = [ + module.fargate_profile, + module.eks_managed_node_group, + module.self_managed_node_group, + ] + + tags = var.tags +} + +################################################################################ +# EKS Identity Provider +# Note - this is different from IRSA +################################################################################ + +resource "aws_eks_identity_provider_config" "this" { + for_each = { for k, v in var.cluster_identity_providers : k => v if local.create } + + cluster_name = aws_eks_cluster.this[0].name + + oidc { + client_id = each.value.client_id + groups_claim = lookup(each.value, "groups_claim", null) + groups_prefix = lookup(each.value, "groups_prefix", null) + identity_provider_config_name = try(each.value.identity_provider_config_name, each.key) + issuer_url = try(each.value.issuer_url, aws_eks_cluster.this[0].identity[0].oidc[0].issuer) + required_claims = lookup(each.value, "required_claims", null) + username_claim = lookup(each.value, "username_claim", null) + username_prefix = lookup(each.value, "username_prefix", null) + } + + tags = var.tags +} + +################################################################################ +# aws-auth configmap +################################################################################ + +locals { + node_iam_role_arns_non_windows = distinct( + compact( + concat( + [for group in module.eks_managed_node_group : group.iam_role_arn], + [for group in module.self_managed_node_group : group.iam_role_arn if group.platform != "windows"], + var.aws_auth_node_iam_role_arns_non_windows, + ) + ) + ) + + node_iam_role_arns_windows = distinct( + compact( + concat( + [for group in module.self_managed_node_group : group.iam_role_arn if group.platform == "windows"], + var.aws_auth_node_iam_role_arns_windows, + ) + ) + ) + + fargate_profile_pod_execution_role_arns = distinct( + compact( + concat( + [for group in module.fargate_profile : group.fargate_profile_pod_execution_role_arn], + var.aws_auth_fargate_profile_pod_execution_role_arns, + ) + ) + ) + + aws_auth_configmap_data = { + mapRoles = yamlencode(concat( + [for role_arn in local.node_iam_role_arns_non_windows : { + rolearn = role_arn + username = "system:node:{{EC2PrivateDNSName}}" + groups = [ + "system:bootstrappers", + "system:nodes", + ] + } + ], + [for role_arn in local.node_iam_role_arns_windows : { + rolearn = role_arn + username = "system:node:{{EC2PrivateDNSName}}" + groups = [ + "eks:kube-proxy-windows", + "system:bootstrappers", + "system:nodes", + ] + } + ], + # Fargate profile + [for role_arn in local.fargate_profile_pod_execution_role_arns : { + rolearn = role_arn + username = "system:node:{{SessionName}}" + groups = [ + "system:bootstrappers", + "system:nodes", + "system:node-proxier", + ] + } + ], + var.aws_auth_roles + )) + mapUsers = yamlencode(var.aws_auth_users) + mapAccounts = yamlencode(var.aws_auth_accounts) + } +} + +resource "kubernetes_config_map" "aws_auth" { + count = var.create && var.create_aws_auth_configmap ? 1 : 0 + + metadata { + name = "aws-auth" + namespace = "kube-system" + } + + data = local.aws_auth_configmap_data + + lifecycle { + # We are ignoring the data here since we will manage it with the resource below + # This is only intended to be used in scenarios where the configmap does not exist + ignore_changes = [data] + } +} + +resource "kubernetes_config_map_v1_data" "aws_auth" { + count = var.create && var.manage_aws_auth_configmap ? 1 : 0 + + force = true + + metadata { + name = "aws-auth" + namespace = "kube-system" + } + + data = local.aws_auth_configmap_data + + depends_on = [ + # Required for instances where the configmap does not exist yet to avoid race condition + kubernetes_config_map.aws_auth, + ] +} + diff --git a/modules/aws-eks/modules/_user_data/README.md b/modules/aws-eks/modules/_user_data/README.md new file mode 100644 index 0000000..6d28da3 --- /dev/null +++ b/modules/aws-eks/modules/_user_data/README.md @@ -0,0 +1,53 @@ +# User Data Module + +Configuration in this directory renders the appropriate user data for the given inputs. See [`docs/user_data.md`](https://github.com/kloia/platform-modules/blob/add-eks/terraform-aws-eks/docs/user_data.md) for more info. + +See [`examples/user_data/`](https://github.com/kloia/platform-modules/tree/add-eks/terraform-aws-eks/examples/user_data) for various examples using this module. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [cloudinit](#requirement\_cloudinit) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [cloudinit](#provider\_cloudinit) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [cloudinit_config.linux_eks_managed_node_group](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [bootstrap\_extra\_args](#input\_bootstrap\_extra\_args) | Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data | `string` | `""` | no | +| [cluster\_auth\_base64](#input\_cluster\_auth\_base64) | Base64 encoded CA of associated EKS cluster | `string` | `""` | no | +| [cluster\_endpoint](#input\_cluster\_endpoint) | Endpoint of associated EKS cluster | `string` | `""` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the EKS cluster | `string` | `""` | no | +| [cluster\_service\_ipv4\_cidr](#input\_cluster\_service\_ipv4\_cidr) | The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks | `string` | `null` | no | +| [create](#input\_create) | Determines whether to create user-data or not | `bool` | `true` | no | +| [enable\_bootstrap\_user\_data](#input\_enable\_bootstrap\_user\_data) | Determines whether the bootstrap configurations are populated within the user data template | `bool` | `false` | no | +| [is\_eks\_managed\_node\_group](#input\_is\_eks\_managed\_node\_group) | Determines whether the user data is used on nodes in an EKS managed node group. Used to determine if user data will be appended or not | `bool` | `true` | no | +| [platform](#input\_platform) | Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based | `string` | `"linux"` | no | +| [post\_bootstrap\_user\_data](#input\_post\_bootstrap\_user\_data) | User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [pre\_bootstrap\_user\_data](#input\_pre\_bootstrap\_user\_data) | User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [user\_data\_template\_path](#input\_user\_data\_template\_path) | Path to a local, custom user data template file to use when rendering user data | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [user\_data](#output\_user\_data) | Base64 encoded user data rendered for the provided inputs | + diff --git a/modules/aws-eks/modules/_user_data/main.tf b/modules/aws-eks/modules/_user_data/main.tf new file mode 100644 index 0000000..8eec42d --- /dev/null +++ b/modules/aws-eks/modules/_user_data/main.tf @@ -0,0 +1,78 @@ + +locals { + int_linux_default_user_data = var.create && var.platform == "linux" && (var.enable_bootstrap_user_data || var.user_data_template_path != "") ? base64encode(templatefile( + coalesce(var.user_data_template_path, "${path.module}/../../templates/linux_user_data.tpl"), + { + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-custom-ami + enable_bootstrap_user_data = var.enable_bootstrap_user_data + # Required to bootstrap node + cluster_name = var.cluster_name + cluster_endpoint = var.cluster_endpoint + cluster_auth_base64 = var.cluster_auth_base64 + # Optional + cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr != null ? var.cluster_service_ipv4_cidr : "" + bootstrap_extra_args = var.bootstrap_extra_args + pre_bootstrap_user_data = var.pre_bootstrap_user_data + post_bootstrap_user_data = var.post_bootstrap_user_data + } + )) : "" + platform = { + bottlerocket = { + user_data = var.create && var.platform == "bottlerocket" && (var.enable_bootstrap_user_data || var.user_data_template_path != "" || var.bootstrap_extra_args != "") ? base64encode(templatefile( + coalesce(var.user_data_template_path, "${path.module}/../../templates/bottlerocket_user_data.tpl"), + { + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-custom-ami + enable_bootstrap_user_data = var.enable_bootstrap_user_data + # Required to bootstrap node + cluster_name = var.cluster_name + cluster_endpoint = var.cluster_endpoint + cluster_auth_base64 = var.cluster_auth_base64 + # Optional - is appended if using EKS managed node group without custom AMI + # cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr # Bottlerocket pulls this automatically https://github.com/bottlerocket-os/bottlerocket/issues/1866 + bootstrap_extra_args = var.bootstrap_extra_args + } + )) : "" + } + linux = { + user_data = try(data.cloudinit_config.linux_eks_managed_node_group[0].rendered, local.int_linux_default_user_data) + + } + windows = { + user_data = var.create && var.platform == "windows" && var.enable_bootstrap_user_data ? base64encode(templatefile( + coalesce(var.user_data_template_path, "${path.module}/../../templates/windows_user_data.tpl"), + { + # Required to bootstrap node + cluster_name = var.cluster_name + cluster_endpoint = var.cluster_endpoint + cluster_auth_base64 = var.cluster_auth_base64 + # Optional - is appended if using EKS managed node group without custom AMI + # cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr # Not supported yet: https://github.com/awslabs/amazon-eks-ami/issues/805 + bootstrap_extra_args = var.bootstrap_extra_args + pre_bootstrap_user_data = var.pre_bootstrap_user_data + post_bootstrap_user_data = var.post_bootstrap_user_data + } + )) : "" + } + } +} + +# https://github.com/aws/containers-roadmap/issues/596#issuecomment-675097667 +# An important note is that user data must in MIME multi-part archive format, +# as by default, EKS will merge the bootstrapping command required for nodes to join the +# cluster with your user data. If you use a custom AMI in your launch template, +# this merging will NOT happen and you are responsible for nodes joining the cluster. +# See docs for more details -> https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-user-data + +data "cloudinit_config" "linux_eks_managed_node_group" { + count = var.create && var.platform == "linux" && var.is_eks_managed_node_group && !var.enable_bootstrap_user_data && var.pre_bootstrap_user_data != "" && var.user_data_template_path == "" ? 1 : 0 + + base64_encode = true + gzip = false + boundary = "//" + + # Prepend to existing user data suppled by AWS EKS + part { + content_type = "text/x-shellscript" + content = var.pre_bootstrap_user_data + } +} diff --git a/modules/aws-eks/modules/_user_data/outputs.tf b/modules/aws-eks/modules/_user_data/outputs.tf new file mode 100644 index 0000000..c2a569b --- /dev/null +++ b/modules/aws-eks/modules/_user_data/outputs.tf @@ -0,0 +1,4 @@ +output "user_data" { + description = "Base64 encoded user data rendered for the provided inputs" + value = try(local.platform[var.platform].user_data, "") +} diff --git a/modules/aws-eks/modules/_user_data/variables.tf b/modules/aws-eks/modules/_user_data/variables.tf new file mode 100644 index 0000000..232e1e8 --- /dev/null +++ b/modules/aws-eks/modules/_user_data/variables.tf @@ -0,0 +1,71 @@ +variable "create" { + description = "Determines whether to create user-data or not" + type = bool + default = true +} + +variable "platform" { + description = "Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based" + type = string + default = "linux" +} + +variable "enable_bootstrap_user_data" { + description = "Determines whether the bootstrap configurations are populated within the user data template" + type = bool + default = false +} + +variable "is_eks_managed_node_group" { + description = "Determines whether the user data is used on nodes in an EKS managed node group. Used to determine if user data will be appended or not" + type = bool + default = true +} + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "" +} + +variable "cluster_endpoint" { + description = "Endpoint of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_auth_base64" { + description = "Base64 encoded CA of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_service_ipv4_cidr" { + description = "The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks" + type = string + default = null +} + +variable "pre_bootstrap_user_data" { + description = "User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "post_bootstrap_user_data" { + description = "User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "bootstrap_extra_args" { + description = "Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data" + type = string + default = "" +} + +variable "user_data_template_path" { + description = "Path to a local, custom user data template file to use when rendering user data" + type = string + default = "" +} diff --git a/modules/aws-eks/modules/_user_data/versions.tf b/modules/aws-eks/modules/_user_data/versions.tf new file mode 100644 index 0000000..e293dc6 --- /dev/null +++ b/modules/aws-eks/modules/_user_data/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + cloudinit = { + source = "hashicorp/cloudinit" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-eks/modules/eks-managed-node-group/README.md b/modules/aws-eks/modules/eks-managed-node-group/README.md new file mode 100644 index 0000000..44aa062 --- /dev/null +++ b/modules/aws-eks/modules/eks-managed-node-group/README.md @@ -0,0 +1,188 @@ +# EKS Managed Node Group Module + +Configuration in this directory creates an EKS Managed Node Group along with an IAM role, security group, and launch template + +## Usage + +```hcl +module "eks_managed_node_group" { + source = "terraform-aws-modules/eks/aws//modules/eks-managed-node-group" + + name = "separate-eks-mng" + cluster_name = "my-cluster" + cluster_version = "1.22" + + vpc_id = "vpc-1234556abcdef" + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + + // The following variables are necessary if you decide to use the module outside of the parent EKS module context. + // Without it, the security groups of the nodes are empty and thus won't join the cluster. + cluster_primary_security_group_id = module.eks.cluster_primary_security_group_id + cluster_security_group_id = module.eks.node_security_group_id + + min_size = 1 + max_size = 10 + desired_size = 1 + + instance_types = ["t3.large"] + capacity_type = "SPOT" + + labels = { + Environment = "test" + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } + + taints = { + dedicated = { + key = "dedicated" + value = "gpuGroup" + effect = "NO_SCHEDULE" + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [user\_data](#module\_user\_data) | ../_user_data | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_eks_node_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_launch_template.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ami\_id](#input\_ami\_id) | The AMI from which to launch the instance. If not supplied, EKS will use its own default image | `string` | `""` | no | +| [ami\_release\_version](#input\_ami\_release\_version) | AMI version of the EKS Node Group. Defaults to latest version for Kubernetes version | `string` | `null` | no | +| [ami\_type](#input\_ami\_type) | Type of Amazon Machine Image (AMI) associated with the EKS Node Group. Valid values are `AL2_x86_64`, `AL2_x86_64_GPU`, `AL2_ARM_64`, `CUSTOM`, `BOTTLEROCKET_ARM_64`, `BOTTLEROCKET_x86_64` | `string` | `null` | no | +| [block\_device\_mappings](#input\_block\_device\_mappings) | Specify volumes to attach to the instance besides the volumes specified by the AMI | `any` | `{}` | no | +| [bootstrap\_extra\_args](#input\_bootstrap\_extra\_args) | Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data | `string` | `""` | no | +| [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Targeting for EC2 capacity reservations | `any` | `{}` | no | +| [capacity\_type](#input\_capacity\_type) | Type of capacity associated with the EKS Node Group. Valid values: `ON_DEMAND`, `SPOT` | `string` | `"ON_DEMAND"` | no | +| [cluster\_auth\_base64](#input\_cluster\_auth\_base64) | Base64 encoded CA of associated EKS cluster | `string` | `""` | no | +| [cluster\_endpoint](#input\_cluster\_endpoint) | Endpoint of associated EKS cluster | `string` | `""` | no | +| [cluster\_ip\_family](#input\_cluster\_ip\_family) | The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6` | `string` | `null` | no | +| [cluster\_name](#input\_cluster\_name) | Name of associated EKS cluster | `string` | `null` | no | +| [cluster\_primary\_security\_group\_id](#input\_cluster\_primary\_security\_group\_id) | The ID of the EKS cluster primary security group to associate with the instance(s). This is the security group that is automatically created by the EKS service | `string` | `null` | no | +| [cluster\_security\_group\_id](#input\_cluster\_security\_group\_id) | Cluster control plane security group ID | `string` | `null` | no | +| [cluster\_service\_ipv4\_cidr](#input\_cluster\_service\_ipv4\_cidr) | The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks | `string` | `null` | no | +| [cluster\_version](#input\_cluster\_version) | Kubernetes version. Defaults to EKS Cluster Kubernetes version | `string` | `null` | no | +| [cpu\_options](#input\_cpu\_options) | The CPU options for the instance | `map(string)` | `{}` | no | +| [create](#input\_create) | Determines whether to create EKS managed node group or not | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether an IAM role is created or to use an existing IAM role | `bool` | `true` | no | +| [create\_launch\_template](#input\_create\_launch\_template) | Determines whether to create a launch template or not. If set to `false`, EKS will use its own default launch template | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines whether to create a security group | `bool` | `true` | no | +| [credit\_specification](#input\_credit\_specification) | Customize the credit specification of the instance | `map(string)` | `{}` | no | +| [desired\_size](#input\_desired\_size) | Desired number of instances/nodes | `number` | `1` | no | +| [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 instance termination protection | `bool` | `null` | no | +| [disk\_size](#input\_disk\_size) | Disk size in GiB for nodes. Defaults to `20` | `number` | `null` | no | +| [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance(s) will be EBS-optimized | `bool` | `null` | no | +| [elastic\_gpu\_specifications](#input\_elastic\_gpu\_specifications) | The elastic GPU to attach to the instance | `map(string)` | `{}` | no | +| [elastic\_inference\_accelerator](#input\_elastic\_inference\_accelerator) | Configuration block containing an Elastic Inference Accelerator to attach to the instance | `map(string)` | `{}` | no | +| [enable\_bootstrap\_user\_data](#input\_enable\_bootstrap\_user\_data) | Determines whether the bootstrap configurations are populated within the user data template | `bool` | `false` | no | +| [enable\_monitoring](#input\_enable\_monitoring) | Enables/disables detailed monitoring | `bool` | `true` | no | +| [enclave\_options](#input\_enclave\_options) | Enable Nitro Enclaves on launched instances | `map(string)` | `{}` | no | +| [force\_update\_version](#input\_force\_update\_version) | Force version update if existing pods are unable to be drained due to a pod disruption budget issue | `bool` | `null` | no | +| [iam\_role\_additional\_policies](#input\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `list(string)` | `[]` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN for the node group. Required if `create_iam_role` is set to `false` | `string` | `null` | no | +| [iam\_role\_attach\_cni\_policy](#input\_iam\_role\_attach\_cni\_policy) | Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster | `bool` | `true` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [instance\_market\_options](#input\_instance\_market\_options) | The market (purchasing) option for the instance | `any` | `{}` | no | +| [instance\_types](#input\_instance\_types) | Set of instance types associated with the EKS Node Group. Defaults to `["t3.medium"]` | `list(string)` | `null` | no | +| [kernel\_id](#input\_kernel\_id) | The kernel ID | `string` | `null` | no | +| [key\_name](#input\_key\_name) | The key name that should be used for the instance(s) | `string` | `null` | no | +| [labels](#input\_labels) | Key-value map of Kubernetes labels. Only labels that are applied with the EKS API are managed by this argument. Other Kubernetes labels applied to the EKS Node Group will not be managed | `map(string)` | `null` | no | +| [launch\_template\_default\_version](#input\_launch\_template\_default\_version) | Default version of the launch template | `string` | `null` | no | +| [launch\_template\_description](#input\_launch\_template\_description) | Description of the launch template | `string` | `null` | no | +| [launch\_template\_name](#input\_launch\_template\_name) | Launch template name - either to be created (`var.create_launch_template` = `true`) or existing (`var.create_launch_template` = `false`) | `string` | `""` | no | +| [launch\_template\_tags](#input\_launch\_template\_tags) | A map of additional tags to add to the tag\_specifications of launch template created | `map(string)` | `{}` | no | +| [launch\_template\_use\_name\_prefix](#input\_launch\_template\_use\_name\_prefix) | Determines whether to use `launch_template_name` as is or create a unique name beginning with the `launch_template_name` as the prefix | `bool` | `true` | no | +| [launch\_template\_version](#input\_launch\_template\_version) | Launch template version number. The default is `$Default` | `string` | `null` | no | +| [license\_specifications](#input\_license\_specifications) | A list of license specifications to associate with | `map(string)` | `{}` | no | +| [max\_size](#input\_max\_size) | Maximum number of instances/nodes | `number` | `3` | no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options for the instance | `map(string)` |
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 2,
"http_tokens": "required"
}
| no | +| [min\_size](#input\_min\_size) | Minimum number of instances/nodes | `number` | `0` | no | +| [name](#input\_name) | Name of the EKS managed node group | `string` | `""` | no | +| [network\_interfaces](#input\_network\_interfaces) | Customize network interfaces to be attached at instance boot time | `list(any)` | `[]` | no | +| [placement](#input\_placement) | The placement of the instance | `map(string)` | `{}` | no | +| [platform](#input\_platform) | Identifies if the OS platform is `bottlerocket` or `linux` based; `windows` is not supported | `string` | `"linux"` | no | +| [post\_bootstrap\_user\_data](#input\_post\_bootstrap\_user\_data) | User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [pre\_bootstrap\_user\_data](#input\_pre\_bootstrap\_user\_data) | User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [ram\_disk\_id](#input\_ram\_disk\_id) | The ID of the ram disk | `string` | `null` | no | +| [remote\_access](#input\_remote\_access) | Configuration block with remote access settings | `any` | `{}` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description for the security group created | `string` | `"EKS managed node group security group"` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | List of security group rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [subnet\_ids](#input\_subnet\_ids) | Identifiers of EC2 Subnets to associate with the EKS Node Group. These subnets must have the following resource tag: `kubernetes.io/cluster/CLUSTER_NAME` | `list(string)` | `null` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [taints](#input\_taints) | The Kubernetes taints to be applied to the nodes in the node group. Maximum of 50 taints per node group | `any` | `{}` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the node group | `map(string)` | `{}` | no | +| [update\_config](#input\_update\_config) | Configuration block of settings for max unavailable resources during node group updates | `map(string)` | `{}` | no | +| [update\_launch\_template\_default\_version](#input\_update\_launch\_template\_default\_version) | Whether to update the launch templates default version on each update. Conflicts with `launch_template_default_version` | `bool` | `true` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether to use `name` as is or create a unique name beginning with the `name` as the prefix | `bool` | `true` | no | +| [user\_data\_template\_path](#input\_user\_data\_template\_path) | Path to a local, custom user data template file to use when rendering user data | `string` | `""` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where the security group/nodes will be provisioned | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | A list of security group IDs to associate | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [launch\_template\_arn](#output\_launch\_template\_arn) | The ARN of the launch template | +| [launch\_template\_id](#output\_launch\_template\_id) | The ID of the launch template | +| [launch\_template\_latest\_version](#output\_launch\_template\_latest\_version) | The latest version of the launch template | +| [launch\_template\_name](#output\_launch\_template\_name) | The name of the launch template | +| [node\_group\_arn](#output\_node\_group\_arn) | Amazon Resource Name (ARN) of the EKS Node Group | +| [node\_group\_autoscaling\_group\_names](#output\_node\_group\_autoscaling\_group\_names) | List of the autoscaling group names | +| [node\_group\_id](#output\_node\_group\_id) | EKS Cluster name and EKS Node Group name separated by a colon (`:`) | +| [node\_group\_labels](#output\_node\_group\_labels) | Map of labels applied to the node group | +| [node\_group\_resources](#output\_node\_group\_resources) | List of objects containing information about underlying resources | +| [node\_group\_status](#output\_node\_group\_status) | Status of the EKS Node Group | +| [node\_group\_taints](#output\_node\_group\_taints) | List of objects containing information about taints applied to the node group | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | + diff --git a/modules/aws-eks/modules/eks-managed-node-group/main.tf b/modules/aws-eks/modules/eks-managed-node-group/main.tf new file mode 100644 index 0000000..65b3d23 --- /dev/null +++ b/modules/aws-eks/modules/eks-managed-node-group/main.tf @@ -0,0 +1,456 @@ +data "aws_partition" "current" {} + +data "aws_caller_identity" "current" {} + +################################################################################ +# User Data +################################################################################ + +module "user_data" { + source = "../_user_data" + + create = var.create + platform = var.platform + + cluster_name = var.cluster_name + cluster_endpoint = var.cluster_endpoint + cluster_auth_base64 = var.cluster_auth_base64 + + cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr + + enable_bootstrap_user_data = var.enable_bootstrap_user_data + pre_bootstrap_user_data = var.pre_bootstrap_user_data + post_bootstrap_user_data = var.post_bootstrap_user_data + bootstrap_extra_args = var.bootstrap_extra_args + user_data_template_path = var.user_data_template_path +} + +################################################################################ +# Launch template +################################################################################ + +locals { + # There are 4 scenarios here that have to be considered for `use_custom_launch_template`: + # 1. `var.create_launch_template = false && var.launch_template_name == ""` => EKS MNG will use its own default LT + # 2. `var.create_launch_template = false && var.launch_template_name == "something"` => User provided custom LT will be used + # 3. `var.create_launch_template = true && var.launch_template_name == ""` => Custom LT will be used, module will provide a default name + # 4. `var.create_launch_template = true && var.launch_template_name == "something"` => Custom LT will be used, LT name is provided by user + use_custom_launch_template = var.create_launch_template || var.launch_template_name != "" + + launch_template_name_int = coalesce(var.launch_template_name, "${var.name}-eks-node-group") + + security_group_ids = compact(concat([try(aws_security_group.this[0].id, ""), var.cluster_primary_security_group_id], var.vpc_security_group_ids)) +} + +resource "aws_launch_template" "this" { + count = var.create && var.create_launch_template ? 1 : 0 + + name = var.launch_template_use_name_prefix ? null : local.launch_template_name_int + name_prefix = var.launch_template_use_name_prefix ? "${local.launch_template_name_int}-" : null + description = var.launch_template_description + + ebs_optimized = var.ebs_optimized + image_id = var.ami_id + # # Set on node group instead + # instance_type = var.launch_template_instance_type + key_name = var.key_name + user_data = module.user_data.user_data + + vpc_security_group_ids = length(var.network_interfaces) > 0 ? [] : local.security_group_ids + + default_version = var.launch_template_default_version + update_default_version = var.update_launch_template_default_version + disable_api_termination = var.disable_api_termination + # Set on EKS managed node group, will fail if set here + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-basics + # instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + kernel_id = var.kernel_id + ram_disk_id = var.ram_disk_id + + dynamic "block_device_mappings" { + for_each = var.block_device_mappings + content { + device_name = block_device_mappings.value.device_name + no_device = lookup(block_device_mappings.value, "no_device", null) + virtual_name = lookup(block_device_mappings.value, "virtual_name", null) + + dynamic "ebs" { + for_each = flatten([lookup(block_device_mappings.value, "ebs", [])]) + content { + delete_on_termination = lookup(ebs.value, "delete_on_termination", null) + encrypted = lookup(ebs.value, "encrypted", null) + kms_key_id = lookup(ebs.value, "kms_key_id", null) + iops = lookup(ebs.value, "iops", null) + throughput = lookup(ebs.value, "throughput", null) + snapshot_id = lookup(ebs.value, "snapshot_id", null) + volume_size = var.disk_size #lookup(ebs.value, "volume_size", null) + volume_type = lookup(ebs.value, "volume_type", null) + } + } + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + content { + capacity_reservation_preference = lookup(capacity_reservation_specification.value, "capacity_reservation_preference", null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + content { + capacity_reservation_id = lookup(capacity_reservation_target.value, "capacity_reservation_id", null) + } + } + } + } + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + content { + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core + } + } + + dynamic "credit_specification" { + for_each = length(var.credit_specification) > 0 ? [var.credit_specification] : [] + content { + cpu_credits = credit_specification.value.cpu_credits + } + } + + dynamic "elastic_gpu_specifications" { + for_each = length(var.elastic_gpu_specifications) > 0 ? [var.elastic_gpu_specifications] : [] + content { + type = elastic_gpu_specifications.value.type + } + } + + dynamic "elastic_inference_accelerator" { + for_each = length(var.elastic_inference_accelerator) > 0 ? [var.elastic_inference_accelerator] : [] + content { + type = elastic_inference_accelerator.value.type + } + } + + dynamic "enclave_options" { + for_each = length(var.enclave_options) > 0 ? [var.enclave_options] : [] + content { + enabled = enclave_options.value.enabled + } + } + + # Set on EKS managed node group, will fail if set here + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-basics + # dynamic "hibernation_options" { + # for_each = var.hibernation_options != null ? [var.hibernation_options] : [] + # content { + # configured = hibernation_options.value.configured + # } + # } + + # Set on EKS managed node group, will fail if set here + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-basics + # dynamic "iam_instance_profile" { + # for_each = [var.iam_instance_profile] + # content { + # name = lookup(var.iam_instance_profile, "name", null) + # arn = lookup(var.iam_instance_profile, "arn", null) + # } + # } + + dynamic "instance_market_options" { + for_each = length(var.instance_market_options) > 0 ? [var.instance_market_options] : [] + content { + market_type = instance_market_options.value.market_type + + dynamic "spot_options" { + for_each = length(lookup(instance_market_options.value, "spot_options", {})) > 0 ? [instance_market_options.value.spot_options] : [] + content { + block_duration_minutes = lookup(spot_options.value, "block_duration_minutes", null) + instance_interruption_behavior = lookup(spot_options.value, "instance_interruption_behavior", null) + max_price = lookup(spot_options.value, "max_price", null) + spot_instance_type = lookup(spot_options.value, "spot_instance_type", null) + valid_until = lookup(spot_options.value, "valid_until", null) + } + } + } + } + + dynamic "license_specification" { + for_each = length(var.license_specifications) > 0 ? [var.license_specifications] : [] + content { + license_configuration_arn = license_specifications.value.license_configuration_arn + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + content { + http_endpoint = lookup(metadata_options.value, "http_endpoint", null) + http_tokens = lookup(metadata_options.value, "http_tokens", null) + http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", null) + http_protocol_ipv6 = lookup(metadata_options.value, "http_protocol_ipv6", null) + instance_metadata_tags = lookup(metadata_options.value, "instance_metadata_tags", null) + } + } + + dynamic "monitoring" { + for_each = var.enable_monitoring != null ? [1] : [] + content { + enabled = var.enable_monitoring + } + } + + dynamic "network_interfaces" { + for_each = var.network_interfaces + content { + associate_carrier_ip_address = lookup(network_interfaces.value, "associate_carrier_ip_address", null) + associate_public_ip_address = lookup(network_interfaces.value, "associate_public_ip_address", null) + delete_on_termination = lookup(network_interfaces.value, "delete_on_termination", null) + description = lookup(network_interfaces.value, "description", null) + device_index = lookup(network_interfaces.value, "device_index", null) + interface_type = lookup(network_interfaces.value, "interface_type", null) + ipv4_addresses = try(network_interfaces.value.ipv4_addresses, []) + ipv4_address_count = lookup(network_interfaces.value, "ipv4_address_count", null) + ipv6_addresses = try(network_interfaces.value.ipv6_addresses, []) + ipv6_address_count = lookup(network_interfaces.value, "ipv6_address_count", null) + network_interface_id = lookup(network_interfaces.value, "network_interface_id", null) + private_ip_address = lookup(network_interfaces.value, "private_ip_address", null) + security_groups = compact(concat(try(network_interfaces.value.security_groups, []), local.security_group_ids)) + # Set on EKS managed node group, will fail if set here + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-basics + # subnet_id = lookup(network_interfaces.value, "subnet_id", null) + } + } + + dynamic "placement" { + for_each = length(var.placement) > 0 ? [var.placement] : [] + content { + affinity = lookup(placement.value, "affinity", null) + availability_zone = lookup(placement.value, "availability_zone", null) + group_name = lookup(placement.value, "group_name", null) + host_id = lookup(placement.value, "host_id", null) + spread_domain = lookup(placement.value, "spread_domain", null) + tenancy = lookup(placement.value, "tenancy", null) + partition_number = lookup(placement.value, "partition_number", null) + } + } + + dynamic "tag_specifications" { + for_each = toset(["instance", "volume", "network-interface"]) + content { + resource_type = tag_specifications.key + tags = merge(var.tags, { Name = var.name }, var.launch_template_tags) + } + } + + lifecycle { + create_before_destroy = true + } + + # Prevent premature access of security group roles and policies by pods that + # require permissions on create/destroy that depend on nodes + depends_on = [ + aws_security_group_rule.this, + aws_iam_role_policy_attachment.this, + ] + + tags = var.tags +} + +################################################################################ +# Node Group +################################################################################ + +locals { + launch_template_name = try(aws_launch_template.this[0].name, var.launch_template_name, null) + # Change order to allow users to set version priority before using defaults + launch_template_version = coalesce(var.launch_template_version, try(aws_launch_template.this[0].default_version, "$Default")) +} + +resource "aws_eks_node_group" "this" { + count = var.create ? 1 : 0 + + # Required + cluster_name = var.cluster_name + node_role_arn = var.create_iam_role ? aws_iam_role.this[0].arn : var.iam_role_arn + subnet_ids = var.subnet_ids + + scaling_config { + min_size = var.min_size + max_size = var.max_size + desired_size = var.desired_size + } + + # Optional + node_group_name = var.use_name_prefix ? null : var.name + node_group_name_prefix = var.use_name_prefix ? "${var.name}-" : null + + # https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-custom-ami + ami_type = var.ami_id != "" ? null : var.ami_type + release_version = var.ami_id != "" ? null : var.ami_release_version + version = var.ami_id != "" ? null : var.cluster_version + + capacity_type = var.capacity_type + disk_size = local.use_custom_launch_template ? null : var.disk_size # if using LT, set disk size on LT or else it will error here + force_update_version = var.force_update_version + instance_types = var.instance_types + labels = var.labels + + dynamic "launch_template" { + for_each = local.use_custom_launch_template ? [1] : [] + content { + name = local.launch_template_name + version = local.launch_template_version + } + } + + dynamic "remote_access" { + for_each = length(var.remote_access) > 0 ? [var.remote_access] : [] + content { + ec2_ssh_key = try(remote_access.value.ec2_ssh_key, null) + source_security_group_ids = try(remote_access.value.source_security_group_ids, []) + } + } + + dynamic "taint" { + for_each = var.taints + content { + key = taint.value.key + value = lookup(taint.value, "value") + effect = taint.value.effect + } + } + + dynamic "update_config" { + for_each = length(var.update_config) > 0 ? [var.update_config] : [] + content { + max_unavailable_percentage = try(update_config.value.max_unavailable_percentage, null) + max_unavailable = try(update_config.value.max_unavailable, null) + } + } + + timeouts { + create = lookup(var.timeouts, "create", null) + update = lookup(var.timeouts, "update", null) + delete = lookup(var.timeouts, "delete", null) + } + + lifecycle { + create_before_destroy = true + ignore_changes = [ + scaling_config[0].desired_size, + ] + } + + tags = merge( + var.tags, + { Name = var.name } + ) +} + +################################################################################ +# Security Group +################################################################################ + +locals { + security_group_name = coalesce(var.security_group_name, "${var.name}-eks-node-group") + create_security_group = var.create && var.create_security_group +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { "Name" = local.security_group_name }, + var.security_group_tags + ) + + # https://github.com/hashicorp/terraform-provider-aws/issues/2445 + # https://github.com/hashicorp/terraform-provider-aws/issues/9692 + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = try(each.value.description, null) + cidr_blocks = try(each.value.cidr_blocks, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, []) + self = try(each.value.self, null) + source_security_group_id = try( + each.value.source_security_group_id, + try(each.value.source_cluster_security_group, false) ? var.cluster_security_group_id : null + ) +} + +################################################################################ +# IAM Role +################################################################################ + +locals { + iam_role_name = coalesce(var.iam_role_name, "${var.name}-eks-node-group") + + iam_role_policy_prefix = "arn:${data.aws_partition.current.partition}:iam::aws:policy" + + cni_policy = var.cluster_ip_family == "ipv6" ? "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:policy/AmazonEKS_CNI_IPv6_Policy" : "${local.iam_role_policy_prefix}/AmazonEKS_CNI_Policy" +} + +data "aws_iam_policy_document" "assume_role_policy" { + count = var.create && var.create_iam_role ? 1 : 0 + + statement { + sid = "EKSNodeAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create && var.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +# Policies attached ref https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group +resource "aws_iam_role_policy_attachment" "this" { + for_each = var.create && var.create_iam_role ? toset(compact(distinct(concat([ + "${local.iam_role_policy_prefix}/AmazonEKSWorkerNodePolicy", + "${local.iam_role_policy_prefix}/AmazonEC2ContainerRegistryReadOnly", + var.iam_role_attach_cni_policy ? local.cni_policy : "", + ], var.iam_role_additional_policies)))) : toset([]) + + policy_arn = each.value + role = aws_iam_role.this[0].name +} diff --git a/modules/aws-eks/modules/eks-managed-node-group/outputs.tf b/modules/aws-eks/modules/eks-managed-node-group/outputs.tf new file mode 100644 index 0000000..9d75353 --- /dev/null +++ b/modules/aws-eks/modules/eks-managed-node-group/outputs.tf @@ -0,0 +1,95 @@ +################################################################################ +# Launch template +################################################################################ + +output "launch_template_id" { + description = "The ID of the launch template" + value = try(aws_launch_template.this[0].id, "") +} + +output "launch_template_arn" { + description = "The ARN of the launch template" + value = try(aws_launch_template.this[0].arn, "") +} + +output "launch_template_latest_version" { + description = "The latest version of the launch template" + value = try(aws_launch_template.this[0].latest_version, "") +} + +output "launch_template_name" { + description = "The name of the launch template" + value = try(aws_launch_template.this[0].name, "") +} + +################################################################################ +# Node Group +################################################################################ + +output "node_group_arn" { + description = "Amazon Resource Name (ARN) of the EKS Node Group" + value = try(aws_eks_node_group.this[0].arn, "") +} + +output "node_group_id" { + description = "EKS Cluster name and EKS Node Group name separated by a colon (`:`)" + value = try(aws_eks_node_group.this[0].id, "") +} + +output "node_group_resources" { + description = "List of objects containing information about underlying resources" + value = try(aws_eks_node_group.this[0].resources, "") +} + +output "node_group_autoscaling_group_names" { + description = "List of the autoscaling group names" + value = try(flatten(aws_eks_node_group.this[0].resources[*].autoscaling_groups[*].name), []) +} + +output "node_group_status" { + description = "Status of the EKS Node Group" + value = try(aws_eks_node_group.this[0].arn, "") +} + +output "node_group_labels" { + description = "Map of labels applied to the node group" + value = try(aws_eks_node_group.this[0].labels, {}) +} + +output "node_group_taints" { + description = "List of objects containing information about taints applied to the node group" + value = try(aws_eks_node_group.this[0].taint, []) +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, "") +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, "") +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, var.iam_role_arn) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/aws-eks/modules/eks-managed-node-group/variables.tf b/modules/aws-eks/modules/eks-managed-node-group/variables.tf new file mode 100644 index 0000000..72a5aa7 --- /dev/null +++ b/modules/aws-eks/modules/eks-managed-node-group/variables.tf @@ -0,0 +1,491 @@ +variable "create" { + description = "Determines whether to create EKS managed node group or not" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "platform" { + description = "Identifies if the OS platform is `bottlerocket` or `linux` based; `windows` is not supported" + type = string + default = "linux" +} + +################################################################################ +# User Data +################################################################################ + +variable "enable_bootstrap_user_data" { + description = "Determines whether the bootstrap configurations are populated within the user data template" + type = bool + default = false +} + +variable "cluster_name" { + description = "Name of associated EKS cluster" + type = string + default = null +} + +variable "cluster_endpoint" { + description = "Endpoint of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_auth_base64" { + description = "Base64 encoded CA of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_service_ipv4_cidr" { + description = "The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks" + type = string + default = null +} + +variable "pre_bootstrap_user_data" { + description = "User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "post_bootstrap_user_data" { + description = "User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "bootstrap_extra_args" { + description = "Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data" + type = string + default = "" +} + +variable "user_data_template_path" { + description = "Path to a local, custom user data template file to use when rendering user data" + type = string + default = "" +} + +################################################################################ +# Launch template +################################################################################ + +variable "create_launch_template" { + description = "Determines whether to create a launch template or not. If set to `false`, EKS will use its own default launch template" + type = bool + default = true +} + +variable "launch_template_name" { + description = "Launch template name - either to be created (`var.create_launch_template` = `true`) or existing (`var.create_launch_template` = `false`)" + type = string + default = "" +} + +variable "launch_template_use_name_prefix" { + description = "Determines whether to use `launch_template_name` as is or create a unique name beginning with the `launch_template_name` as the prefix" + type = bool + default = true +} + +variable "launch_template_description" { + description = "Description of the launch template" + type = string + default = null +} + +variable "ebs_optimized" { + description = "If true, the launched EC2 instance(s) will be EBS-optimized" + type = bool + default = null +} + +variable "ami_id" { + description = "The AMI from which to launch the instance. If not supplied, EKS will use its own default image" + type = string + default = "" +} + +variable "key_name" { + description = "The key name that should be used for the instance(s)" + type = string + default = null +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate" + type = list(string) + default = [] +} + +variable "cluster_primary_security_group_id" { + description = "The ID of the EKS cluster primary security group to associate with the instance(s). This is the security group that is automatically created by the EKS service" + type = string + default = null +} + +variable "launch_template_default_version" { + description = "Default version of the launch template" + type = string + default = null +} + +variable "update_launch_template_default_version" { + description = "Whether to update the launch templates default version on each update. Conflicts with `launch_template_default_version`" + type = bool + default = true +} + +variable "disable_api_termination" { + description = "If true, enables EC2 instance termination protection" + type = bool + default = null +} + +variable "kernel_id" { + description = "The kernel ID" + type = string + default = null +} + +variable "ram_disk_id" { + description = "The ID of the ram disk" + type = string + default = null +} + +variable "block_device_mappings" { + description = "Specify volumes to attach to the instance besides the volumes specified by the AMI" + type = any + default = {} +} + +variable "capacity_reservation_specification" { + description = "Targeting for EC2 capacity reservations" + type = any + default = {} +} + +variable "cpu_options" { + description = "The CPU options for the instance" + type = map(string) + default = {} +} + +variable "credit_specification" { + description = "Customize the credit specification of the instance" + type = map(string) + default = {} +} + +variable "elastic_gpu_specifications" { + description = "The elastic GPU to attach to the instance" + type = map(string) + default = {} +} + +variable "elastic_inference_accelerator" { + description = "Configuration block containing an Elastic Inference Accelerator to attach to the instance" + type = map(string) + default = {} +} + +variable "enclave_options" { + description = "Enable Nitro Enclaves on launched instances" + type = map(string) + default = {} +} + +variable "instance_market_options" { + description = "The market (purchasing) option for the instance" + type = any + default = {} +} + +variable "license_specifications" { + description = "A list of license specifications to associate with" + type = map(string) + default = {} +} + +variable "metadata_options" { + description = "Customize the metadata options for the instance" + type = map(string) + default = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + } +} + +variable "enable_monitoring" { + description = "Enables/disables detailed monitoring" + type = bool + default = true +} + +variable "network_interfaces" { + description = "Customize network interfaces to be attached at instance boot time" + type = list(any) + default = [] +} + +variable "placement" { + description = "The placement of the instance" + type = map(string) + default = {} +} + +variable "launch_template_tags" { + description = "A map of additional tags to add to the tag_specifications of launch template created" + type = map(string) + default = {} +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +variable "subnet_ids" { + description = "Identifiers of EC2 Subnets to associate with the EKS Node Group. These subnets must have the following resource tag: `kubernetes.io/cluster/CLUSTER_NAME`" + type = list(string) + default = null +} + +variable "min_size" { + description = "Minimum number of instances/nodes" + type = number + default = 0 +} + +variable "max_size" { + description = "Maximum number of instances/nodes" + type = number + default = 3 +} + +variable "desired_size" { + description = "Desired number of instances/nodes" + type = number + default = 1 +} + +variable "name" { + description = "Name of the EKS managed node group" + type = string + default = "" +} + +variable "use_name_prefix" { + description = "Determines whether to use `name` as is or create a unique name beginning with the `name` as the prefix" + type = bool + default = true +} + +variable "ami_type" { + description = "Type of Amazon Machine Image (AMI) associated with the EKS Node Group. Valid values are `AL2_x86_64`, `AL2_x86_64_GPU`, `AL2_ARM_64`, `CUSTOM`, `BOTTLEROCKET_ARM_64`, `BOTTLEROCKET_x86_64`" + type = string + default = null +} + +variable "ami_release_version" { + description = "AMI version of the EKS Node Group. Defaults to latest version for Kubernetes version" + type = string + default = null +} + +variable "capacity_type" { + description = "Type of capacity associated with the EKS Node Group. Valid values: `ON_DEMAND`, `SPOT`" + type = string + default = "ON_DEMAND" +} + +variable "disk_size" { + description = "Disk size in GiB for nodes. Defaults to `20`" + type = number + default = null +} + +variable "force_update_version" { + description = "Force version update if existing pods are unable to be drained due to a pod disruption budget issue" + type = bool + default = null +} + +variable "instance_types" { + description = "Set of instance types associated with the EKS Node Group. Defaults to `[\"t3.medium\"]`" + type = list(string) + default = null +} + +variable "labels" { + description = "Key-value map of Kubernetes labels. Only labels that are applied with the EKS API are managed by this argument. Other Kubernetes labels applied to the EKS Node Group will not be managed" + type = map(string) + default = null +} + +variable "cluster_version" { + description = "Kubernetes version. Defaults to EKS Cluster Kubernetes version" + type = string + default = null +} + +variable "launch_template_version" { + description = "Launch template version number. The default is `$Default`" + type = string + default = null +} + +variable "remote_access" { + description = "Configuration block with remote access settings" + type = any + default = {} +} + +variable "taints" { + description = "The Kubernetes taints to be applied to the nodes in the node group. Maximum of 50 taints per node group" + type = any + default = {} +} + +variable "update_config" { + description = "Configuration block of settings for max unavailable resources during node group updates" + type = map(string) + default = {} +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the node group" + type = map(string) + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines whether to create a security group" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description for the security group created" + type = string + default = "EKS managed node group security group" +} + +variable "vpc_id" { + description = "ID of the VPC where the security group/nodes will be provisioned" + type = string + default = null +} + +variable "security_group_rules" { + description = "List of security group rules to add to the security group created" + type = any + default = {} +} + +variable "cluster_security_group_id" { + description = "Cluster control plane security group ID" + type = string + default = null +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +################################################################################ +# IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether an IAM role is created or to use an existing IAM role" + type = bool + default = true +} + +variable "cluster_ip_family" { + description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`" + type = string + default = null +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN for the node group. Required if `create_iam_role` is set to `false`" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_attach_cni_policy" { + description = "Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster" + type = bool + default = true +} + +variable "iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = list(string) + default = [] +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} diff --git a/modules/aws-eks/modules/eks-managed-node-group/versions.tf b/modules/aws-eks/modules/eks-managed-node-group/versions.tf new file mode 100644 index 0000000..22e8d72 --- /dev/null +++ b/modules/aws-eks/modules/eks-managed-node-group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + } +} diff --git a/modules/aws-eks/modules/fargate-profile/README.md b/modules/aws-eks/modules/fargate-profile/README.md new file mode 100644 index 0000000..97dd0d3 --- /dev/null +++ b/modules/aws-eks/modules/fargate-profile/README.md @@ -0,0 +1,89 @@ +# EKS Fargate Profile Module + +Configuration in this directory creates a Fargate EKS Profile + +## Usage + +```hcl +module "fargate_profile" { + source = "terraform-aws-modules/eks/aws//modules/fargate-profile" + + name = "separate-fargate-profile" + cluster_name = "my-cluster" + + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + selectors = [{ + namespace = "kube-system" + }] + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_eks_fargate_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_fargate_profile) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_ip\_family](#input\_cluster\_ip\_family) | The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6` | `string` | `null` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the EKS cluster | `string` | `null` | no | +| [create](#input\_create) | Determines whether to create Fargate profile or not | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether an IAM role is created or to use an existing IAM role | `bool` | `true` | no | +| [iam\_role\_additional\_policies](#input\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `list(string)` | `[]` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN for the Fargate profile. Required if `create_iam_role` is set to `false` | `string` | `null` | no | +| [iam\_role\_attach\_cni\_policy](#input\_iam\_role\_attach\_cni\_policy) | Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster | `bool` | `true` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `""` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [name](#input\_name) | Name of the EKS Fargate Profile | `string` | `""` | no | +| [selectors](#input\_selectors) | Configuration block(s) for selecting Kubernetes Pods to execute with this Fargate Profile | `any` | `[]` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of subnet IDs for the EKS Fargate Profile | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Create and delete timeout configurations for the Fargate Profile | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [fargate\_profile\_arn](#output\_fargate\_profile\_arn) | Amazon Resource Name (ARN) of the EKS Fargate Profile | +| [fargate\_profile\_id](#output\_fargate\_profile\_id) | EKS Cluster name and EKS Fargate Profile name separated by a colon (`:`) | +| [fargate\_profile\_pod\_execution\_role\_arn](#output\_fargate\_profile\_pod\_execution\_role\_arn) | Amazon Resource Name (ARN) of the EKS Fargate Profile Pod execution role ARN | +| [fargate\_profile\_status](#output\_fargate\_profile\_status) | Status of the EKS Fargate Profile | +| [iam\_role\_arn](#output\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | + diff --git a/modules/aws-eks/modules/fargate-profile/main.tf b/modules/aws-eks/modules/fargate-profile/main.tf new file mode 100644 index 0000000..554b0e8 --- /dev/null +++ b/modules/aws-eks/modules/fargate-profile/main.tf @@ -0,0 +1,86 @@ +data "aws_partition" "current" {} + +data "aws_caller_identity" "current" {} + +locals { + iam_role_name = coalesce(var.iam_role_name, var.name, "fargate-profile") + + iam_role_policy_prefix = "arn:${data.aws_partition.current.partition}:iam::aws:policy" + + cni_policy = var.cluster_ip_family == "ipv6" ? "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:policy/AmazonEKS_CNI_IPv6_Policy" : "${local.iam_role_policy_prefix}/AmazonEKS_CNI_Policy" +} + +################################################################################ +# IAM Role +################################################################################ + +data "aws_iam_policy_document" "assume_role_policy" { + count = var.create && var.create_iam_role ? 1 : 0 + + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["eks-fargate-pods.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create && var.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = var.create && var.create_iam_role ? toset(compact(distinct(concat([ + "${local.iam_role_policy_prefix}/AmazonEKSFargatePodExecutionRolePolicy", + var.iam_role_attach_cni_policy ? local.cni_policy : "", + ], var.iam_role_additional_policies)))) : toset([]) + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +################################################################################ +# Fargate Profile +################################################################################ + +resource "aws_eks_fargate_profile" "this" { + count = var.create ? 1 : 0 + + cluster_name = var.cluster_name + fargate_profile_name = var.name + pod_execution_role_arn = var.create_iam_role ? aws_iam_role.this[0].arn : var.iam_role_arn + subnet_ids = var.subnet_ids + + dynamic "selector" { + for_each = var.selectors + + content { + namespace = selector.value.namespace + labels = lookup(selector.value, "labels", {}) + } + } + + dynamic "timeouts" { + for_each = [var.timeouts] + content { + create = lookup(var.timeouts, "create", null) + delete = lookup(var.timeouts, "delete", null) + } + } + + tags = var.tags +} diff --git a/modules/aws-eks/modules/fargate-profile/outputs.tf b/modules/aws-eks/modules/fargate-profile/outputs.tf new file mode 100644 index 0000000..c8b663e --- /dev/null +++ b/modules/aws-eks/modules/fargate-profile/outputs.tf @@ -0,0 +1,42 @@ +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, var.iam_role_arn) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profile_arn" { + description = "Amazon Resource Name (ARN) of the EKS Fargate Profile" + value = try(aws_eks_fargate_profile.this[0].arn, "") +} + +output "fargate_profile_id" { + description = "EKS Cluster name and EKS Fargate Profile name separated by a colon (`:`)" + value = try(aws_eks_fargate_profile.this[0].id, "") +} + +output "fargate_profile_status" { + description = "Status of the EKS Fargate Profile" + value = try(aws_eks_fargate_profile.this[0].status, "") +} + +output "fargate_profile_pod_execution_role_arn" { + description = "Amazon Resource Name (ARN) of the EKS Fargate Profile Pod execution role ARN" + value = try(aws_eks_fargate_profile.this[0].pod_execution_role_arn, "") +} diff --git a/modules/aws-eks/modules/fargate-profile/variables.tf b/modules/aws-eks/modules/fargate-profile/variables.tf new file mode 100644 index 0000000..4d5e95c --- /dev/null +++ b/modules/aws-eks/modules/fargate-profile/variables.tf @@ -0,0 +1,115 @@ +variable "create" { + description = "Determines whether to create Fargate profile or not" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether an IAM role is created or to use an existing IAM role" + type = bool + default = true +} + +variable "cluster_ip_family" { + description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`" + type = string + default = null +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN for the Fargate profile. Required if `create_iam_role` is set to `false`" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = "" +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_attach_cni_policy" { + description = "Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster" + type = bool + default = true +} + +variable "iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = list(string) + default = [] +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +################################################################################ +# Fargate Profile +################################################################################ + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = null +} + +variable "name" { + description = "Name of the EKS Fargate Profile" + type = string + default = "" +} + +variable "subnet_ids" { + description = "A list of subnet IDs for the EKS Fargate Profile" + type = list(string) + default = [] +} + +variable "selectors" { + description = "Configuration block(s) for selecting Kubernetes Pods to execute with this Fargate Profile" + type = any + default = [] +} + +variable "timeouts" { + description = "Create and delete timeout configurations for the Fargate Profile" + type = map(string) + default = {} +} diff --git a/modules/aws-eks/modules/fargate-profile/versions.tf b/modules/aws-eks/modules/fargate-profile/versions.tf new file mode 100644 index 0000000..22e8d72 --- /dev/null +++ b/modules/aws-eks/modules/fargate-profile/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + } +} diff --git a/modules/aws-eks/modules/karpenter/README.md b/modules/aws-eks/modules/karpenter/README.md new file mode 100644 index 0000000..8e9b6dc --- /dev/null +++ b/modules/aws-eks/modules/karpenter/README.md @@ -0,0 +1,200 @@ +# Karpenter Module + +Configuration in this directory creates the AWS resources required by Karpenter + +## Usage + +### All Resources (Default) + +In the following example, the Karpenter module will create: +- An IAM role for service accounts (IRSA) with a narrowly scoped IAM policy for the Karpenter controller to utilize +- An IAM role and instance profile for the nodes created by Karpenter to utilize + - Note: This IAM role ARN will need to be added to the `aws-auth` configmap for nodes to join the cluster successfully +- An SQS queue and Eventbridge event rules for Karpenter to utilize for spot termination handling, capacity rebalancing, etc. + +This setup is great for running Karpenter on EKS Fargate: + +```hcl +module "eks" { + source = "terraform-aws-modules/eks" + + # Shown just for connection between cluster and Karpenter sub-module below + manage_aws_auth_configmap = true + aws_auth_roles = [ + # We need to add in the Karpenter node IAM role for nodes launched by Karpenter + { + rolearn = module.karpenter.role_arn + username = "system:node:{{EC2PrivateDNSName}}" + groups = [ + "system:bootstrappers", + "system:nodes", + ] + }, + ] + ... +} + +module "karpenter" { + source = "terraform-aws-modules/eks/aws//modules/karpenter" + + cluster_name = module.eks.cluster_name + + irsa_oidc_provider_arn = module.eks.oidc_provider_arn + irsa_namespace_service_accounts = ["karpenter:karpenter"] + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### External Node IAM Role (Default) + +In the following example, the Karpenter module will create: +- An IAM role for service accounts (IRSA) with a narrowly scoped IAM policy for the Karpenter controller to utilize +- An IAM instance profile for the nodes created by Karpenter to utilize + - Note: This setup will utilize the existing IAM role created by the EKS Managed Node group which means the role is already populated in the `aws-auth` configmap and no further updates are required. +- An SQS queue and Eventbridge event rules for Karpenter to utilize for spot termination handling, capacity rebalancing, etc. + +In this scenario, Karpenter would run atop the EKS Managed Node group and scale out nodes as needed from there: + +```hcl +module "eks" { + source = "terraform-aws-modules/eks" + + # Shown just for connection between cluster and Karpenter sub-module below + eks_managed_node_groups = { + initial = { + instance_types = ["t3.medium"] + + min_size = 1 + max_size = 3 + desired_size = 1 + } + } + ... +} + +module "karpenter" { + source = "terraform-aws-modules/eks/aws//modules/karpenter" + + cluster_name = module.eks.cluster_name + + irsa_oidc_provider_arn = module.eks.oidc_provider_arn + irsa_namespace_service_accounts = ["karpenter:karpenter"] + + create_iam_role = false + iam_role_arn = module.eks.eks_managed_node_groups["initial"].iam_role_arn + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.47 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.47 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_event_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | +| [aws_cloudwatch_event_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | +| [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_policy.irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.irsa_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_sqs_queue.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.irsa_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_ip\_family](#input\_cluster\_ip\_family) | The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6` | `string` | `null` | no | +| [cluster\_name](#input\_cluster\_name) | The name of the EKS cluster | `string` | `""` | no | +| [create](#input\_create) | Determines whether to create EKS managed node group or not | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether an IAM role is created or to use an existing IAM role | `bool` | `true` | no | +| [create\_instance\_profile](#input\_create\_instance\_profile) | Whether to create an IAM instance profile | `bool` | `true` | no | +| [create\_irsa](#input\_create\_irsa) | Determines whether an IAM role for service accounts is created | `bool` | `true` | no | +| [enable\_spot\_termination](#input\_enable\_spot\_termination) | Determines whether to enable native spot termination handling | `bool` | `true` | no | +| [iam\_role\_additional\_policies](#input\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `list(string)` | `[]` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN for the IAM instance profile. Required if `create_iam_role` is set to `false` | `string` | `null` | no | +| [iam\_role\_attach\_cni\_policy](#input\_iam\_role\_attach\_cni\_policy) | Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster | `bool` | `true` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_max\_session\_duration](#input\_iam\_role\_max\_session\_duration) | Maximum API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `"/"` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [irsa\_assume\_role\_condition\_test](#input\_irsa\_assume\_role\_condition\_test) | Name of the [IAM condition operator](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html) to evaluate when assuming the role | `string` | `"StringEquals"` | no | +| [irsa\_description](#input\_irsa\_description) | IAM role for service accounts description | `string` | `"Karpenter IAM role for service account"` | no | +| [irsa\_max\_session\_duration](#input\_irsa\_max\_session\_duration) | Maximum API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [irsa\_name](#input\_irsa\_name) | Name of IAM role for service accounts | `string` | `null` | no | +| [irsa\_namespace\_service\_accounts](#input\_irsa\_namespace\_service\_accounts) | List of `namespace:serviceaccount`pairs to use in trust policy for IAM role for service accounts | `list(string)` |
[
"karpenter:karpenter"
]
| no | +| [irsa\_oidc\_provider\_arn](#input\_irsa\_oidc\_provider\_arn) | OIDC provider arn used in trust policy for IAM role for service accounts | `string` | `""` | no | +| [irsa\_path](#input\_irsa\_path) | Path of IAM role for service accounts | `string` | `"/"` | no | +| [irsa\_permissions\_boundary\_arn](#input\_irsa\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role for service accounts | `string` | `null` | no | +| [irsa\_policy\_name](#input\_irsa\_policy\_name) | Name of IAM policy for service accounts | `string` | `null` | no | +| [irsa\_ssm\_parameter\_arns](#input\_irsa\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | +| [irsa\_subnet\_account\_id](#input\_irsa\_subnet\_account\_id) | Account ID of where the subnets Karpenter will utilize resides. Used when subnets are shared from another account | `string` | `""` | no | +| [irsa\_tag\_key](#input\_irsa\_tag\_key) | Tag key (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner | `string` | `"karpenter.sh/discovery"` | no | +| [irsa\_tag\_values](#input\_irsa\_tag\_values) | Tag values (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner. Defaults to cluster name when not set. | `list(string)` | `[]` | no | +| [irsa\_tags](#input\_irsa\_tags) | A map of additional tags to add the the IAM role for service accounts | `map(any)` | `{}` | no | +| [irsa\_use\_name\_prefix](#input\_irsa\_use\_name\_prefix) | Determines whether the IAM role for service accounts name (`irsa_name`) is used as a prefix | `bool` | `true` | no | +| [policies](#input\_policies) | Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format | `map(string)` | `{}` | no | +| [queue\_kms\_data\_key\_reuse\_period\_seconds](#input\_queue\_kms\_data\_key\_reuse\_period\_seconds) | The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again | `number` | `null` | no | +| [queue\_kms\_master\_key\_id](#input\_queue\_kms\_master\_key\_id) | The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK | `string` | `null` | no | +| [queue\_managed\_sse\_enabled](#input\_queue\_managed\_sse\_enabled) | Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys | `bool` | `true` | no | +| [queue\_name](#input\_queue\_name) | Name of the SQS queue | `string` | `null` | no | +| [rule\_name\_prefix](#input\_rule\_name\_prefix) | Prefix used for all event bridge rules | `string` | `"Karpenter"` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [event\_rules](#output\_event\_rules) | Map of the event rules created and their attributes | +| [instance\_profile\_arn](#output\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [instance\_profile\_id](#output\_instance\_profile\_id) | Instance profile's ID | +| [instance\_profile\_name](#output\_instance\_profile\_name) | Name of the instance profile | +| [instance\_profile\_unique](#output\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [irsa\_arn](#output\_irsa\_arn) | The Amazon Resource Name (ARN) specifying the IAM role for service accounts | +| [irsa\_name](#output\_irsa\_name) | The name of the IAM role for service accounts | +| [irsa\_unique\_id](#output\_irsa\_unique\_id) | Stable and unique string identifying the IAM role for service accounts | +| [queue\_arn](#output\_queue\_arn) | The ARN of the SQS queue | +| [queue\_name](#output\_queue\_name) | The name of the created Amazon SQS queue | +| [queue\_url](#output\_queue\_url) | The URL for the created Amazon SQS queue | +| [role\_arn](#output\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [role\_name](#output\_role\_name) | The name of the IAM role | +| [role\_unique\_id](#output\_role\_unique\_id) | Stable and unique string identifying the IAM role | + diff --git a/modules/aws-eks/modules/karpenter/main.tf b/modules/aws-eks/modules/karpenter/main.tf new file mode 100644 index 0000000..592ac28 --- /dev/null +++ b/modules/aws-eks/modules/karpenter/main.tf @@ -0,0 +1,379 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition + dns_suffix = data.aws_partition.current.dns_suffix +} + +################################################################################ +# IAM Role for Service Account (IRSA) +# This is used by the Karpenter controller +################################################################################ + +locals { + create_irsa = var.create && var.create_irsa + irsa_name = coalesce(var.irsa_name, "KarpenterIRSA-${var.cluster_name}") + irsa_policy_name = coalesce(var.irsa_policy_name, local.irsa_name) + + irsa_oidc_provider_url = replace(var.irsa_oidc_provider_arn, "/^(.*provider/)/", "") +} + +data "aws_iam_policy_document" "irsa_assume_role" { + count = local.create_irsa ? 1 : 0 + + statement { + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = [var.irsa_oidc_provider_arn] + } + + condition { + test = var.irsa_assume_role_condition_test + variable = "${local.irsa_oidc_provider_url}:sub" + values = [for sa in var.irsa_namespace_service_accounts : "system:serviceaccount:${sa}"] + } + + # https://aws.amazon.com/premiumsupport/knowledge-center/eks-troubleshoot-oidc-and-irsa/?nc1=h_ls + condition { + test = var.irsa_assume_role_condition_test + variable = "${local.irsa_oidc_provider_url}:aud" + values = ["sts.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "irsa" { + count = local.create_irsa ? 1 : 0 + + name = var.irsa_use_name_prefix ? null : local.irsa_name + name_prefix = var.irsa_use_name_prefix ? "${local.irsa_name}-" : null + path = var.irsa_path + description = var.irsa_description + + assume_role_policy = data.aws_iam_policy_document.irsa_assume_role[0].json + max_session_duration = var.irsa_max_session_duration + permissions_boundary = var.irsa_permissions_boundary_arn + force_detach_policies = true + + tags = merge(var.tags, var.irsa_tags) +} + +locals { + irsa_tag_values = coalescelist(var.irsa_tag_values, [var.cluster_name]) +} + +data "aws_iam_policy_document" "irsa" { + count = local.create_irsa ? 1 : 0 + + statement { + actions = [ + "ec2:CreateLaunchTemplate", + "ec2:CreateFleet", + "ec2:CreateTags", + "ec2:DescribeLaunchTemplates", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSpotPriceHistory", + "pricing:GetProducts", + ] + + resources = ["*"] + } + + statement { + actions = [ + "ec2:TerminateInstances", + "ec2:DeleteLaunchTemplate", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/${var.irsa_tag_key}" + values = local.irsa_tag_values + } + } + + statement { + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:*:${local.account_id}:launch-template/*", + ] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/${var.irsa_tag_key}" + values = local.irsa_tag_values + } + } + + statement { + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:*::image/*", + "arn:${local.partition}:ec2:*::snapshot/*", + "arn:${local.partition}:ec2:*:${local.account_id}:instance/*", + "arn:${local.partition}:ec2:*:${local.account_id}:spot-instances-request/*", + "arn:${local.partition}:ec2:*:${local.account_id}:security-group/*", + "arn:${local.partition}:ec2:*:${local.account_id}:volume/*", + "arn:${local.partition}:ec2:*:${local.account_id}:network-interface/*", + "arn:${local.partition}:ec2:*:${coalesce(var.irsa_subnet_account_id, local.account_id)}:subnet/*", + ] + } + + statement { + actions = ["ssm:GetParameter"] + resources = var.irsa_ssm_parameter_arns + } + + statement { + actions = ["eks:DescribeCluster"] + resources = ["arn:${local.partition}:eks:*:${local.account_id}:cluster/${var.cluster_name}"] + } + + statement { + actions = ["iam:PassRole"] + resources = [var.create_iam_role ? aws_iam_role.this[0].arn : var.iam_role_arn] + } + + dynamic "statement" { + for_each = local.enable_spot_termination ? [1] : [] + + content { + actions = [ + "sqs:DeleteMessage", + "sqs:GetQueueUrl", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage", + ] + resources = [aws_sqs_queue.this[0].arn] + } + } +} + +resource "aws_iam_policy" "irsa" { + count = local.create_irsa ? 1 : 0 + + name_prefix = "${local.irsa_policy_name}-" + path = var.irsa_path + description = var.irsa_description + policy = data.aws_iam_policy_document.irsa[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "irsa" { + count = local.create_irsa ? 1 : 0 + + role = aws_iam_role.irsa[0].name + policy_arn = aws_iam_policy.irsa[0].arn +} + +resource "aws_iam_role_policy_attachment" "irsa_additional" { + for_each = { for k, v in var.policies : k => v if local.create_irsa } + + role = aws_iam_role.irsa[0].name + policy_arn = each.value +} + +################################################################################ +# Node Termination Queue +################################################################################ + +locals { + enable_spot_termination = var.create && var.enable_spot_termination + + queue_name = coalesce(var.queue_name, "Karpenter-${var.cluster_name}") +} + +resource "aws_sqs_queue" "this" { + count = local.enable_spot_termination ? 1 : 0 + + name = local.queue_name + message_retention_seconds = 300 + sqs_managed_sse_enabled = var.queue_managed_sse_enabled ? var.queue_managed_sse_enabled : null + kms_master_key_id = var.queue_kms_master_key_id + kms_data_key_reuse_period_seconds = var.queue_kms_data_key_reuse_period_seconds + + tags = var.tags +} + +data "aws_iam_policy_document" "queue" { + count = local.enable_spot_termination ? 1 : 0 + + statement { + sid = "SqsWrite" + actions = ["sqs:SendMessage"] + resources = [aws_sqs_queue.this[0].arn] + + principals { + type = "Service" + identifiers = [ + "events.${local.dns_suffix}", + "sqs.${local.dns_suffix}", + ] + } + + } +} + +resource "aws_sqs_queue_policy" "this" { + count = local.enable_spot_termination ? 1 : 0 + + queue_url = aws_sqs_queue.this[0].url + policy = data.aws_iam_policy_document.queue[0].json +} + +################################################################################ +# Node Termination Event Rules +################################################################################ + +locals { + events = { + health_event = { + name = "HealthEvent" + description = "Karpenter interrupt - AWS health event" + event_pattern = { + source = ["aws.health"] + detail-type = ["AWS Health Event"] + } + } + spot_interupt = { + name = "SpotInterrupt" + description = "Karpenter interrupt - EC2 spot instance interruption warning" + event_pattern = { + source = ["aws.ec2"] + detail-type = ["EC2 Spot Instance Interruption Warning"] + } + } + instance_rebalance = { + name = "InstanceRebalance" + description = "Karpenter interrupt - EC2 instance rebalance recommendation" + event_pattern = { + source = ["aws.ec2"] + detail-type = ["EC2 Instance Rebalance Recommendation"] + } + } + instance_state_change = { + name = "InstanceStateChange" + description = "Karpenter interrupt - EC2 instance state-change notification" + event_pattern = { + source = ["aws.ec2"] + detail-type = ["EC2 Instance State-change Notification"] + } + } + } +} + +resource "aws_cloudwatch_event_rule" "this" { + for_each = { for k, v in local.events : k => v if local.enable_spot_termination } + + description = each.value.description + event_pattern = jsonencode(each.value.event_pattern) + + tags = merge( + { "ClusterName" : var.cluster_name }, + var.tags, + ) +} + +resource "aws_cloudwatch_event_target" "this" { + for_each = { for k, v in local.events : k => v if local.enable_spot_termination } + + rule = aws_cloudwatch_event_rule.this[each.key].name + target_id = "KarpenterInterruptionQueueTarget" + arn = aws_sqs_queue.this[0].arn +} + +################################################################################ +# Node IAM Role +# This is used by the nodes launched by Karpenter +################################################################################ + +locals { + create_iam_role = var.create && var.create_iam_role + + iam_role_name = coalesce(var.iam_role_name, "Karpenter-${var.cluster_name}") + iam_role_policy_prefix = "arn:${local.partition}:iam::aws:policy" + cni_policy = var.cluster_ip_family == "ipv6" ? "${local.iam_role_policy_prefix}/AmazonEKS_CNI_IPv6_Policy" : "${local.iam_role_policy_prefix}/AmazonEKS_CNI_Policy" +} + +data "aws_iam_policy_document" "assume_role" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "EKSNodeAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role[0].json + max_session_duration = var.iam_role_max_session_duration + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +# Policies attached ref https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in toset(compact([ + "${local.iam_role_policy_prefix}/AmazonEKSWorkerNodePolicy", + "${local.iam_role_policy_prefix}/AmazonEC2ContainerRegistryReadOnly", + var.iam_role_attach_cni_policy ? local.cni_policy : "", + ])) : k => v if local.create_iam_role } + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +resource "aws_iam_role_policy_attachment" "additional" { + for_each = { for k, v in var.iam_role_additional_policies : k => v if local.create_iam_role } + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +################################################################################ +# Node IAM Instance Profile +# This is used by the nodes launched by Karpenter +################################################################################ + +locals { + external_role_name = try(replace(var.iam_role_arn, "/^(.*role/)/", ""), null) +} + +resource "aws_iam_instance_profile" "this" { + count = var.create && var.create_instance_profile ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + role = var.create_iam_role ? aws_iam_role.this[0].name : local.external_role_name + + tags = merge(var.tags, var.iam_role_tags) +} diff --git a/modules/aws-eks/modules/karpenter/outputs.tf b/modules/aws-eks/modules/karpenter/outputs.tf new file mode 100644 index 0000000..947de39 --- /dev/null +++ b/modules/aws-eks/modules/karpenter/outputs.tf @@ -0,0 +1,89 @@ +################################################################################ +# IAM Role for Service Account (IRSA) +################################################################################ + +output "irsa_name" { + description = "The name of the IAM role for service accounts" + value = try(aws_iam_role.irsa[0].name, null) +} + +output "irsa_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role for service accounts" + value = try(aws_iam_role.irsa[0].arn, null) +} + +output "irsa_unique_id" { + description = "Stable and unique string identifying the IAM role for service accounts" + value = try(aws_iam_role.irsa[0].unique_id, null) +} + +################################################################################ +# Node Termination Queue +################################################################################ + +output "queue_arn" { + description = "The ARN of the SQS queue" + value = try(aws_sqs_queue.this[0].arn, null) +} + +output "queue_name" { + description = "The name of the created Amazon SQS queue" + value = try(aws_sqs_queue.this[0].name, null) +} + +output "queue_url" { + description = "The URL for the created Amazon SQS queue" + value = try(aws_sqs_queue.this[0].url, null) +} + +################################################################################ +# Node Termination Event Rules +################################################################################ + +output "event_rules" { + description = "Map of the event rules created and their attributes" + value = aws_cloudwatch_event_rule.this +} + +################################################################################ +# Node IAM Role +################################################################################ + +output "role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, null) +} + +output "role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, var.iam_role_arn) +} + +output "role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, null) +} + +################################################################################ +# Node IAM Instance Profile +################################################################################ + +output "instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = try(aws_iam_instance_profile.this[0].arn, null) +} + +output "instance_profile_id" { + description = "Instance profile's ID" + value = try(aws_iam_instance_profile.this[0].id, null) +} + +output "instance_profile_name" { + description = "Name of the instance profile" + value = try(aws_iam_instance_profile.this[0].name, null) +} + +output "instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = try(aws_iam_instance_profile.this[0].unique_id, null) +} diff --git a/modules/aws-eks/modules/karpenter/provider.tf b/modules/aws-eks/modules/karpenter/provider.tf new file mode 100644 index 0000000..e5657de --- /dev/null +++ b/modules/aws-eks/modules/karpenter/provider.tf @@ -0,0 +1,6 @@ +provider "aws" { + assume_role { + role_arn = var.provider_assume_role_arn + } + region = var.provider_region +} diff --git a/modules/aws-eks/modules/karpenter/variables.tf b/modules/aws-eks/modules/karpenter/variables.tf new file mode 100644 index 0000000..9e6ed77 --- /dev/null +++ b/modules/aws-eks/modules/karpenter/variables.tf @@ -0,0 +1,271 @@ +variable "create" { + description = "Creates the resources under this module" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "cluster_name" { + description = "The name of the EKS cluster" + type = string + default = "" +} + +################################################################################ +# IAM Role for Service Account (IRSA) +################################################################################ + +variable "create_irsa" { + description = "Determines whether an IAM role for service accounts is created" + type = bool + default = true +} + +variable "irsa_name" { + description = "Name of IAM role for service accounts" + type = string + default = null +} + +variable "irsa_policy_name" { + description = "Name of IAM policy for service accounts" + type = string + default = null +} + +variable "irsa_use_name_prefix" { + description = "Determines whether the IAM role for service accounts name (`irsa_name`) is used as a prefix" + type = bool + default = true +} + +variable "irsa_path" { + description = "Path of IAM role for service accounts" + type = string + default = "/" +} + +variable "irsa_description" { + description = "IAM role for service accounts description" + type = string + default = "Karpenter IAM role for service account" +} + +variable "irsa_max_session_duration" { + description = "Maximum API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +variable "irsa_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role for service accounts" + type = string + default = null +} + +variable "irsa_tags" { + description = "A map of additional tags to add the the IAM role for service accounts" + type = map(any) + default = {} +} + +variable "policies" { + description = "Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format" + type = map(string) + default = {} +} + +variable "irsa_tag_key" { + description = "Tag key (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner" + type = string + default = "karpenter.sh/discovery" +} + +variable "irsa_tag_values" { + description = "Tag values (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner. Defaults to cluster name when not set." + type = list(string) + default = [] +} + +variable "irsa_ssm_parameter_arns" { + description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" + type = list(string) + # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 + default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] +} + +variable "irsa_subnet_account_id" { + description = "Account ID of where the subnets Karpenter will utilize resides. Used when subnets are shared from another account" + type = string + default = "" +} + +variable "irsa_oidc_provider_arn" { + description = "OIDC provider arn used in trust policy for IAM role for service accounts" + type = string + default = "" +} + +variable "irsa_namespace_service_accounts" { + description = "List of `namespace:serviceaccount`pairs to use in trust policy for IAM role for service accounts" + type = list(string) + default = ["karpenter:karpenter"] +} + +variable "irsa_assume_role_condition_test" { + description = "Name of the [IAM condition operator](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html) to evaluate when assuming the role" + type = string + default = "StringEquals" +} + +################################################################################ +# Node Termination Queue +################################################################################ + +variable "enable_spot_termination" { + description = "Determines whether to enable native spot termination handling" + type = bool + default = true +} + +variable "queue_name" { + description = "Name of the SQS queue" + type = string + default = null +} + +variable "queue_managed_sse_enabled" { + description = "Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys" + type = bool + default = true +} + +variable "queue_kms_master_key_id" { + description = "The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK" + type = string + default = null +} + +variable "queue_kms_data_key_reuse_period_seconds" { + description = "The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again" + type = number + default = null +} + +################################################################################ +# Node IAM Role & Instance Profile +################################################################################ + +variable "create_iam_role" { + description = "Determines whether an IAM role is created or to use an existing IAM role" + type = bool + default = true +} + +variable "cluster_ip_family" { + description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`" + type = string + default = null +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN for the IAM instance profile. Required if `create_iam_role` is set to `false`" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = "/" +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_max_session_duration" { + description = "Maximum API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_attach_cni_policy" { + description = "Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster" + type = bool + default = true +} + +variable "iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = list(string) + default = [] +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +################################################################################ +# Node IAM Instance Profile +################################################################################ + +variable "create_instance_profile" { + description = "Whether to create an IAM instance profile" + type = bool + default = true +} + +################################################################################ +# Event Bridge Rules +################################################################################ + +variable "rule_name_prefix" { + description = "Prefix used for all event bridge rules" + type = string + default = "Karpenter" +} + + +################################################################################ +# Provider configuration +################################################################################ + +variable "provider_region" { + description = "Region to provision resources" + type = string + default = "eu-west-1" +} + +variable "provider_assume_role_arn" { + description = "Provider to assume role for account access" + type = string + default = "" +} \ No newline at end of file diff --git a/modules/aws-eks/modules/karpenter/versions.tf b/modules/aws-eks/modules/karpenter/versions.tf new file mode 100644 index 0000000..55eff62 --- /dev/null +++ b/modules/aws-eks/modules/karpenter/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.47" + } + } +} diff --git a/modules/aws-eks/modules/self-managed-node-group/README.md b/modules/aws-eks/modules/self-managed-node-group/README.md new file mode 100644 index 0000000..eb0b484 --- /dev/null +++ b/modules/aws-eks/modules/self-managed-node-group/README.md @@ -0,0 +1,208 @@ +# Self Managed Node Group Module + +Configuration in this directory creates a Self Managed Node Group (AutoScaling Group) along with an IAM role, security group, and launch template + +## Usage + +```hcl +module "self_managed_node_group" { + source = "terraform-aws-modules/eks/aws//modules/self-managed-node-group" + + name = "separate-self-mng" + cluster_name = "my-cluster" + cluster_version = "1.22" + cluster_endpoint = "https://012345678903AB2BAE5D1E0BFE0E2B50.gr7.us-east-1.eks.amazonaws.com" + cluster_auth_base64 = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKbXFqQ1VqNGdGR2w3ZW5PeWthWnZ2RjROOTVOUEZCM2o0cGhVZUsrWGFtN2ZSQnZya0d6OGxKZmZEZWF2b2plTwpQK2xOZFlqdHZncmxCUEpYdHZIZmFzTzYxVzdIZmdWQ2EvamdRM2w3RmkvL1dpQmxFOG9oWUZkdWpjc0s1SXM2CnNkbk5KTTNYUWN2TysrSitkV09NT2ZlNzlsSWdncmdQLzgvRU9CYkw3eUY1aU1hS3lsb1RHL1V3TlhPUWt3ZUcKblBNcjdiUmdkQ1NCZTlXYXowOGdGRmlxV2FOditsTDhsODBTdFZLcWVNVlUxbjQyejVwOVpQRTd4T2l6L0xTNQpYV2lXWkVkT3pMN0xBWGVCS2gzdkhnczFxMkI2d1BKZnZnS1NzWllQRGFpZTloT1NNOUJkNFNPY3JrZTRYSVBOCkVvcXVhMlYrUDRlTWJEQzhMUkVWRDdCdVZDdWdMTldWOTBoL3VJUy9WU2VOcEdUOGVScE5DakszSjc2aFlsWm8KWjNGRG5QWUY0MWpWTHhiOXF0U1ROdEp6amYwWXBEYnFWci9xZzNmQWlxbVorMzd3YWM1eHlqMDZ4cmlaRUgzZgpUM002d2lCUEVHYVlGeWN5TmNYTk5aYW9DWDJVL0N1d2JsUHAKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==" + + vpc_id = "vpc-1234556abcdef" + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + vpc_security_group_ids = [ + # cluster_security_group_id, + ] + + min_size = 1 + max_size = 10 + desired_size = 1 + + launch_template_name = "separate-self-mng" + instance_type = "m5.large" + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [user\_data](#module\_user\_data) | ../_user_data | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | +| [aws_autoscaling_schedule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | +| [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_launch_template.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_ami.eks_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_default_tags.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/default_tags) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ami\_id](#input\_ami\_id) | The AMI from which to launch the instance | `string` | `""` | no | +| [autoscaling\_group\_tags](#input\_autoscaling\_group\_tags) | A map of additional tags to add to the autoscaling group created. Tags are applied to the autoscaling group only and are NOT propagated to instances | `map(string)` | `{}` | no | +| [availability\_zones](#input\_availability\_zones) | A list of one or more availability zones for the group. Used for EC2-Classic and default subnets when not specified with `subnet_ids` argument. Conflicts with `subnet_ids` | `list(string)` | `null` | no | +| [block\_device\_mappings](#input\_block\_device\_mappings) | Specify volumes to attach to the instance besides the volumes specified by the AMI | `any` | `{}` | no | +| [bootstrap\_extra\_args](#input\_bootstrap\_extra\_args) | Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data | `string` | `""` | no | +| [capacity\_rebalance](#input\_capacity\_rebalance) | Indicates whether capacity rebalance is enabled | `bool` | `null` | no | +| [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Targeting for EC2 capacity reservations | `any` | `{}` | no | +| [cluster\_auth\_base64](#input\_cluster\_auth\_base64) | Base64 encoded CA of associated EKS cluster | `string` | `""` | no | +| [cluster\_endpoint](#input\_cluster\_endpoint) | Endpoint of associated EKS cluster | `string` | `""` | no | +| [cluster\_ip\_family](#input\_cluster\_ip\_family) | The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6` | `string` | `null` | no | +| [cluster\_name](#input\_cluster\_name) | Name of associated EKS cluster | `string` | `""` | no | +| [cluster\_primary\_security\_group\_id](#input\_cluster\_primary\_security\_group\_id) | The ID of the EKS cluster primary security group to associate with the instance(s). This is the security group that is automatically created by the EKS service | `string` | `null` | no | +| [cluster\_security\_group\_id](#input\_cluster\_security\_group\_id) | Cluster control plane security group ID | `string` | `null` | no | +| [cluster\_version](#input\_cluster\_version) | Kubernetes cluster version - used to lookup default AMI ID if one is not provided | `string` | `null` | no | +| [cpu\_options](#input\_cpu\_options) | The CPU options for the instance | `map(string)` | `{}` | no | +| [create](#input\_create) | Determines whether to create self managed node group or not | `bool` | `true` | no | +| [create\_autoscaling\_group](#input\_create\_autoscaling\_group) | Determines whether to create autoscaling group or not | `bool` | `true` | no | +| [create\_iam\_instance\_profile](#input\_create\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `true` | no | +| [create\_launch\_template](#input\_create\_launch\_template) | Determines whether to create launch template or not | `bool` | `true` | no | +| [create\_schedule](#input\_create\_schedule) | Determines whether to create autoscaling group schedule or not | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines whether to create a security group | `bool` | `true` | no | +| [credit\_specification](#input\_credit\_specification) | Customize the credit specification of the instance | `map(string)` | `{}` | no | +| [default\_cooldown](#input\_default\_cooldown) | The amount of time, in seconds, after a scaling activity completes before another scaling activity can start | `number` | `null` | no | +| [delete\_timeout](#input\_delete\_timeout) | Delete timeout to wait for destroying autoscaling group | `string` | `null` | no | +| [desired\_size](#input\_desired\_size) | The number of Amazon EC2 instances that should be running in the autoscaling group | `number` | `1` | no | +| [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 instance termination protection | `bool` | `null` | no | +| [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | +| [elastic\_gpu\_specifications](#input\_elastic\_gpu\_specifications) | The elastic GPU to attach to the instance | `map(string)` | `{}` | no | +| [elastic\_inference\_accelerator](#input\_elastic\_inference\_accelerator) | Configuration block containing an Elastic Inference Accelerator to attach to the instance | `map(string)` | `{}` | no | +| [enable\_monitoring](#input\_enable\_monitoring) | Enables/disables detailed monitoring | `bool` | `true` | no | +| [enabled\_metrics](#input\_enabled\_metrics) | A list of metrics to collect. The allowed values are `GroupDesiredCapacity`, `GroupInServiceCapacity`, `GroupPendingCapacity`, `GroupMinSize`, `GroupMaxSize`, `GroupInServiceInstances`, `GroupPendingInstances`, `GroupStandbyInstances`, `GroupStandbyCapacity`, `GroupTerminatingCapacity`, `GroupTerminatingInstances`, `GroupTotalCapacity`, `GroupTotalInstances` | `list(string)` | `[]` | no | +| [enclave\_options](#input\_enclave\_options) | Enable Nitro Enclaves on launched instances | `map(string)` | `{}` | no | +| [force\_delete](#input\_force\_delete) | Allows deleting the Auto Scaling Group without waiting for all instances in the pool to terminate. You can force an Auto Scaling Group to delete even if it's in the process of scaling a resource. Normally, Terraform drains all the instances before deleting the group. This bypasses that behavior and potentially leaves resources dangling | `bool` | `null` | no | +| [health\_check\_grace\_period](#input\_health\_check\_grace\_period) | Time (in seconds) after instance comes into service before checking health | `number` | `null` | no | +| [health\_check\_type](#input\_health\_check\_type) | `EC2` or `ELB`. Controls how health checking is done | `string` | `null` | no | +| [hibernation\_options](#input\_hibernation\_options) | The hibernation options for the instance | `map(string)` | `{}` | no | +| [iam\_instance\_profile\_arn](#input\_iam\_instance\_profile\_arn) | Amazon Resource Name (ARN) of an existing IAM instance profile that provides permissions for the node group. Required if `create_iam_instance_profile` = `false` | `string` | `null` | no | +| [iam\_role\_additional\_policies](#input\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `list(string)` | `[]` | no | +| [iam\_role\_attach\_cni\_policy](#input\_iam\_role\_attach\_cni\_policy) | Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster | `bool` | `true` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether cluster IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [initial\_lifecycle\_hooks](#input\_initial\_lifecycle\_hooks) | One or more Lifecycle Hooks to attach to the Auto Scaling Group before instances are launched. The syntax is exactly the same as the separate `aws_autoscaling_lifecycle_hook` resource, without the `autoscaling_group_name` attribute. Please note that this will only work when creating a new Auto Scaling Group. For all other use-cases, please use `aws_autoscaling_lifecycle_hook` resource | `list(map(string))` | `[]` | no | +| [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Shutdown behavior for the instance. Can be `stop` or `terminate`. (Default: `stop`) | `string` | `null` | no | +| [instance\_market\_options](#input\_instance\_market\_options) | The market (purchasing) option for the instance | `any` | `{}` | no | +| [instance\_refresh](#input\_instance\_refresh) | If this block is configured, start an Instance Refresh when this Auto Scaling Group is updated | `any` | `{}` | no | +| [instance\_type](#input\_instance\_type) | The type of the instance to launch | `string` | `""` | no | +| [kernel\_id](#input\_kernel\_id) | The kernel ID | `string` | `null` | no | +| [key\_name](#input\_key\_name) | The key name that should be used for the instance | `string` | `null` | no | +| [launch\_template\_default\_version](#input\_launch\_template\_default\_version) | Default Version of the launch template | `string` | `null` | no | +| [launch\_template\_description](#input\_launch\_template\_description) | Description of the launch template | `string` | `null` | no | +| [launch\_template\_name](#input\_launch\_template\_name) | Launch template name - either to be created (`var.create_launch_template` = `true`) or existing (`var.create_launch_template` = `false`) | `string` | `null` | no | +| [launch\_template\_tags](#input\_launch\_template\_tags) | A map of additional tags to add to the tag\_specifications of launch template created | `map(string)` | `{}` | no | +| [launch\_template\_use\_name\_prefix](#input\_launch\_template\_use\_name\_prefix) | Determines whether to use `launch_template_name` as is or create a unique name beginning with the `launch_template_name` as the prefix | `bool` | `true` | no | +| [launch\_template\_version](#input\_launch\_template\_version) | Launch template version. Can be version number, `$Latest`, or `$Default` | `string` | `null` | no | +| [license\_specifications](#input\_license\_specifications) | A list of license specifications to associate with | `map(string)` | `{}` | no | +| [max\_instance\_lifetime](#input\_max\_instance\_lifetime) | The maximum amount of time, in seconds, that an instance can be in service, values must be either equal to 0 or between 604800 and 31536000 seconds | `number` | `null` | no | +| [max\_size](#input\_max\_size) | The maximum size of the autoscaling group | `number` | `3` | no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options for the instance | `map(string)` |
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 2,
"http_tokens": "required"
}
| no | +| [metrics\_granularity](#input\_metrics\_granularity) | The granularity to associate with the metrics to collect. The only valid value is `1Minute` | `string` | `null` | no | +| [min\_elb\_capacity](#input\_min\_elb\_capacity) | Setting this causes Terraform to wait for this number of instances to show up healthy in the ELB only on creation. Updates will not wait on ELB instance number changes | `number` | `null` | no | +| [min\_size](#input\_min\_size) | The minimum size of the autoscaling group | `number` | `0` | no | +| [mixed\_instances\_policy](#input\_mixed\_instances\_policy) | Configuration block containing settings to define launch targets for Auto Scaling groups | `any` | `null` | no | +| [name](#input\_name) | Name of the Self managed Node Group | `string` | `""` | no | +| [network\_interfaces](#input\_network\_interfaces) | Customize network interfaces to be attached at instance boot time | `list(any)` | `[]` | no | +| [placement](#input\_placement) | The placement of the instance | `map(string)` | `{}` | no | +| [placement\_group](#input\_placement\_group) | The name of the placement group into which you'll launch your instances, if any | `string` | `null` | no | +| [platform](#input\_platform) | Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based | `string` | `"linux"` | no | +| [post\_bootstrap\_user\_data](#input\_post\_bootstrap\_user\_data) | User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [pre\_bootstrap\_user\_data](#input\_pre\_bootstrap\_user\_data) | User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket` | `string` | `""` | no | +| [protect\_from\_scale\_in](#input\_protect\_from\_scale\_in) | Allows setting instance protection. The autoscaling group will not select instances with this setting for termination during scale in events. | `bool` | `false` | no | +| [ram\_disk\_id](#input\_ram\_disk\_id) | The ID of the ram disk | `string` | `null` | no | +| [schedules](#input\_schedules) | Map of autoscaling group schedule to create | `map(any)` | `{}` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description for the security group created | `string` | `"EKS self-managed node group security group"` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | List of security group rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [service\_linked\_role\_arn](#input\_service\_linked\_role\_arn) | The ARN of the service-linked role that the ASG will use to call other AWS services | `string` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of subnet IDs to launch resources in. Subnets automatically determine which availability zones the group will reside. Conflicts with `availability_zones` | `list(string)` | `null` | no | +| [suspended\_processes](#input\_suspended\_processes) | A list of processes to suspend for the Auto Scaling Group. The allowed values are `Launch`, `Terminate`, `HealthCheck`, `ReplaceUnhealthy`, `AZRebalance`, `AlarmNotification`, `ScheduledActions`, `AddToLoadBalancer`. Note that if you suspend either the `Launch` or `Terminate` process types, it can prevent your Auto Scaling Group from functioning properly | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [target\_group\_arns](#input\_target\_group\_arns) | A set of `aws_alb_target_group` ARNs, for use with Application or Network Load Balancing | `list(string)` | `[]` | no | +| [termination\_policies](#input\_termination\_policies) | A list of policies to decide how the instances in the Auto Scaling Group should be terminated. The allowed values are `OldestInstance`, `NewestInstance`, `OldestLaunchConfiguration`, `ClosestToNextInstanceHour`, `OldestLaunchTemplate`, `AllocationStrategy`, `Default` | `list(string)` | `[]` | no | +| [update\_launch\_template\_default\_version](#input\_update\_launch\_template\_default\_version) | Whether to update Default Version each update. Conflicts with `launch_template_default_version` | `bool` | `true` | no | +| [use\_default\_tags](#input\_use\_default\_tags) | Enables/disables the use of provider default tags in the tag\_specifications of the Auto Scaling group | `bool` | `false` | no | +| [use\_mixed\_instances\_policy](#input\_use\_mixed\_instances\_policy) | Determines whether to use a mixed instances policy in the autoscaling group or not | `bool` | `false` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether to use `name` as is or create a unique name beginning with the `name` as the prefix | `bool` | `true` | no | +| [user\_data\_template\_path](#input\_user\_data\_template\_path) | Path to a local, custom user data template file to use when rendering user data | `string` | `""` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where the security group/nodes will be provisioned | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | A list of security group IDs to associate | `list(string)` | `[]` | no | +| [wait\_for\_capacity\_timeout](#input\_wait\_for\_capacity\_timeout) | A maximum duration that Terraform should wait for ASG instances to be healthy before timing out. (See also Waiting for Capacity below.) Setting this to '0' causes Terraform to skip all Capacity Waiting behavior. | `string` | `null` | no | +| [wait\_for\_elb\_capacity](#input\_wait\_for\_elb\_capacity) | Setting this will cause Terraform to wait for exactly this number of healthy instances in all attached load balancers on both create and update operations. Takes precedence over `min_elb_capacity` behavior. | `number` | `null` | no | +| [warm\_pool](#input\_warm\_pool) | If this block is configured, add a Warm Pool to the specified Auto Scaling group | `any` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_group\_arn](#output\_autoscaling\_group\_arn) | The ARN for this autoscaling group | +| [autoscaling\_group\_availability\_zones](#output\_autoscaling\_group\_availability\_zones) | The availability zones of the autoscaling group | +| [autoscaling\_group\_default\_cooldown](#output\_autoscaling\_group\_default\_cooldown) | Time between a scaling activity and the succeeding scaling activity | +| [autoscaling\_group\_desired\_capacity](#output\_autoscaling\_group\_desired\_capacity) | The number of Amazon EC2 instances that should be running in the group | +| [autoscaling\_group\_health\_check\_grace\_period](#output\_autoscaling\_group\_health\_check\_grace\_period) | Time after instance comes into service before checking health | +| [autoscaling\_group\_health\_check\_type](#output\_autoscaling\_group\_health\_check\_type) | EC2 or ELB. Controls how health checking is done | +| [autoscaling\_group\_id](#output\_autoscaling\_group\_id) | The autoscaling group id | +| [autoscaling\_group\_max\_size](#output\_autoscaling\_group\_max\_size) | The maximum size of the autoscaling group | +| [autoscaling\_group\_min\_size](#output\_autoscaling\_group\_min\_size) | The minimum size of the autoscaling group | +| [autoscaling\_group\_name](#output\_autoscaling\_group\_name) | The autoscaling group name | +| [autoscaling\_group\_schedule\_arns](#output\_autoscaling\_group\_schedule\_arns) | ARNs of autoscaling group schedules | +| [autoscaling\_group\_vpc\_zone\_identifier](#output\_autoscaling\_group\_vpc\_zone\_identifier) | The VPC zone identifier | +| [iam\_instance\_profile\_arn](#output\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | Instance profile's ID | +| [iam\_instance\_profile\_unique](#output\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [iam\_role\_arn](#output\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [image\_id](#output\_image\_id) | ID of the image | +| [launch\_template\_arn](#output\_launch\_template\_arn) | The ARN of the launch template | +| [launch\_template\_id](#output\_launch\_template\_id) | The ID of the launch template | +| [launch\_template\_latest\_version](#output\_launch\_template\_latest\_version) | The latest version of the launch template | +| [launch\_template\_name](#output\_launch\_template\_name) | The name of the launch template | +| [platform](#output\_platform) | Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [user\_data](#output\_user\_data) | Base64 encoded user data | + diff --git a/modules/aws-eks/modules/self-managed-node-group/main.tf b/modules/aws-eks/modules/self-managed-node-group/main.tf new file mode 100644 index 0000000..f74fb81 --- /dev/null +++ b/modules/aws-eks/modules/self-managed-node-group/main.tf @@ -0,0 +1,568 @@ +data "aws_partition" "current" {} + +data "aws_caller_identity" "current" {} + +data "aws_default_tags" "current" {} + +data "aws_ami" "eks_default" { + count = var.create ? 1 : 0 + + filter { + name = "name" + values = ["amazon-eks-node-${var.cluster_version}-v*"] + } + + most_recent = true + owners = ["amazon"] +} + +################################################################################ +# User Data +################################################################################ + +module "user_data" { + source = "../_user_data" + + create = var.create + platform = var.platform + is_eks_managed_node_group = false + + cluster_name = var.cluster_name + cluster_endpoint = var.cluster_endpoint + cluster_auth_base64 = var.cluster_auth_base64 + + enable_bootstrap_user_data = true + pre_bootstrap_user_data = var.pre_bootstrap_user_data + post_bootstrap_user_data = var.post_bootstrap_user_data + bootstrap_extra_args = var.bootstrap_extra_args + user_data_template_path = var.user_data_template_path +} + +################################################################################ +# Launch template +################################################################################ + +locals { + launch_template_name_int = coalesce(var.launch_template_name, "${var.name}-node-group") + + security_group_ids = compact(concat([try(aws_security_group.this[0].id, ""), var.cluster_primary_security_group_id], var.vpc_security_group_ids)) +} + +resource "aws_launch_template" "this" { + count = var.create && var.create_launch_template ? 1 : 0 + + name = var.launch_template_use_name_prefix ? null : local.launch_template_name_int + name_prefix = var.launch_template_use_name_prefix ? "${local.launch_template_name_int}-" : null + description = var.launch_template_description + + ebs_optimized = var.ebs_optimized + image_id = coalesce(var.ami_id, data.aws_ami.eks_default[0].image_id) + instance_type = var.instance_type + key_name = var.key_name + user_data = module.user_data.user_data + + vpc_security_group_ids = length(var.network_interfaces) > 0 ? [] : local.security_group_ids + + default_version = var.launch_template_default_version + update_default_version = var.update_launch_template_default_version + disable_api_termination = var.disable_api_termination + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + kernel_id = var.kernel_id + ram_disk_id = var.ram_disk_id + + dynamic "block_device_mappings" { + for_each = var.block_device_mappings + content { + device_name = block_device_mappings.value.device_name + no_device = lookup(block_device_mappings.value, "no_device", null) + virtual_name = lookup(block_device_mappings.value, "virtual_name", null) + + dynamic "ebs" { + for_each = flatten([lookup(block_device_mappings.value, "ebs", [])]) + content { + delete_on_termination = lookup(ebs.value, "delete_on_termination", null) + encrypted = lookup(ebs.value, "encrypted", null) + kms_key_id = lookup(ebs.value, "kms_key_id", null) + iops = lookup(ebs.value, "iops", null) + throughput = lookup(ebs.value, "throughput", null) + snapshot_id = lookup(ebs.value, "snapshot_id", null) + volume_size = lookup(ebs.value, "volume_size", null) + volume_type = lookup(ebs.value, "volume_type", null) + } + } + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + content { + capacity_reservation_preference = lookup(capacity_reservation_specification.value, "capacity_reservation_preference", null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + content { + capacity_reservation_id = lookup(capacity_reservation_target.value, "capacity_reservation_id", null) + } + } + } + } + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + content { + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core + } + } + + dynamic "credit_specification" { + for_each = length(var.credit_specification) > 0 ? [var.credit_specification] : [] + content { + cpu_credits = credit_specification.value.cpu_credits + } + } + + dynamic "elastic_gpu_specifications" { + for_each = length(var.elastic_gpu_specifications) > 0 ? [var.elastic_gpu_specifications] : [] + content { + type = elastic_gpu_specifications.value.type + } + } + + dynamic "elastic_inference_accelerator" { + for_each = length(var.elastic_inference_accelerator) > 0 ? [var.elastic_inference_accelerator] : [] + content { + type = elastic_inference_accelerator.value.type + } + } + + dynamic "enclave_options" { + for_each = length(var.enclave_options) > 0 ? [var.enclave_options] : [] + content { + enabled = enclave_options.value.enabled + } + } + + dynamic "hibernation_options" { + for_each = length(var.hibernation_options) > 0 ? [var.hibernation_options] : [] + content { + configured = hibernation_options.value.configured + } + } + + iam_instance_profile { + arn = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].arn : var.iam_instance_profile_arn + } + + dynamic "instance_market_options" { + for_each = length(var.instance_market_options) > 0 ? [var.instance_market_options] : [] + content { + market_type = instance_market_options.value.market_type + + dynamic "spot_options" { + for_each = length(lookup(instance_market_options.value, "spot_options", {})) > 0 ? [instance_market_options.value.spot_options] : [] + content { + block_duration_minutes = lookup(spot_options.value, "block_duration_minutes", null) + instance_interruption_behavior = lookup(spot_options.value, "instance_interruption_behavior", null) + max_price = lookup(spot_options.value, "max_price", null) + spot_instance_type = lookup(spot_options.value, "spot_instance_type", null) + valid_until = lookup(spot_options.value, "valid_until", null) + } + } + } + } + + dynamic "license_specification" { + for_each = length(var.license_specifications) > 0 ? [var.license_specifications] : [] + content { + license_configuration_arn = license_specifications.value.license_configuration_arn + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + content { + http_endpoint = lookup(metadata_options.value, "http_endpoint", null) + http_tokens = lookup(metadata_options.value, "http_tokens", null) + http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", null) + http_protocol_ipv6 = lookup(metadata_options.value, "http_protocol_ipv6", null) + instance_metadata_tags = lookup(metadata_options.value, "instance_metadata_tags", null) + } + } + + dynamic "monitoring" { + for_each = var.enable_monitoring != null ? [1] : [] + content { + enabled = var.enable_monitoring + } + } + + dynamic "network_interfaces" { + for_each = var.network_interfaces + content { + associate_carrier_ip_address = lookup(network_interfaces.value, "associate_carrier_ip_address", null) + associate_public_ip_address = lookup(network_interfaces.value, "associate_public_ip_address", null) + delete_on_termination = lookup(network_interfaces.value, "delete_on_termination", null) + description = lookup(network_interfaces.value, "description", null) + device_index = lookup(network_interfaces.value, "device_index", null) + interface_type = lookup(network_interfaces.value, "interface_type", null) + ipv4_addresses = try(network_interfaces.value.ipv4_addresses, []) + ipv4_address_count = lookup(network_interfaces.value, "ipv4_address_count", null) + ipv6_addresses = try(network_interfaces.value.ipv6_addresses, []) + ipv6_address_count = lookup(network_interfaces.value, "ipv6_address_count", null) + network_interface_id = lookup(network_interfaces.value, "network_interface_id", null) + private_ip_address = lookup(network_interfaces.value, "private_ip_address", null) + security_groups = compact(concat(try(network_interfaces.value.security_groups, []), local.security_group_ids)) + subnet_id = lookup(network_interfaces.value, "subnet_id", null) + } + } + + dynamic "placement" { + for_each = length(var.placement) > 0 ? [var.placement] : [] + content { + affinity = lookup(placement.value, "affinity", null) + availability_zone = lookup(placement.value, "availability_zone", null) + group_name = lookup(placement.value, "group_name", null) + host_id = lookup(placement.value, "host_id", null) + spread_domain = lookup(placement.value, "spread_domain", null) + tenancy = lookup(placement.value, "tenancy", null) + partition_number = lookup(placement.value, "partition_number", null) + } + } + + dynamic "tag_specifications" { + for_each = toset(["instance", "volume", "network-interface"]) + content { + resource_type = tag_specifications.key + tags = merge(var.tags, { Name = var.name }, var.launch_template_tags) + } + } + + lifecycle { + create_before_destroy = true + } + + # Prevent premature access of security group roles and policies by pods that + # require permissions on create/destroy that depend on nodes + depends_on = [ + aws_security_group_rule.this, + aws_iam_role_policy_attachment.this, + ] + + tags = var.tags +} + +################################################################################ +# Node Group +################################################################################ + +locals { + launch_template_name = try(aws_launch_template.this[0].name, var.launch_template_name) + # Change order to allow users to set version priority before using defaults + launch_template_version = coalesce(var.launch_template_version, try(aws_launch_template.this[0].default_version, "$Default")) +} + +resource "aws_autoscaling_group" "this" { + count = var.create && var.create_autoscaling_group ? 1 : 0 + + name = var.use_name_prefix ? null : var.name + name_prefix = var.use_name_prefix ? "${var.name}-" : null + + dynamic "launch_template" { + for_each = var.use_mixed_instances_policy ? [] : [1] + + content { + name = local.launch_template_name + version = local.launch_template_version + } + } + + availability_zones = var.availability_zones + vpc_zone_identifier = var.subnet_ids + + min_size = var.min_size + max_size = var.max_size + desired_capacity = var.desired_size + capacity_rebalance = var.capacity_rebalance + min_elb_capacity = var.min_elb_capacity + wait_for_elb_capacity = var.wait_for_elb_capacity + wait_for_capacity_timeout = var.wait_for_capacity_timeout + default_cooldown = var.default_cooldown + protect_from_scale_in = var.protect_from_scale_in + + target_group_arns = var.target_group_arns + placement_group = var.placement_group + health_check_type = var.health_check_type + health_check_grace_period = var.health_check_grace_period + + force_delete = var.force_delete + termination_policies = var.termination_policies + suspended_processes = var.suspended_processes + max_instance_lifetime = var.max_instance_lifetime + + enabled_metrics = var.enabled_metrics + metrics_granularity = var.metrics_granularity + service_linked_role_arn = var.service_linked_role_arn + + dynamic "initial_lifecycle_hook" { + for_each = var.initial_lifecycle_hooks + content { + name = initial_lifecycle_hook.value.name + default_result = lookup(initial_lifecycle_hook.value, "default_result", null) + heartbeat_timeout = lookup(initial_lifecycle_hook.value, "heartbeat_timeout", null) + lifecycle_transition = initial_lifecycle_hook.value.lifecycle_transition + notification_metadata = lookup(initial_lifecycle_hook.value, "notification_metadata", null) + notification_target_arn = lookup(initial_lifecycle_hook.value, "notification_target_arn", null) + role_arn = lookup(initial_lifecycle_hook.value, "role_arn", null) + } + } + + dynamic "instance_refresh" { + for_each = length(var.instance_refresh) > 0 ? [var.instance_refresh] : [] + content { + strategy = instance_refresh.value.strategy + triggers = lookup(instance_refresh.value, "triggers", null) + + dynamic "preferences" { + for_each = length(lookup(instance_refresh.value, "preferences", {})) > 0 ? [instance_refresh.value.preferences] : [] + content { + instance_warmup = lookup(preferences.value, "instance_warmup", null) + min_healthy_percentage = lookup(preferences.value, "min_healthy_percentage", null) + checkpoint_delay = lookup(preferences.value, "checkpoint_delay", null) + checkpoint_percentages = lookup(preferences.value, "checkpoint_percentages", null) + } + } + } + } + + dynamic "mixed_instances_policy" { + for_each = var.use_mixed_instances_policy ? [var.mixed_instances_policy] : [] + content { + dynamic "instances_distribution" { + for_each = try([mixed_instances_policy.value.instances_distribution], []) + content { + on_demand_allocation_strategy = lookup(instances_distribution.value, "on_demand_allocation_strategy", null) + on_demand_base_capacity = lookup(instances_distribution.value, "on_demand_base_capacity", null) + on_demand_percentage_above_base_capacity = lookup(instances_distribution.value, "on_demand_percentage_above_base_capacity", null) + spot_allocation_strategy = lookup(instances_distribution.value, "spot_allocation_strategy", null) + spot_instance_pools = lookup(instances_distribution.value, "spot_instance_pools", null) + spot_max_price = lookup(instances_distribution.value, "spot_max_price", null) + } + } + + launch_template { + launch_template_specification { + launch_template_name = local.launch_template_name + version = local.launch_template_version + } + + dynamic "override" { + for_each = try(mixed_instances_policy.value.override, []) + content { + instance_type = lookup(override.value, "instance_type", null) + weighted_capacity = lookup(override.value, "weighted_capacity", null) + + dynamic "launch_template_specification" { + for_each = length(lookup(override.value, "launch_template_specification", {})) > 0 ? override.value.launch_template_specification : [] + content { + launch_template_id = lookup(launch_template_specification.value, "launch_template_id", null) + } + } + } + } + } + } + } + + dynamic "warm_pool" { + for_each = length(var.warm_pool) > 0 ? [var.warm_pool] : [] + content { + pool_state = lookup(warm_pool.value, "pool_state", null) + min_size = lookup(warm_pool.value, "min_size", null) + max_group_prepared_capacity = lookup(warm_pool.value, "max_group_prepared_capacity", null) + } + } + + dynamic "tag" { + for_each = merge( + { + "Name" = var.name + "kubernetes.io/cluster/${var.cluster_name}" = "owned" + "k8s.io/cluster/${var.cluster_name}" = "owned" + }, + var.use_default_tags ? merge(data.aws_default_tags.current.tags, var.tags) : var.tags + ) + + content { + key = tag.key + value = tag.value + propagate_at_launch = true + } + } + + dynamic "tag" { + for_each = var.autoscaling_group_tags + + content { + key = tag.key + value = tag.value + propagate_at_launch = false + } + } + + timeouts { + delete = var.delete_timeout + } + + lifecycle { + create_before_destroy = true + ignore_changes = [ + desired_capacity + ] + } +} + +################################################################################ +# Autoscaling group schedule +################################################################################ + +resource "aws_autoscaling_schedule" "this" { + for_each = var.create && var.create_schedule ? var.schedules : {} + + scheduled_action_name = each.key + autoscaling_group_name = aws_autoscaling_group.this[0].name + + min_size = lookup(each.value, "min_size", null) + max_size = lookup(each.value, "max_size", null) + desired_capacity = lookup(each.value, "desired_size", null) + start_time = lookup(each.value, "start_time", null) + end_time = lookup(each.value, "end_time", null) + time_zone = lookup(each.value, "time_zone", null) + + # [Minute] [Hour] [Day_of_Month] [Month_of_Year] [Day_of_Week] + # Cron examples: https://crontab.guru/examples.html + recurrence = lookup(each.value, "recurrence", null) +} + +################################################################################ +# Security Group +################################################################################ + +locals { + security_group_name = coalesce(var.security_group_name, "${var.name}-node-group") + create_security_group = var.create && var.create_security_group +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { + "Name" = local.security_group_name + }, + var.security_group_tags + ) + + # https://github.com/hashicorp/terraform-provider-aws/issues/2445 + # https://github.com/hashicorp/terraform-provider-aws/issues/9692 + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = try(each.value.description, null) + cidr_blocks = try(each.value.cidr_blocks, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, []) + self = try(each.value.self, null) + source_security_group_id = try( + each.value.source_security_group_id, + try(each.value.source_cluster_security_group, false) ? var.cluster_security_group_id : null + ) +} + +################################################################################ +# IAM Role +################################################################################ + +locals { + iam_role_name = coalesce(var.iam_role_name, "${var.name}-node-group") + + iam_role_policy_prefix = "arn:${data.aws_partition.current.partition}:iam::aws:policy" + + cni_policy = var.cluster_ip_family == "ipv6" ? "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:policy/AmazonEKS_CNI_IPv6_Policy" : "${local.iam_role_policy_prefix}/AmazonEKS_CNI_Policy" +} + +data "aws_iam_policy_document" "assume_role_policy" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + statement { + sid = "EKSNodeAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = var.create && var.create_iam_instance_profile ? toset(compact(distinct(concat([ + "${local.iam_role_policy_prefix}/AmazonEKSWorkerNodePolicy", + "${local.iam_role_policy_prefix}/AmazonEC2ContainerRegistryReadOnly", + var.iam_role_attach_cni_policy ? local.cni_policy : "", + ], var.iam_role_additional_policies)))) : toset([]) + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +# Only self-managed node group requires instance profile +resource "aws_iam_instance_profile" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + role = aws_iam_role.this[0].name + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + + lifecycle { + create_before_destroy = true + } + + tags = merge(var.tags, var.iam_role_tags) +} diff --git a/modules/aws-eks/modules/self-managed-node-group/outputs.tf b/modules/aws-eks/modules/self-managed-node-group/outputs.tf new file mode 100644 index 0000000..0cfba97 --- /dev/null +++ b/modules/aws-eks/modules/self-managed-node-group/outputs.tf @@ -0,0 +1,162 @@ +################################################################################ +# Launch template +################################################################################ + +output "launch_template_id" { + description = "The ID of the launch template" + value = try(aws_launch_template.this[0].id, "") +} + +output "launch_template_arn" { + description = "The ARN of the launch template" + value = try(aws_launch_template.this[0].arn, "") +} + +output "launch_template_latest_version" { + description = "The latest version of the launch template" + value = try(aws_launch_template.this[0].latest_version, "") +} + +output "launch_template_name" { + description = "The name of the launch template" + value = try(aws_launch_template.this[0].name, "") +} + +################################################################################ +# autoscaling group +################################################################################ + +output "autoscaling_group_arn" { + description = "The ARN for this autoscaling group" + value = try(aws_autoscaling_group.this[0].arn, "") +} + +output "autoscaling_group_id" { + description = "The autoscaling group id" + value = try(aws_autoscaling_group.this[0].id, "") +} + +output "autoscaling_group_name" { + description = "The autoscaling group name" + value = try(aws_autoscaling_group.this[0].name, "") +} + +output "autoscaling_group_min_size" { + description = "The minimum size of the autoscaling group" + value = try(aws_autoscaling_group.this[0].min_size, "") +} + +output "autoscaling_group_max_size" { + description = "The maximum size of the autoscaling group" + value = try(aws_autoscaling_group.this[0].max_size, "") +} + +output "autoscaling_group_desired_capacity" { + description = "The number of Amazon EC2 instances that should be running in the group" + value = try(aws_autoscaling_group.this[0].desired_capacity, "") +} + +output "autoscaling_group_default_cooldown" { + description = "Time between a scaling activity and the succeeding scaling activity" + value = try(aws_autoscaling_group.this[0].default_cooldown, "") +} + +output "autoscaling_group_health_check_grace_period" { + description = "Time after instance comes into service before checking health" + value = try(aws_autoscaling_group.this[0].health_check_grace_period, "") +} + +output "autoscaling_group_health_check_type" { + description = "EC2 or ELB. Controls how health checking is done" + value = try(aws_autoscaling_group.this[0].health_check_type, "") +} + +output "autoscaling_group_availability_zones" { + description = "The availability zones of the autoscaling group" + value = try(aws_autoscaling_group.this[0].availability_zones, "") +} + +output "autoscaling_group_vpc_zone_identifier" { + description = "The VPC zone identifier" + value = try(aws_autoscaling_group.this[0].vpc_zone_identifier, "") +} + +################################################################################ +# autoscaling group schedule +################################################################################ + +output "autoscaling_group_schedule_arns" { + description = "ARNs of autoscaling group schedules" + value = { for k, v in aws_autoscaling_schedule.this : k => v.arn } +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, "") +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, "") +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} + +################################################################################ +# IAM Instance Profile +################################################################################ + +output "iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = try(aws_iam_instance_profile.this[0].arn, var.iam_instance_profile_arn) +} + +output "iam_instance_profile_id" { + description = "Instance profile's ID" + value = try(aws_iam_instance_profile.this[0].id, "") +} + +output "iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = try(aws_iam_instance_profile.this[0].unique_id, "") +} + +################################################################################ +# Additional +################################################################################ + +output "platform" { + description = "Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based" + value = var.platform +} + +output "image_id" { + description = "ID of the image" + value = try(data.aws_ami.eks_default[0].image_id, "") +} + +output "user_data" { + description = "Base64 encoded user data" + value = try(module.user_data.user_data, "") +} diff --git a/modules/aws-eks/modules/self-managed-node-group/variables.tf b/modules/aws-eks/modules/self-managed-node-group/variables.tf new file mode 100644 index 0000000..3734d90 --- /dev/null +++ b/modules/aws-eks/modules/self-managed-node-group/variables.tf @@ -0,0 +1,609 @@ +variable "create" { + description = "Determines whether to create self managed node group or not" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "platform" { + description = "Identifies if the OS platform is `bottlerocket`, `linux`, or `windows` based" + type = string + default = "linux" +} + +################################################################################ +# User Data +################################################################################ + +variable "cluster_name" { + description = "Name of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_endpoint" { + description = "Endpoint of associated EKS cluster" + type = string + default = "" +} + +variable "cluster_auth_base64" { + description = "Base64 encoded CA of associated EKS cluster" + type = string + default = "" +} + +variable "pre_bootstrap_user_data" { + description = "User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "post_bootstrap_user_data" { + description = "User data that is appended to the user data script after of the EKS bootstrap script. Not used when `platform` = `bottlerocket`" + type = string + default = "" +} + +variable "bootstrap_extra_args" { + description = "Additional arguments passed to the bootstrap script. When `platform` = `bottlerocket`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data" + type = string + default = "" +} + +variable "user_data_template_path" { + description = "Path to a local, custom user data template file to use when rendering user data" + type = string + default = "" +} + +################################################################################ +# Launch template +################################################################################ + +variable "create_launch_template" { + description = "Determines whether to create launch template or not" + type = bool + default = true +} + +variable "launch_template_name" { + description = "Launch template name - either to be created (`var.create_launch_template` = `true`) or existing (`var.create_launch_template` = `false`)" + type = string + default = null +} + +variable "launch_template_use_name_prefix" { + description = "Determines whether to use `launch_template_name` as is or create a unique name beginning with the `launch_template_name` as the prefix" + type = bool + default = true +} + +variable "launch_template_description" { + description = "Description of the launch template" + type = string + default = null +} + +variable "launch_template_default_version" { + description = "Default Version of the launch template" + type = string + default = null +} + +variable "update_launch_template_default_version" { + description = "Whether to update Default Version each update. Conflicts with `launch_template_default_version`" + type = bool + default = true +} + +variable "disable_api_termination" { + description = "If true, enables EC2 instance termination protection" + type = bool + default = null +} + +variable "instance_initiated_shutdown_behavior" { + description = "Shutdown behavior for the instance. Can be `stop` or `terminate`. (Default: `stop`)" + type = string + default = null +} + +variable "kernel_id" { + description = "The kernel ID" + type = string + default = null +} + +variable "ram_disk_id" { + description = "The ID of the ram disk" + type = string + default = null +} + +variable "block_device_mappings" { + description = "Specify volumes to attach to the instance besides the volumes specified by the AMI" + type = any + default = {} +} + +variable "capacity_reservation_specification" { + description = "Targeting for EC2 capacity reservations" + type = any + default = {} +} + +variable "cpu_options" { + description = "The CPU options for the instance" + type = map(string) + default = {} +} + +variable "credit_specification" { + description = "Customize the credit specification of the instance" + type = map(string) + default = {} +} + +variable "elastic_gpu_specifications" { + description = "The elastic GPU to attach to the instance" + type = map(string) + default = {} +} + +variable "elastic_inference_accelerator" { + description = "Configuration block containing an Elastic Inference Accelerator to attach to the instance" + type = map(string) + default = {} +} + +variable "enclave_options" { + description = "Enable Nitro Enclaves on launched instances" + type = map(string) + default = {} +} + +variable "hibernation_options" { + description = "The hibernation options for the instance" + type = map(string) + default = {} +} + +variable "instance_market_options" { + description = "The market (purchasing) option for the instance" + type = any + default = {} +} + +variable "license_specifications" { + description = "A list of license specifications to associate with" + type = map(string) + default = {} +} + +variable "network_interfaces" { + description = "Customize network interfaces to be attached at instance boot time" + type = list(any) + default = [] +} + +variable "placement" { + description = "The placement of the instance" + type = map(string) + default = {} +} + +variable "ebs_optimized" { + description = "If true, the launched EC2 instance will be EBS-optimized" + type = bool + default = null +} + +variable "ami_id" { + description = "The AMI from which to launch the instance" + type = string + default = "" +} + +variable "cluster_version" { + description = "Kubernetes cluster version - used to lookup default AMI ID if one is not provided" + type = string + default = null +} + +variable "instance_type" { + description = "The type of the instance to launch" + type = string + default = "" +} + +variable "key_name" { + description = "The key name that should be used for the instance" + type = string + default = null +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate" + type = list(string) + default = [] +} + +variable "cluster_primary_security_group_id" { + description = "The ID of the EKS cluster primary security group to associate with the instance(s). This is the security group that is automatically created by the EKS service" + type = string + default = null +} + +variable "enable_monitoring" { + description = "Enables/disables detailed monitoring" + type = bool + default = true +} + +variable "metadata_options" { + description = "Customize the metadata options for the instance" + type = map(string) + default = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + } +} + +variable "launch_template_tags" { + description = "A map of additional tags to add to the tag_specifications of launch template created" + type = map(string) + default = {} +} + +################################################################################ +# Autoscaling group +################################################################################ + +variable "create_autoscaling_group" { + description = "Determines whether to create autoscaling group or not" + type = bool + default = true +} + +variable "name" { + description = "Name of the Self managed Node Group" + type = string + default = "" +} + +variable "use_name_prefix" { + description = "Determines whether to use `name` as is or create a unique name beginning with the `name` as the prefix" + type = bool + default = true +} + +variable "launch_template_version" { + description = "Launch template version. Can be version number, `$Latest`, or `$Default`" + type = string + default = null +} + +variable "availability_zones" { + description = "A list of one or more availability zones for the group. Used for EC2-Classic and default subnets when not specified with `subnet_ids` argument. Conflicts with `subnet_ids`" + type = list(string) + default = null +} + +variable "subnet_ids" { + description = "A list of subnet IDs to launch resources in. Subnets automatically determine which availability zones the group will reside. Conflicts with `availability_zones`" + type = list(string) + default = null +} + +variable "min_size" { + description = "The minimum size of the autoscaling group" + type = number + default = 0 +} + +variable "max_size" { + description = "The maximum size of the autoscaling group" + type = number + default = 3 +} + +variable "desired_size" { + description = "The number of Amazon EC2 instances that should be running in the autoscaling group" + type = number + default = 1 +} + +variable "capacity_rebalance" { + description = "Indicates whether capacity rebalance is enabled" + type = bool + default = null +} + +variable "min_elb_capacity" { + description = "Setting this causes Terraform to wait for this number of instances to show up healthy in the ELB only on creation. Updates will not wait on ELB instance number changes" + type = number + default = null +} + +variable "wait_for_elb_capacity" { + description = "Setting this will cause Terraform to wait for exactly this number of healthy instances in all attached load balancers on both create and update operations. Takes precedence over `min_elb_capacity` behavior." + type = number + default = null +} + +variable "wait_for_capacity_timeout" { + description = "A maximum duration that Terraform should wait for ASG instances to be healthy before timing out. (See also Waiting for Capacity below.) Setting this to '0' causes Terraform to skip all Capacity Waiting behavior." + type = string + default = null +} + +variable "default_cooldown" { + description = "The amount of time, in seconds, after a scaling activity completes before another scaling activity can start" + type = number + default = null +} + +variable "protect_from_scale_in" { + description = "Allows setting instance protection. The autoscaling group will not select instances with this setting for termination during scale in events." + type = bool + default = false +} + +variable "target_group_arns" { + description = "A set of `aws_alb_target_group` ARNs, for use with Application or Network Load Balancing" + type = list(string) + default = [] +} + +variable "placement_group" { + description = "The name of the placement group into which you'll launch your instances, if any" + type = string + default = null +} + +variable "health_check_type" { + description = "`EC2` or `ELB`. Controls how health checking is done" + type = string + default = null +} + +variable "health_check_grace_period" { + description = "Time (in seconds) after instance comes into service before checking health" + type = number + default = null +} + +variable "force_delete" { + description = "Allows deleting the Auto Scaling Group without waiting for all instances in the pool to terminate. You can force an Auto Scaling Group to delete even if it's in the process of scaling a resource. Normally, Terraform drains all the instances before deleting the group. This bypasses that behavior and potentially leaves resources dangling" + type = bool + default = null +} + +variable "termination_policies" { + description = "A list of policies to decide how the instances in the Auto Scaling Group should be terminated. The allowed values are `OldestInstance`, `NewestInstance`, `OldestLaunchConfiguration`, `ClosestToNextInstanceHour`, `OldestLaunchTemplate`, `AllocationStrategy`, `Default`" + type = list(string) + default = [] +} + +variable "suspended_processes" { + description = "A list of processes to suspend for the Auto Scaling Group. The allowed values are `Launch`, `Terminate`, `HealthCheck`, `ReplaceUnhealthy`, `AZRebalance`, `AlarmNotification`, `ScheduledActions`, `AddToLoadBalancer`. Note that if you suspend either the `Launch` or `Terminate` process types, it can prevent your Auto Scaling Group from functioning properly" + type = list(string) + default = [] +} + +variable "max_instance_lifetime" { + description = "The maximum amount of time, in seconds, that an instance can be in service, values must be either equal to 0 or between 604800 and 31536000 seconds" + type = number + default = null +} + +variable "enabled_metrics" { + description = "A list of metrics to collect. The allowed values are `GroupDesiredCapacity`, `GroupInServiceCapacity`, `GroupPendingCapacity`, `GroupMinSize`, `GroupMaxSize`, `GroupInServiceInstances`, `GroupPendingInstances`, `GroupStandbyInstances`, `GroupStandbyCapacity`, `GroupTerminatingCapacity`, `GroupTerminatingInstances`, `GroupTotalCapacity`, `GroupTotalInstances`" + type = list(string) + default = [] +} + +variable "metrics_granularity" { + description = "The granularity to associate with the metrics to collect. The only valid value is `1Minute`" + type = string + default = null +} + +variable "service_linked_role_arn" { + description = "The ARN of the service-linked role that the ASG will use to call other AWS services" + type = string + default = null +} + +variable "initial_lifecycle_hooks" { + description = "One or more Lifecycle Hooks to attach to the Auto Scaling Group before instances are launched. The syntax is exactly the same as the separate `aws_autoscaling_lifecycle_hook` resource, without the `autoscaling_group_name` attribute. Please note that this will only work when creating a new Auto Scaling Group. For all other use-cases, please use `aws_autoscaling_lifecycle_hook` resource" + type = list(map(string)) + default = [] +} + +variable "instance_refresh" { + description = "If this block is configured, start an Instance Refresh when this Auto Scaling Group is updated" + type = any + default = {} +} + +variable "use_mixed_instances_policy" { + description = "Determines whether to use a mixed instances policy in the autoscaling group or not" + type = bool + default = false +} + +variable "mixed_instances_policy" { + description = "Configuration block containing settings to define launch targets for Auto Scaling groups" + type = any + default = null +} + +variable "warm_pool" { + description = "If this block is configured, add a Warm Pool to the specified Auto Scaling group" + type = any + default = {} +} + +variable "delete_timeout" { + description = "Delete timeout to wait for destroying autoscaling group" + type = string + default = null +} + +variable "use_default_tags" { + description = "Enables/disables the use of provider default tags in the tag_specifications of the Auto Scaling group" + type = bool + default = false +} + +variable "autoscaling_group_tags" { + description = "A map of additional tags to add to the autoscaling group created. Tags are applied to the autoscaling group only and are NOT propagated to instances" + type = map(string) + default = {} +} + +################################################################################ +# Autoscaling group schedule +################################################################################ + +variable "create_schedule" { + description = "Determines whether to create autoscaling group schedule or not" + type = bool + default = true +} + +variable "schedules" { + description = "Map of autoscaling group schedule to create" + type = map(any) + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines whether to create a security group" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description for the security group created" + type = string + default = "EKS self-managed node group security group" +} + +variable "vpc_id" { + description = "ID of the VPC where the security group/nodes will be provisioned" + type = string + default = null +} + +variable "security_group_rules" { + description = "List of security group rules to add to the security group created" + type = any + default = {} +} + +variable "cluster_security_group_id" { + description = "Cluster control plane security group ID" + type = string + default = null +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +################################################################################ +# IAM Role +################################################################################ + +variable "create_iam_instance_profile" { + description = "Determines whether an IAM instance profile is created or to use an existing IAM instance profile" + type = bool + default = true +} + +variable "cluster_ip_family" { + description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`" + type = string + default = null +} + +variable "iam_instance_profile_arn" { + description = "Amazon Resource Name (ARN) of an existing IAM instance profile that provides permissions for the node group. Required if `create_iam_instance_profile` = `false`" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether cluster IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_attach_cni_policy" { + description = "Whether to attach the `AmazonEKS_CNI_Policy`/`AmazonEKS_CNI_IPv6_Policy` IAM policy to the IAM IAM role. WARNING: If set `false` the permissions must be assigned to the `aws-node` DaemonSet pods via another method or nodes will not be able to join the cluster" + type = bool + default = true +} + +variable "iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = list(string) + default = [] +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} diff --git a/modules/aws-eks/modules/self-managed-node-group/versions.tf b/modules/aws-eks/modules/self-managed-node-group/versions.tf new file mode 100644 index 0000000..22e8d72 --- /dev/null +++ b/modules/aws-eks/modules/self-managed-node-group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + } +} diff --git a/modules/aws-eks/node_groups.tf b/modules/aws-eks/node_groups.tf new file mode 100644 index 0000000..73480e8 --- /dev/null +++ b/modules/aws-eks/node_groups.tf @@ -0,0 +1,488 @@ +locals { + metadata_options = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + } +} + +################################################################################ +# EKS IPV6 CNI Policy +# TODO - hopefully AWS releases a managed policy which can replace this +# https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy +################################################################################ + +data "aws_iam_policy_document" "cni_ipv6_policy" { + count = var.create && var.create_cni_ipv6_iam_policy ? 1 : 0 + + statement { + sid = "AssignDescribe" + actions = [ + "ec2:AssignIpv6Addresses", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypes" + ] + resources = ["*"] + } + + statement { + sid = "CreateTags" + actions = ["ec2:CreateTags"] + resources = ["arn:${data.aws_partition.current.partition}:ec2:*:*:network-interface/*"] + } +} + +# Note - we are keeping this to a minimim in hopes that its soon replaced with an AWS managed policy like `AmazonEKS_CNI_Policy` +resource "aws_iam_policy" "cni_ipv6_policy" { + count = var.create && var.create_cni_ipv6_iam_policy ? 1 : 0 + + # Will cause conflicts if trying to create on multiple clusters but necessary to reference by exact name in sub-modules + name = "AmazonEKS_CNI_IPv6_Policy" + description = "IAM policy for EKS CNI to assign IPV6 addresses" + policy = data.aws_iam_policy_document.cni_ipv6_policy[0].json + + tags = var.tags +} + +################################################################################ +# Node Security Group +# Defaults follow https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html +# Plus NTP/HTTPS (otherwise nodes fail to launch) +################################################################################ + +locals { + node_sg_name = coalesce(var.node_security_group_name, "${var.cluster_name}-node") + create_node_sg = var.create && var.create_node_security_group + + node_security_group_id = local.create_node_sg ? aws_security_group.node[0].id : var.node_security_group_id + + node_security_group_rules = { + egress_cluster_443 = { + description = "Node groups to cluster API" + protocol = "tcp" + from_port = 443 + to_port = 443 + type = "egress" + source_cluster_security_group = true + } + ingress_cluster_443 = { + description = "Cluster API to node groups" + protocol = "tcp" + from_port = 443 + to_port = 443 + type = "ingress" + source_cluster_security_group = true + } + ingress_apiserver_9443 = { + description = "Cluster API to Kube Apiserver for AWS Load Balancer Controller" + protocol = "tcp" + from_port = 9443 + to_port = 9443 + type = "ingress" + source_cluster_security_group = true + } + ingress_apiserver_8443 = { + description = "Nginx Ingress controller to Kube Apiserver" + protocol = "tcp" + from_port = 8443 + to_port = 8443 + type = "ingress" + source_cluster_security_group = true + } + ingress_cluster_kubelet = { + description = "Cluster API to node kubelets" + protocol = "tcp" + from_port = 10250 + to_port = 10250 + type = "ingress" + source_cluster_security_group = true + } + ingress_self_apiserver_8443 = { + description = "Nginx Ingress controller to Kube Apiserver" + protocol = "tcp" + from_port = 8443 + to_port = 8443 + type = "ingress" + self = true + } + ingress_self_coredns_tcp = { + description = "Node to node CoreDNS" + protocol = "tcp" + from_port = 53 + to_port = 53 + type = "ingress" + self = true + } + egress_self_coredns_tcp = { + description = "Node to node CoreDNS" + protocol = "tcp" + from_port = 53 + to_port = 53 + type = "egress" + self = true + } + ingress_self_coredns_udp = { + description = "Node to node CoreDNS" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "ingress" + self = true + } + egress_self_coredns_udp = { + description = "Node to node CoreDNS" + protocol = "udp" + from_port = 53 + to_port = 53 + type = "egress" + self = true + } + egress_https = { + description = "Egress all HTTPS to internet" + protocol = "tcp" + from_port = 443 + to_port = 443 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = var.cluster_ip_family == "ipv6" ? ["::/0"] : null + } + egress_ntp_tcp = { + description = "Egress NTP/TCP to internet" + protocol = "tcp" + from_port = 123 + to_port = 123 + type = "egress" + cidr_blocks = var.node_security_group_ntp_ipv4_cidr_block + ipv6_cidr_blocks = var.cluster_ip_family == "ipv6" ? var.node_security_group_ntp_ipv6_cidr_block : null + } + egress_ntp_udp = { + description = "Egress NTP/UDP to internet" + protocol = "udp" + from_port = 123 + to_port = 123 + type = "egress" + cidr_blocks = var.node_security_group_ntp_ipv4_cidr_block + ipv6_cidr_blocks = var.cluster_ip_family == "ipv6" ? var.node_security_group_ntp_ipv6_cidr_block : null + } + } +} + +resource "aws_security_group" "node" { + count = local.create_node_sg ? 1 : 0 + + name = var.node_security_group_use_name_prefix ? null : local.node_sg_name + name_prefix = var.node_security_group_use_name_prefix ? "${local.node_sg_name}${var.prefix_separator}" : null + description = var.node_security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { + "Name" = local.node_sg_name + "kubernetes.io/cluster/${var.cluster_name}" = "owned" + }, + var.node_security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "node" { + for_each = { for k, v in merge(local.node_security_group_rules, var.node_security_group_additional_rules) : k => v if local.create_node_sg } + + # Required + security_group_id = aws_security_group.node[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = try(each.value.description, null) + cidr_blocks = try(each.value.cidr_blocks, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, []) + self = try(each.value.self, null) + source_security_group_id = try( + each.value.source_security_group_id, + try(each.value.source_cluster_security_group, false) ? local.cluster_security_group_id : null + ) +} + +################################################################################ +# Fargate Profile +################################################################################ + +module "fargate_profile" { + source = "./modules/fargate-profile" + + for_each = { for k, v in var.fargate_profiles : k => v if var.create } + + create = try(each.value.create, true) + + # Fargate Profile + cluster_name = aws_eks_cluster.this[0].name + cluster_ip_family = var.cluster_ip_family + name = try(each.value.name, each.key) + subnet_ids = try(each.value.subnet_ids, var.fargate_profile_defaults.subnet_ids, var.subnet_ids) + selectors = try(each.value.selectors, var.fargate_profile_defaults.selectors, []) + timeouts = try(each.value.timeouts, var.fargate_profile_defaults.timeouts, {}) + + # IAM role + create_iam_role = try(each.value.create_iam_role, var.fargate_profile_defaults.create_iam_role, true) + iam_role_arn = try(each.value.iam_role_arn, var.fargate_profile_defaults.iam_role_arn, null) + iam_role_name = try(each.value.iam_role_name, var.fargate_profile_defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.fargate_profile_defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.fargate_profile_defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.fargate_profile_defaults.iam_role_description, "Fargate profile IAM role") + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.fargate_profile_defaults.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, var.fargate_profile_defaults.iam_role_tags, {}) + iam_role_attach_cni_policy = try(each.value.iam_role_attach_cni_policy, var.fargate_profile_defaults.iam_role_attach_cni_policy, true) + iam_role_additional_policies = try(each.value.iam_role_additional_policies, var.fargate_profile_defaults.iam_role_additional_policies, []) + + tags = merge(var.tags, try(each.value.tags, var.fargate_profile_defaults.tags, {})) +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +module "eks_managed_node_group" { + source = "./modules/eks-managed-node-group" + + for_each = { for k, v in var.eks_managed_node_groups : k => v if var.create } + + create = try(each.value.create, true) + + cluster_name = aws_eks_cluster.this[0].name + cluster_version = try(each.value.cluster_version, var.eks_managed_node_group_defaults.cluster_version, aws_eks_cluster.this[0].version) + cluster_security_group_id = local.cluster_security_group_id + cluster_ip_family = var.cluster_ip_family + + # EKS Managed Node Group + name = try(each.value.name, each.key) + use_name_prefix = try(each.value.use_name_prefix, var.eks_managed_node_group_defaults.use_name_prefix, true) + + subnet_ids = try(each.value.subnet_ids, var.eks_managed_node_group_defaults.subnet_ids, var.subnet_ids) + + min_size = try(each.value.min_size, var.eks_managed_node_group_defaults.min_size, 1) + max_size = try(each.value.max_size, var.eks_managed_node_group_defaults.max_size, 3) + desired_size = try(each.value.desired_size, var.eks_managed_node_group_defaults.desired_size, 1) + + ami_id = try(each.value.ami_id, var.eks_managed_node_group_defaults.ami_id, "") + ami_type = try(each.value.ami_type, var.eks_managed_node_group_defaults.ami_type, null) + ami_release_version = try(each.value.ami_release_version, var.eks_managed_node_group_defaults.ami_release_version, null) + + capacity_type = try(each.value.capacity_type, var.eks_managed_node_group_defaults.capacity_type, null) + disk_size = try(each.value.disk_size, var.eks_managed_node_group_defaults.disk_size, null) + force_update_version = try(each.value.force_update_version, var.eks_managed_node_group_defaults.force_update_version, null) + instance_types = try(each.value.instance_types, var.eks_managed_node_group_defaults.instance_types, null) + labels = try(each.value.labels, var.eks_managed_node_group_defaults.labels, null) + + remote_access = try(each.value.remote_access, var.eks_managed_node_group_defaults.remote_access, {}) + taints = try(each.value.taints, var.eks_managed_node_group_defaults.taints, {}) + update_config = try(each.value.update_config, var.eks_managed_node_group_defaults.update_config, {}) + timeouts = try(each.value.timeouts, var.eks_managed_node_group_defaults.timeouts, {}) + + # User data + platform = try(each.value.platform, var.eks_managed_node_group_defaults.platform, "linux") + cluster_endpoint = try(aws_eks_cluster.this[0].endpoint, "") + cluster_auth_base64 = try(aws_eks_cluster.this[0].certificate_authority[0].data, "") + cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr + enable_bootstrap_user_data = try(each.value.enable_bootstrap_user_data, var.eks_managed_node_group_defaults.enable_bootstrap_user_data, false) + pre_bootstrap_user_data = try(each.value.pre_bootstrap_user_data, var.eks_managed_node_group_defaults.pre_bootstrap_user_data, "") + post_bootstrap_user_data = try(each.value.post_bootstrap_user_data, var.eks_managed_node_group_defaults.post_bootstrap_user_data, "") + bootstrap_extra_args = try(each.value.bootstrap_extra_args, var.eks_managed_node_group_defaults.bootstrap_extra_args, "") + user_data_template_path = try(each.value.user_data_template_path, var.eks_managed_node_group_defaults.user_data_template_path, "") + + # Launch Template + create_launch_template = try(each.value.create_launch_template, var.eks_managed_node_group_defaults.create_launch_template, true) + launch_template_name = try(each.value.launch_template_name, var.eks_managed_node_group_defaults.launch_template_name, each.key) + launch_template_use_name_prefix = try(each.value.launch_template_use_name_prefix, var.eks_managed_node_group_defaults.launch_template_use_name_prefix, true) + launch_template_version = try(each.value.launch_template_version, var.eks_managed_node_group_defaults.launch_template_version, null) + launch_template_description = try(each.value.launch_template_description, var.eks_managed_node_group_defaults.launch_template_description, "Custom launch template for ${try(each.value.name, each.key)} EKS managed node group") + launch_template_tags = try(each.value.launch_template_tags, var.eks_managed_node_group_defaults.launch_template_tags, {}) + + ebs_optimized = try(each.value.ebs_optimized, var.eks_managed_node_group_defaults.ebs_optimized, null) + key_name = try(each.value.key_name, var.eks_managed_node_group_defaults.key_name, null) + launch_template_default_version = try(each.value.launch_template_default_version, var.eks_managed_node_group_defaults.launch_template_default_version, null) + update_launch_template_default_version = try(each.value.update_launch_template_default_version, var.eks_managed_node_group_defaults.update_launch_template_default_version, true) + disable_api_termination = try(each.value.disable_api_termination, var.eks_managed_node_group_defaults.disable_api_termination, null) + kernel_id = try(each.value.kernel_id, var.eks_managed_node_group_defaults.kernel_id, null) + ram_disk_id = try(each.value.ram_disk_id, var.eks_managed_node_group_defaults.ram_disk_id, null) + + block_device_mappings = try(each.value.block_device_mappings, var.eks_managed_node_group_defaults.block_device_mappings, {}) + capacity_reservation_specification = try(each.value.capacity_reservation_specification, var.eks_managed_node_group_defaults.capacity_reservation_specification, {}) + cpu_options = try(each.value.cpu_options, var.eks_managed_node_group_defaults.cpu_options, {}) + credit_specification = try(each.value.credit_specification, var.eks_managed_node_group_defaults.credit_specification, {}) + elastic_gpu_specifications = try(each.value.elastic_gpu_specifications, var.eks_managed_node_group_defaults.elastic_gpu_specifications, {}) + elastic_inference_accelerator = try(each.value.elastic_inference_accelerator, var.eks_managed_node_group_defaults.elastic_inference_accelerator, {}) + enclave_options = try(each.value.enclave_options, var.eks_managed_node_group_defaults.enclave_options, {}) + instance_market_options = try(each.value.instance_market_options, var.eks_managed_node_group_defaults.instance_market_options, {}) + license_specifications = try(each.value.license_specifications, var.eks_managed_node_group_defaults.license_specifications, {}) + metadata_options = try(each.value.metadata_options, var.eks_managed_node_group_defaults.metadata_options, local.metadata_options) + enable_monitoring = try(each.value.enable_monitoring, var.eks_managed_node_group_defaults.enable_monitoring, true) + network_interfaces = try(each.value.network_interfaces, var.eks_managed_node_group_defaults.network_interfaces, []) + placement = try(each.value.placement, var.eks_managed_node_group_defaults.placement, {}) + + # IAM role + create_iam_role = try(each.value.create_iam_role, var.eks_managed_node_group_defaults.create_iam_role, true) + iam_role_arn = try(each.value.iam_role_arn, var.eks_managed_node_group_defaults.iam_role_arn, null) + iam_role_name = try(each.value.iam_role_name, var.eks_managed_node_group_defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.eks_managed_node_group_defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.eks_managed_node_group_defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.eks_managed_node_group_defaults.iam_role_description, "EKS managed node group IAM role") + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.eks_managed_node_group_defaults.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, var.eks_managed_node_group_defaults.iam_role_tags, {}) + iam_role_attach_cni_policy = try(each.value.iam_role_attach_cni_policy, var.eks_managed_node_group_defaults.iam_role_attach_cni_policy, true) + iam_role_additional_policies = try(each.value.iam_role_additional_policies, var.eks_managed_node_group_defaults.iam_role_additional_policies, []) + + # Security group + vpc_security_group_ids = compact(concat([local.node_security_group_id], try(each.value.vpc_security_group_ids, var.eks_managed_node_group_defaults.vpc_security_group_ids, []))) + cluster_primary_security_group_id = try(each.value.attach_cluster_primary_security_group, var.eks_managed_node_group_defaults.attach_cluster_primary_security_group, false) ? aws_eks_cluster.this[0].vpc_config[0].cluster_security_group_id : null + create_security_group = try(each.value.create_security_group, var.eks_managed_node_group_defaults.create_security_group, true) + security_group_name = try(each.value.security_group_name, var.eks_managed_node_group_defaults.security_group_name, null) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.eks_managed_node_group_defaults.security_group_use_name_prefix, true) + security_group_description = try(each.value.security_group_description, var.eks_managed_node_group_defaults.security_group_description, "EKS managed node group security group") + vpc_id = try(each.value.vpc_id, var.eks_managed_node_group_defaults.vpc_id, var.vpc_id) + security_group_rules = try(each.value.security_group_rules, var.eks_managed_node_group_defaults.security_group_rules, {}) + security_group_tags = try(each.value.security_group_tags, var.eks_managed_node_group_defaults.security_group_tags, {}) + + tags = merge(var.tags, try(each.value.tags, var.eks_managed_node_group_defaults.tags, {})) +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +module "self_managed_node_group" { + source = "./modules/self-managed-node-group" + + for_each = { for k, v in var.self_managed_node_groups : k => v if var.create } + + create = try(each.value.create, true) + + cluster_name = aws_eks_cluster.this[0].name + cluster_ip_family = var.cluster_ip_family + + # Autoscaling Group + create_autoscaling_group = try(each.value.create_autoscaling_group, var.self_managed_node_group_defaults.create_autoscaling_group, true) + + name = try(each.value.name, each.key) + use_name_prefix = try(each.value.use_name_prefix, var.self_managed_node_group_defaults.use_name_prefix, true) + + availability_zones = try(each.value.availability_zones, var.self_managed_node_group_defaults.availability_zones, null) + subnet_ids = try(each.value.subnet_ids, var.self_managed_node_group_defaults.subnet_ids, var.subnet_ids) + + min_size = try(each.value.min_size, var.self_managed_node_group_defaults.min_size, 0) + max_size = try(each.value.max_size, var.self_managed_node_group_defaults.max_size, 3) + desired_size = try(each.value.desired_size, var.self_managed_node_group_defaults.desired_size, 1) + capacity_rebalance = try(each.value.capacity_rebalance, var.self_managed_node_group_defaults.capacity_rebalance, null) + min_elb_capacity = try(each.value.min_elb_capacity, var.self_managed_node_group_defaults.min_elb_capacity, null) + wait_for_elb_capacity = try(each.value.wait_for_elb_capacity, var.self_managed_node_group_defaults.wait_for_elb_capacity, null) + wait_for_capacity_timeout = try(each.value.wait_for_capacity_timeout, var.self_managed_node_group_defaults.wait_for_capacity_timeout, null) + default_cooldown = try(each.value.default_cooldown, var.self_managed_node_group_defaults.default_cooldown, null) + protect_from_scale_in = try(each.value.protect_from_scale_in, var.self_managed_node_group_defaults.protect_from_scale_in, null) + + target_group_arns = try(each.value.target_group_arns, var.self_managed_node_group_defaults.target_group_arns, []) + placement_group = try(each.value.placement_group, var.self_managed_node_group_defaults.placement_group, null) + health_check_type = try(each.value.health_check_type, var.self_managed_node_group_defaults.health_check_type, null) + health_check_grace_period = try(each.value.health_check_grace_period, var.self_managed_node_group_defaults.health_check_grace_period, null) + + force_delete = try(each.value.force_delete, var.self_managed_node_group_defaults.force_delete, null) + termination_policies = try(each.value.termination_policies, var.self_managed_node_group_defaults.termination_policies, []) + suspended_processes = try(each.value.suspended_processes, var.self_managed_node_group_defaults.suspended_processes, []) + max_instance_lifetime = try(each.value.max_instance_lifetime, var.self_managed_node_group_defaults.max_instance_lifetime, null) + + enabled_metrics = try(each.value.enabled_metrics, var.self_managed_node_group_defaults.enabled_metrics, []) + metrics_granularity = try(each.value.metrics_granularity, var.self_managed_node_group_defaults.metrics_granularity, null) + service_linked_role_arn = try(each.value.service_linked_role_arn, var.self_managed_node_group_defaults.service_linked_role_arn, null) + + initial_lifecycle_hooks = try(each.value.initial_lifecycle_hooks, var.self_managed_node_group_defaults.initial_lifecycle_hooks, []) + instance_refresh = try(each.value.instance_refresh, var.self_managed_node_group_defaults.instance_refresh, {}) + use_mixed_instances_policy = try(each.value.use_mixed_instances_policy, var.self_managed_node_group_defaults.use_mixed_instances_policy, false) + mixed_instances_policy = try(each.value.mixed_instances_policy, var.self_managed_node_group_defaults.mixed_instances_policy, null) + warm_pool = try(each.value.warm_pool, var.self_managed_node_group_defaults.warm_pool, {}) + + create_schedule = try(each.value.create_schedule, var.self_managed_node_group_defaults.create_schedule, false) + schedules = try(each.value.schedules, var.self_managed_node_group_defaults.schedules, {}) + + delete_timeout = try(each.value.delete_timeout, var.self_managed_node_group_defaults.delete_timeout, null) + use_default_tags = try(each.value.use_default_tags, var.self_managed_node_group_defaults.use_default_tags, false) + autoscaling_group_tags = try(each.value.autoscaling_group_tags, var.self_managed_node_group_defaults.autoscaling_group_tags, {}) + + # User data + platform = try(each.value.platform, var.self_managed_node_group_defaults.platform, "linux") + cluster_endpoint = try(aws_eks_cluster.this[0].endpoint, "") + cluster_auth_base64 = try(aws_eks_cluster.this[0].certificate_authority[0].data, "") + pre_bootstrap_user_data = try(each.value.pre_bootstrap_user_data, var.self_managed_node_group_defaults.pre_bootstrap_user_data, "") + post_bootstrap_user_data = try(each.value.post_bootstrap_user_data, var.self_managed_node_group_defaults.post_bootstrap_user_data, "") + bootstrap_extra_args = try(each.value.bootstrap_extra_args, var.self_managed_node_group_defaults.bootstrap_extra_args, "") + user_data_template_path = try(each.value.user_data_template_path, var.self_managed_node_group_defaults.user_data_template_path, "") + + # Launch Template + create_launch_template = try(each.value.create_launch_template, var.self_managed_node_group_defaults.create_launch_template, true) + launch_template_name = try(each.value.launch_template_name, var.self_managed_node_group_defaults.launch_template_name, each.key) + launch_template_use_name_prefix = try(each.value.launch_template_use_name_prefix, var.self_managed_node_group_defaults.launch_template_use_name_prefix, true) + launch_template_version = try(each.value.launch_template_version, var.self_managed_node_group_defaults.launch_template_version, null) + launch_template_description = try(each.value.launch_template_description, var.self_managed_node_group_defaults.launch_template_description, "Custom launch template for ${try(each.value.name, each.key)} self managed node group") + launch_template_tags = try(each.value.launch_template_tags, var.self_managed_node_group_defaults.launch_template_tags, {}) + + ebs_optimized = try(each.value.ebs_optimized, var.self_managed_node_group_defaults.ebs_optimized, null) + ami_id = try(each.value.ami_id, var.self_managed_node_group_defaults.ami_id, "") + cluster_version = try(each.value.cluster_version, var.self_managed_node_group_defaults.cluster_version, aws_eks_cluster.this[0].version) + instance_type = try(each.value.instance_type, var.self_managed_node_group_defaults.instance_type, "m6i.large") + key_name = try(each.value.key_name, var.self_managed_node_group_defaults.key_name, null) + + launch_template_default_version = try(each.value.launch_template_default_version, var.self_managed_node_group_defaults.launch_template_default_version, null) + update_launch_template_default_version = try(each.value.update_launch_template_default_version, var.self_managed_node_group_defaults.update_launch_template_default_version, true) + disable_api_termination = try(each.value.disable_api_termination, var.self_managed_node_group_defaults.disable_api_termination, null) + instance_initiated_shutdown_behavior = try(each.value.instance_initiated_shutdown_behavior, var.self_managed_node_group_defaults.instance_initiated_shutdown_behavior, null) + kernel_id = try(each.value.kernel_id, var.self_managed_node_group_defaults.kernel_id, null) + ram_disk_id = try(each.value.ram_disk_id, var.self_managed_node_group_defaults.ram_disk_id, null) + + block_device_mappings = try(each.value.block_device_mappings, var.self_managed_node_group_defaults.block_device_mappings, {}) + capacity_reservation_specification = try(each.value.capacity_reservation_specification, var.self_managed_node_group_defaults.capacity_reservation_specification, {}) + cpu_options = try(each.value.cpu_options, var.self_managed_node_group_defaults.cpu_options, {}) + credit_specification = try(each.value.credit_specification, var.self_managed_node_group_defaults.credit_specification, {}) + elastic_gpu_specifications = try(each.value.elastic_gpu_specifications, var.self_managed_node_group_defaults.elastic_gpu_specifications, {}) + elastic_inference_accelerator = try(each.value.elastic_inference_accelerator, var.self_managed_node_group_defaults.elastic_inference_accelerator, {}) + enclave_options = try(each.value.enclave_options, var.self_managed_node_group_defaults.enclave_options, {}) + hibernation_options = try(each.value.hibernation_options, var.self_managed_node_group_defaults.hibernation_options, {}) + instance_market_options = try(each.value.instance_market_options, var.self_managed_node_group_defaults.instance_market_options, {}) + license_specifications = try(each.value.license_specifications, var.self_managed_node_group_defaults.license_specifications, {}) + metadata_options = try(each.value.metadata_options, var.self_managed_node_group_defaults.metadata_options, local.metadata_options) + enable_monitoring = try(each.value.enable_monitoring, var.self_managed_node_group_defaults.enable_monitoring, true) + network_interfaces = try(each.value.network_interfaces, var.self_managed_node_group_defaults.network_interfaces, []) + placement = try(each.value.placement, var.self_managed_node_group_defaults.placement, {}) + + # IAM role + create_iam_instance_profile = try(each.value.create_iam_instance_profile, var.self_managed_node_group_defaults.create_iam_instance_profile, true) + iam_instance_profile_arn = try(each.value.iam_instance_profile_arn, var.self_managed_node_group_defaults.iam_instance_profile_arn, null) + iam_role_name = try(each.value.iam_role_name, var.self_managed_node_group_defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.self_managed_node_group_defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.self_managed_node_group_defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.self_managed_node_group_defaults.iam_role_description, "Self managed node group IAM role") + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.self_managed_node_group_defaults.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, var.self_managed_node_group_defaults.iam_role_tags, {}) + iam_role_attach_cni_policy = try(each.value.iam_role_attach_cni_policy, var.self_managed_node_group_defaults.iam_role_attach_cni_policy, true) + iam_role_additional_policies = try(each.value.iam_role_additional_policies, var.self_managed_node_group_defaults.iam_role_additional_policies, []) + + # Security group + vpc_security_group_ids = compact(concat([local.node_security_group_id], try(each.value.vpc_security_group_ids, var.self_managed_node_group_defaults.vpc_security_group_ids, []))) + cluster_security_group_id = local.cluster_security_group_id + cluster_primary_security_group_id = try(each.value.attach_cluster_primary_security_group, var.self_managed_node_group_defaults.attach_cluster_primary_security_group, false) ? aws_eks_cluster.this[0].vpc_config[0].cluster_security_group_id : null + create_security_group = try(each.value.create_security_group, var.self_managed_node_group_defaults.create_security_group, true) + security_group_name = try(each.value.security_group_name, var.self_managed_node_group_defaults.security_group_name, null) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.self_managed_node_group_defaults.security_group_use_name_prefix, true) + security_group_description = try(each.value.security_group_description, var.self_managed_node_group_defaults.security_group_description, "Self managed node group security group") + vpc_id = try(each.value.vpc_id, var.self_managed_node_group_defaults.vpc_id, var.vpc_id) + security_group_rules = try(each.value.security_group_rules, var.self_managed_node_group_defaults.security_group_rules, {}) + security_group_tags = try(each.value.security_group_tags, var.self_managed_node_group_defaults.security_group_tags, {}) + + tags = merge(var.tags, try(each.value.tags, var.self_managed_node_group_defaults.tags, {})) +} diff --git a/modules/aws-eks/outputs.tf b/modules/aws-eks/outputs.tf new file mode 100644 index 0000000..8ee139c --- /dev/null +++ b/modules/aws-eks/outputs.tf @@ -0,0 +1,223 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = try(aws_eks_cluster.this[0].arn, "") +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = try(aws_eks_cluster.this[0].certificate_authority[0].data, "") +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = try(aws_eks_cluster.this[0].endpoint, "") +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = try(aws_eks_cluster.this[0].id, "") +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = try(aws_eks_cluster.this[0].identity[0].oidc[0].issuer, "") +} + +output "cluster_version" { + description = "The Kubernetes version for the cluster" + value = try(aws_eks_cluster.this[0].version, "") +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = try(aws_eks_cluster.this[0].platform_version, "") +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = try(aws_eks_cluster.this[0].status, "") +} + +output "cluster_primary_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = try(aws_eks_cluster.this[0].vpc_config[0].cluster_security_group_id, "") +} + +################################################################################ +# KMS Key +################################################################################ + +output "kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.kms.key_arn +} + +output "kms_key_id" { + description = "The globally unique identifier for the key" + value = module.kms.key_id +} + +output "kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.kms.key_policy +} + +################################################################################ +# Cluster Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = try(aws_security_group.cluster[0].arn, "") +} + +output "cluster_security_group_id" { + description = "ID of the cluster security group" + value = try(aws_security_group.cluster[0].id, "") +} + +################################################################################ +# Node Security Group +################################################################################ + +output "node_security_group_arn" { + description = "Amazon Resource Name (ARN) of the node shared security group" + value = try(aws_security_group.node[0].arn, "") +} + +output "node_security_group_id" { + description = "ID of the node shared security group" + value = try(aws_security_group.node[0].id, "") +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider" { + description = "The OpenID Connect identity provider (issuer URL without leading `https://`)" + value = try(replace(aws_eks_cluster.this[0].identity[0].oidc[0].issuer, "https://", ""), "") +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = try(aws_iam_openid_connect_provider.oidc_provider[0].arn, "") +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = try(aws_iam_role.this[0].name, "") +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = try(aws_iam_role.this[0].arn, "") +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = aws_eks_addon.this +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = aws_eks_identity_provider_config.this +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, "") +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, "") +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.fargate_profile +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +output "eks_managed_node_groups" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks_managed_node_group +} + +output "eks_managed_node_groups_iam_role_arn" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.eks_managed_node_group["linux"].iam_role_arn +} + +output "eks_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by EKS managed node groups" + value = compact(flatten([for group in module.eks_managed_node_group : group.node_group_autoscaling_group_names])) +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +output "self_managed_node_groups" { + description = "Map of attribute maps for all self managed node groups created" + value = module.self_managed_node_group +} + +output "self_managed_node_groups_autoscaling_group_names" { + description = "List of the autoscaling group names created by self-managed node groups" + value = compact([for group in module.self_managed_node_group : group.autoscaling_group_name]) +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "[DEPRECATED - use `var.manage_aws_auth_configmap`] Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = templatefile("${path.module}/templates/aws_auth_cm.tpl", + { + eks_managed_role_arns = distinct(compact([for group in module.eks_managed_node_group : group.iam_role_arn])) + self_managed_role_arns = distinct(compact([for group in module.self_managed_node_group : group.iam_role_arn if group.platform != "windows"])) + win32_self_managed_role_arns = distinct(compact([for group in module.self_managed_node_group : group.iam_role_arn if group.platform == "windows"])) + fargate_profile_pod_execution_role_arns = distinct(compact([for group in module.fargate_profile : group.fargate_profile_pod_execution_role_arn])) + } + ) +} + +output "cluster_name" { + description = "Name of the EKS cluster" + value = var.cluster_name +} \ No newline at end of file diff --git a/modules/aws-eks/templates/aws_auth_cm.tpl b/modules/aws-eks/templates/aws_auth_cm.tpl new file mode 100644 index 0000000..73a898e --- /dev/null +++ b/modules/aws-eks/templates/aws_auth_cm.tpl @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: aws-auth + namespace: kube-system +data: + mapRoles: | +%{ for role in eks_managed_role_arns ~} + - rolearn: ${role} + username: system:node:{{EC2PrivateDNSName}} + groups: + - system:bootstrappers + - system:nodes +%{ endfor ~} +%{ for role in self_managed_role_arns ~} + - rolearn: ${role} + username: system:node:{{EC2PrivateDNSName}} + groups: + - system:bootstrappers + - system:nodes +%{ endfor ~} +%{ for role in win32_self_managed_role_arns ~} + - rolearn: ${role} + username: system:node:{{EC2PrivateDNSName}} + groups: + - eks:kube-proxy-windows + - system:bootstrappers + - system:nodes +%{ endfor ~} +%{ for role in fargate_profile_pod_execution_role_arns ~} + - rolearn: ${role} + username: system:node:{{SessionName}} + groups: + - system:bootstrappers + - system:nodes + - system:node-proxier +%{ endfor ~} diff --git a/modules/aws-eks/templates/bottlerocket_user_data.tpl b/modules/aws-eks/templates/bottlerocket_user_data.tpl new file mode 100644 index 0000000..640c801 --- /dev/null +++ b/modules/aws-eks/templates/bottlerocket_user_data.tpl @@ -0,0 +1,7 @@ +%{ if enable_bootstrap_user_data ~} +[settings.kubernetes] +"cluster-name" = "${cluster_name}" +"api-server" = "${cluster_endpoint}" +"cluster-certificate" = "${cluster_auth_base64}" +%{ endif ~} +${bootstrap_extra_args ~} diff --git a/modules/aws-eks/templates/linux_user_data.tpl b/modules/aws-eks/templates/linux_user_data.tpl new file mode 100644 index 0000000..14acbd2 --- /dev/null +++ b/modules/aws-eks/templates/linux_user_data.tpl @@ -0,0 +1,14 @@ +%{ if enable_bootstrap_user_data ~} +#!/bin/bash +set -e +%{ endif ~} +${pre_bootstrap_user_data ~} +%{ if length(cluster_service_ipv4_cidr) > 0 ~} +export SERVICE_IPV4_CIDR=${cluster_service_ipv4_cidr} +%{ endif ~} +%{ if enable_bootstrap_user_data ~} +B64_CLUSTER_CA=${cluster_auth_base64} +API_SERVER_URL=${cluster_endpoint} +/etc/eks/bootstrap.sh ${cluster_name} ${bootstrap_extra_args} --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL +${post_bootstrap_user_data ~} +%{ endif ~} diff --git a/modules/aws-eks/templates/windows_user_data.tpl b/modules/aws-eks/templates/windows_user_data.tpl new file mode 100644 index 0000000..5000850 --- /dev/null +++ b/modules/aws-eks/templates/windows_user_data.tpl @@ -0,0 +1,9 @@ + +${pre_bootstrap_user_data ~} +[string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS" +[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1' +[string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName" +& $EKSBootstrapScriptFile -EKSClusterName ${cluster_name} -APIServerEndpoint ${cluster_endpoint} -Base64ClusterCA ${cluster_auth_base64} ${bootstrap_extra_args} 3>&1 4>&1 5>&1 6>&1 +$LastError = if ($?) { 0 } else { $Error[0].Exception.HResult } +${post_bootstrap_user_data ~} + diff --git a/modules/aws-eks/variables.tf b/modules/aws-eks/variables.tf new file mode 100644 index 0000000..5b05540 --- /dev/null +++ b/modules/aws-eks/variables.tf @@ -0,0 +1,574 @@ +variable "create" { + description = "Controls if EKS resources should be created (affects nearly all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "prefix_separator" { + description = "The separator to use between the prefix and the generated timestamp for resource names" + type = string + default = "-" +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "" +} + +variable "cluster_version" { + description = "Kubernetes `.` version to use for the EKS cluster (i.e.: `1.22`)" + type = string + default = null +} + +variable "cluster_enabled_log_types" { + description = "A list of the desired control plane logs to enable. For more information, see Amazon EKS Control Plane Logging documentation (https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html)" + type = list(string) + default = ["audit", "api", "authenticator"] +} + +variable "cluster_additional_security_group_ids" { + description = "List of additional, externally created security group IDs to attach to the cluster control plane" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "A list of subnet IDs where the nodes/node groups will be provisioned." + type = list(string) + default = [] +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +variable "cluster_endpoint_private_access" { + description = "Indicates whether or not the Amazon EKS private API server endpoint is enabled" + type = bool + default = false +} + +variable "cluster_endpoint_public_access" { + description = "Indicates whether or not the Amazon EKS public API server endpoint is enabled" + type = bool + default = true +} + +variable "cluster_endpoint_public_access_cidrs" { + description = "List of CIDR blocks which can access the Amazon EKS public API server endpoint" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "cluster_ip_family" { + description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`. You can only specify an IP family when you create a cluster, changing this value will force a new cluster to be created" + type = string + default = null +} + +variable "cluster_service_ipv4_cidr" { + description = "The CIDR block to assign Kubernetes service IP addresses from. If you don't specify a block, Kubernetes assigns addresses from either the 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks" + type = string + default = null +} + +variable "cluster_encryption_config" { + description = "Configuration block with encryption configuration for the cluster" + type = list(any) + default = [] +} + +variable "attach_cluster_encryption_policy" { + description = "Indicates whether or not to attach an additional policy for the cluster IAM role to utilize the encryption key provided" + type = bool + default = true +} + +variable "cluster_tags" { + description = "A map of additional tags to add to the cluster" + type = map(string) + default = {} +} + +variable "create_cluster_primary_security_group_tags" { + description = "Indicates whether or not to tag the cluster's primary security group. This security group is created by the EKS service, not the module, and therefore tagging is handled after cluster creation" + type = bool + default = true +} + +variable "cluster_timeouts" { + description = "Create, update, and delete timeout configurations for the cluster" + type = map(string) + default = {} +} + +################################################################################ +# KMS Key +################################################################################ + +variable "create_kms_key" { + description = "Controls if a KMS key for cluster encryption should be created" + type = bool + default = false +} + +variable "kms_key_description" { + description = "The description of the key as viewed in AWS console" + type = string + default = null +} + +variable "kms_key_deletion_window_in_days" { + description = "The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30`" + type = number + default = null +} + +variable "enable_kms_key_rotation" { + description = "Specifies whether key rotation is enabled. Defaults to `true`" + type = bool + default = true +} + +variable "kms_key_enable_default_policy" { + description = "Specifies whether to enable the default key policy. Defaults to `false`" + type = bool + default = false +} + +variable "kms_key_owners" { + description = "A list of IAM ARNs for those who will have full key permissions (`kms:*`)" + type = list(string) + default = [] +} + +variable "kms_key_administrators" { + description = "A list of IAM ARNs for [key administrators](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators). If no value is provided, the current caller identity is used to ensure at least one key admin is available" + type = list(string) + default = [] +} + +variable "kms_key_users" { + description = "A list of IAM ARNs for [key users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users)" + type = list(string) + default = [] +} + +variable "kms_key_service_users" { + description = "A list of IAM ARNs for [key service users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration)" + type = list(string) + default = [] +} + +variable "kms_key_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "kms_key_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "kms_key_aliases" { + description = "A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values" + type = list(string) + default = [] +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events. Default retention - 90 days" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +################################################################################ +# Cluster Security Group +################################################################################ + +variable "create_cluster_security_group" { + description = "Determines if a security group is created for the cluster or use the existing `cluster_security_group_id`" + type = bool + default = true +} + +variable "cluster_security_group_id" { + description = "Existing security group ID to be attached to the cluster. Required if `create_cluster_security_group` = `false`" + type = string + default = "" +} + +variable "vpc_id" { + description = "ID of the VPC where the cluster and its nodes will be provisioned" + type = string + default = null +} + +variable "cluster_security_group_name" { + description = "Name to use on cluster security group created" + type = string + default = null +} + +variable "cluster_security_group_use_name_prefix" { + description = "Determines whether cluster security group name (`cluster_security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "cluster_security_group_description" { + description = "Description of the cluster security group created" + type = string + default = "EKS cluster security group" +} + +variable "cluster_security_group_additional_rules" { + description = "List of additional security group rules to add to the cluster security group created. Set `source_node_security_group = true` inside rules to set the `node_security_group` as source" + type = any + default = {} +} + +variable "cluster_security_group_tags" { + description = "A map of additional tags to add to the cluster security group created" + type = map(string) + default = {} +} + +################################################################################ +# EKS IPV6 CNI Policy +################################################################################ + +variable "create_cni_ipv6_iam_policy" { + description = "Determines whether to create an [`AmazonEKS_CNI_IPv6_Policy`](https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy)" + type = bool + default = false +} + +################################################################################ +# Node Security Group +################################################################################ + +variable "create_node_security_group" { + description = "Determines whether to create a security group for the node groups or use the existing `node_security_group_id`" + type = bool + default = true +} + +variable "node_security_group_id" { + description = "ID of an existing security group to attach to the node groups created" + type = string + default = "" +} + +variable "node_security_group_name" { + description = "Name to use on node security group created" + type = string + default = null +} + +variable "node_security_group_use_name_prefix" { + description = "Determines whether node security group name (`node_security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "node_security_group_description" { + description = "Description of the node security group created" + type = string + default = "EKS node shared security group" +} + +variable "node_security_group_additional_rules" { + description = "List of additional security group rules to add to the node security group created. Set `source_cluster_security_group = true` inside rules to set the `cluster_security_group` as source" + type = any + default = {} +} + +variable "node_security_group_tags" { + description = "A map of additional tags to add to the node security group created" + type = map(string) + default = {} +} + +# TODO - at next breaking change, make 169.254.169.123/32 the default +variable "node_security_group_ntp_ipv4_cidr_block" { + description = "IPv4 CIDR block to allow NTP egress. Default is public IP space, but [Amazon Time Sync Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html) can be used as well with `[\"169.254.169.123/32\"]`" + type = list(string) + default = ["0.0.0.0/0"] +} + +# TODO - at next breaking change, make fd00:ec2::123/128 the default +variable "node_security_group_ntp_ipv6_cidr_block" { + description = "IPv4 CIDR block to allow NTP egress. Default is public IP space, but [Amazon Time Sync Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html) can be used as well with `[\"fd00:ec2::123/128\"]`" + type = list(string) + default = ["::/0"] +} + +################################################################################ +# IRSA +################################################################################ + +variable "enable_irsa" { + description = "Determines whether to create an OpenID Connect Provider for EKS to enable IRSA" + type = bool + default = true +} + +variable "openid_connect_audiences" { + description = "List of OpenID Connect audience client IDs to add to the IRSA provider" + type = list(string) + default = [] +} + +variable "custom_oidc_thumbprints" { + description = "Additional list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s)" + type = list(string) + default = [] +} + +################################################################################ +# Cluster IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether a an IAM role is created or to use an existing IAM role" + type = bool + default = true +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN for the cluster. Required if `create_iam_role` is set to `false`" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "Cluster IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = list(string) + default = [] +} + +variable "cluster_iam_role_dns_suffix" { + description = "Base DNS domain name for the current partition (e.g., amazonaws.com in AWS Commercial, amazonaws.com.cn in AWS China)" + type = string + default = null +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "cluster_encryption_policy_use_name_prefix" { + description = "Determines whether cluster encryption policy name (`cluster_encryption_policy_name`) is used as a prefix" + type = bool + default = true +} + +variable "cluster_encryption_policy_name" { + description = "Name to use on cluster encryption policy created" + type = string + default = null +} + +variable "cluster_encryption_policy_description" { + description = "Description of the cluster encryption policy created" + type = string + default = "Cluster encryption policy to allow cluster role to utilize CMK provided" +} + +variable "cluster_encryption_policy_path" { + description = "Cluster encryption policy path" + type = string + default = null +} + +variable "cluster_encryption_policy_tags" { + description = "A map of additional tags to add to the cluster encryption policy created" + type = map(string) + default = {} +} + +################################################################################ +# EKS Addons +################################################################################ + +variable "cluster_addons" { + description = "Map of cluster addon configurations to enable for the cluster. Addon name can be the map keys or set with `name`" + type = any + default = {} +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +variable "cluster_identity_providers" { + description = "Map of cluster identity provider configurations to enable for the cluster. Note - this is different/separate from IRSA" + type = any + default = {} +} + +################################################################################ +# Fargate +################################################################################ + +variable "fargate_profiles" { + description = "Map of Fargate Profile definitions to create" + type = any + default = {} +} + +variable "fargate_profile_defaults" { + description = "Map of Fargate Profile default configurations" + type = any + default = {} +} + +################################################################################ +# Self Managed Node Group +################################################################################ + +variable "self_managed_node_groups" { + description = "Map of self-managed node group definitions to create" + type = any + default = {} +} + +variable "self_managed_node_group_defaults" { + description = "Map of self-managed node group default configurations" + type = any + default = {} +} + +################################################################################ +# EKS Managed Node Group +################################################################################ + +variable "eks_managed_node_groups" { + description = "Map of EKS managed node group definitions to create" + type = any + default = {} +} + +variable "eks_managed_node_group_defaults" { + description = "Map of EKS managed node group default configurations" + type = any + default = {} +} + + +################################################################################ +# aws-auth configmap +################################################################################ + +variable "manage_aws_auth_configmap" { + description = "Determines whether to manage the aws-auth configmap" + type = bool + default = false +} + +variable "create_aws_auth_configmap" { + description = "Determines whether to create the aws-auth configmap. NOTE - this is only intended for scenarios where the configmap does not exist (i.e. - when using only self-managed node groups). Most users should use `manage_aws_auth_configmap`" + type = bool + default = false +} + +variable "aws_auth_node_iam_role_arns_non_windows" { + description = "List of non-Windows based node IAM role ARNs to add to the aws-auth configmap" + type = list(string) + default = [] +} + +variable "aws_auth_node_iam_role_arns_windows" { + description = "List of Windows based node IAM role ARNs to add to the aws-auth configmap" + type = list(string) + default = [] +} + +variable "aws_auth_fargate_profile_pod_execution_role_arns" { + description = "List of Fargate profile pod execution role ARNs to add to the aws-auth configmap" + type = list(string) + default = [] +} + +variable "aws_auth_roles" { + description = "List of role maps to add to the aws-auth configmap" + type = list(any) + default = [] +} + +variable "aws_auth_users" { + description = "List of user maps to add to the aws-auth configmap" + type = list(any) + default = [] +} + +variable "aws_auth_accounts" { + description = "List of account maps to add to the aws-auth configmap" + type = list(any) + default = [] +} diff --git a/modules/aws-eks/versions.tf b/modules/aws-eks/versions.tf new file mode 100644 index 0000000..fde7af0 --- /dev/null +++ b/modules/aws-eks/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + } +} diff --git a/modules/aws-elasticache-redis/README.md b/modules/aws-elasticache-redis/README.md new file mode 100644 index 0000000..793d5cd --- /dev/null +++ b/modules/aws-elasticache-redis/README.md @@ -0,0 +1,259 @@ +# terraform-aws-elasticache-redis + +A Terraform module to create an AWS Redis ElastiCache cluster + +## Terraform versions + +- For AWS Provider >= 4.0.0, pin module version to `~> v3.0`. +- For Terraform 0.14.5 and higher *and* AWS Provider < 4.0.0, pin module version to `~> v2.0`. +- For Terraform 0.12 to 0.14.4, pin module version to `~> v1.0`. +- Submit pull-requests to `main` branch. + +## Usage + +```hcl +module "redis" { + version = "~> 3.0.0" + + name_prefix = "core-example" + num_cache_clusters = 2 + node_type = "cache.t4g.small" + + engine_version = "6.x" + port = 6379 + maintenance_window = "mon:03:00-mon:04:00" + snapshot_window = "04:00-06:00" + snapshot_retention_limit = 7 + + automatic_failover_enabled = true + + at_rest_encryption_enabled = true + transit_encryption_enabled = true + auth_token = "1234567890asdfghjkl" + + apply_immediately = true + family = "redis6.x" + description = "Test elasticache redis." + + subnet_id_names = "*cache*" # subnet tag.Name, you can use regex as given + vpc_id = module.vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + + parameter = [ + { + name = "repl-backlog-size" + value = "16384" + } + ] + + log_delivery_configuration = [ + { + destination_type = "cloudwatch-logs" + destination = "aws_cloudwatch_log_group.example.name" + log_format = "json" + log_type = "engine-log" + } + ] + + tags = { + Project = "Test" + } +} +``` + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.11 | +| [aws](#requirement\_aws) | >= 4.8.0, < 5.0.0 | +| [random](#requirement\_random) | >= 3.3.2, < 4.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.8.0, < 5.0.0 | +| [random](#provider\_random) | >= 3.3.2, < 4.0.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_elasticache_parameter_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_parameter_group) | resource | +| [aws_elasticache_replication_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group) | resource | +| [aws_elasticache_subnet_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource | +| [aws_security_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.other_sg_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_ingress_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_ingress_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [random_id.redis_pg](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allowed\_security\_groups](#input\_allowed\_security\_groups) | List of existing security groups that will be allowed ingress via the elaticache security group rules | `list(string)` | `[]` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any modifications are applied immediately, or during the next maintenance window. | `bool` | `false` | no | +| [at\_rest\_encryption\_enabled](#input\_at\_rest\_encryption\_enabled) | Whether to enable encryption at rest. | `bool` | `true` | no | +| [auth\_token](#input\_auth\_token) | The password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true`. | `string` | `""` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | n/a | `string` | `true` | no | +| [automatic\_failover\_enabled](#input\_automatic\_failover\_enabled) | Specifies whether a read-only replica will be automatically promoted to read/write primary if the existing primary fails. If enabled, number\_cache\_clusters must be greater than 1. Must be enabled for Redis (cluster mode enabled) replication groups. | `bool` | `true` | no | +| [cluster\_mode\_enabled](#input\_cluster\_mode\_enabled) | Enable creation of a native redis cluster. | `bool` | `false` | no | +| [data\_tiering\_enabled](#input\_data\_tiering\_enabled) | Enables data tiering. Data tiering is only supported for replication groups using the r6gd node type. This parameter must be set to true when using r6gd nodes. | `bool` | `false` | no | +| [description](#input\_description) | The description of the all resources. | `string` | `"Managed by Terraform"` | no | +| [engine\_version](#input\_engine\_version) | The version number of the cache engine to be used for the cache clusters in this replication group. | `string` | `"6.x"` | no | +| [family](#input\_family) | The family of the ElastiCache parameter group. | `string` | `"redis6.x"` | no | +| [final\_snapshot\_identifier](#input\_final\_snapshot\_identifier) | The name of your final node group (shard) snapshot. ElastiCache creates the snapshot from the primary node in the cluster. If omitted, no final snapshot will be made. | `string` | `null` | no | +| [global\_replication\_group\_id](#input\_global\_replication\_group\_id) | The ID of the global replication group to which this replication group should belong. | `string` | `null` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of Ingress CIDR blocks. | `list(string)` | `[]` | no | +| [ingress\_self](#input\_ingress\_self) | Specify whether the security group itself will be added as a source to the ingress rule. | `bool` | `false` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true` | `string` | `""` | no | +| [log\_delivery\_configuration](#input\_log\_delivery\_configuration) | Log Delivery configuration for the cluster. |
list(object({
destination_type = string
destination = string
log_format = string
log_type = string
}))
| `[]` | no | +| [maintenance\_window](#input\_maintenance\_window) | Specifies the weekly time range for when maintenance on the cache cluster is performed. | `string` | `""` | no | +| [multi\_az\_enabled](#input\_multi\_az\_enabled) | Specifies whether to enable Multi-AZ Support for the replication group. If true, `automatic_failover_enabled` must also be enabled. Defaults to false. | `string` | `false` | no | +| [name\_prefix](#input\_name\_prefix) | The replication group identifier. This parameter is stored as a lowercase string. | `string` | n/a | yes | +| [node\_type](#input\_node\_type) | The compute and memory capacity of the nodes in the node group. | `string` | n/a | yes | +| [notification\_topic\_arn](#input\_notification\_topic\_arn) | An Amazon Resource Name (ARN) of an SNS topic to send ElastiCache notifications to. Example: `arn:aws:sns:us-east-1:012345678999:my_sns_topic` | `string` | `""` | no | +| [num\_cache\_clusters](#input\_num\_cache\_clusters) | The number of cache clusters (primary and replicas) this replication group will have. If Multi-AZ is enabled, the value of this parameter must be at least 2. Updates will occur before other modifications. Conflicts with num\_node\_groups. | `number` | `1` | no | +| [num\_node\_groups](#input\_num\_node\_groups) | Specify the number of node groups (shards) for this Redis replication group. Changing this number will trigger an online resizing operation before other settings modifications. | `number` | `0` | no | +| [parameter](#input\_parameter) | A list of Redis parameters to apply. Note that parameters may differ from one Redis family to another |
list(object({
name = string
value = string
}))
| `[]` | no | +| [port](#input\_port) | The port number on which each of the cache nodes will accept connections. | `number` | `6379` | no | +| [preferred\_cache\_cluster\_azs](#input\_preferred\_cache\_cluster\_azs) | A list of EC2 availability zones in which the replication group's cache clusters will be created. The order of the availability zones in the list is not important. | `list(string)` | `null` | no | +| [replicas\_per\_node\_group](#input\_replicas\_per\_node\_group) | Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will trigger an online resizing operation before other settings modifications. | `number` | `0` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of Security Groups. | `list(string)` | `[]` | no | +| [snapshot\_retention\_limit](#input\_snapshot\_retention\_limit) | The number of days for which ElastiCache will retain automatic cache cluster snapshots before deleting them. | `number` | `30` | no | +| [snapshot\_window](#input\_snapshot\_window) | The daily time range (in UTC) during which ElastiCache will begin taking a daily snapshot of your cache cluster. | `string` | `""` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of VPC Subnet IDs for the cache subnet group. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | A mapping of tags to assign to all resources. | `map(string)` | `{}` | no | +| [transit\_encryption\_enabled](#input\_transit\_encryption\_enabled) | Whether to enable encryption in transit. | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | VPC Id to associate with Redis ElastiCache. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [elasticache\_auth\_token](#output\_elasticache\_auth\_token) | The Redis Auth Token. | +| [elasticache\_parameter\_group\_id](#output\_elasticache\_parameter\_group\_id) | The ElastiCache parameter group name. | +| [elasticache\_port](#output\_elasticache\_port) | The Redis port. | +| [elasticache\_replication\_group\_arn](#output\_elasticache\_replication\_group\_arn) | The Amazon Resource Name (ARN) of the created ElastiCache Replication Group. | +| [elasticache\_replication\_group\_id](#output\_elasticache\_replication\_group\_id) | The ID of the ElastiCache Replication Group. | +| [elasticache\_replication\_group\_member\_clusters](#output\_elasticache\_replication\_group\_member\_clusters) | The identifiers of all the nodes that are part of this replication group. | +| [elasticache\_replication\_group\_primary\_endpoint\_address](#output\_elasticache\_replication\_group\_primary\_endpoint\_address) | The address of the endpoint for the primary node in the replication group. | +| [elasticache\_replication\_group\_reader\_endpoint\_address](#output\_elasticache\_replication\_group\_reader\_endpoint\_address) | The address of the endpoint for the reader node in the replication group. | +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the Redis ElastiCache security group. | +| [security\_group\_description](#output\_security\_group\_description) | The description of the Redis ElastiCache security group. | +| [security\_group\_egress](#output\_security\_group\_egress) | The egress rules of the Redis ElastiCache security group. | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the Redis ElastiCache security group. | +| [security\_group\_ingress](#output\_security\_group\_ingress) | The ingress rules of the Redis ElastiCache security group. | +| [security\_group\_name](#output\_security\_group\_name) | The name of the Redis ElastiCache security group. | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID of the Redis ElastiCache security group. | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID of the Redis ElastiCache security group. | + + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.11 | +| [aws](#requirement\_aws) | >= 4.8.0, < 5.0.0 | +| [random](#requirement\_random) | >= 3.3.2, < 4.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.8.0, < 5.0.0 | +| [random](#provider\_random) | >= 3.3.2, < 4.0.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_elasticache_parameter_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_parameter_group) | resource | +| [aws_elasticache_replication_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group) | resource | +| [aws_elasticache_subnet_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource | +| [aws_security_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.other_sg_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_ingress_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.redis_ingress_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [random_id.redis_pg](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [aws_subnet_ids.private_subnets_with_cache_tag](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allowed\_security\_groups](#input\_allowed\_security\_groups) | List of existing security groups that will be allowed ingress via the elaticache security group rules | `list(string)` | `[]` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any modifications are applied immediately, or during the next maintenance window. | `bool` | `false` | no | +| [at\_rest\_encryption\_enabled](#input\_at\_rest\_encryption\_enabled) | Whether to enable encryption at rest. | `bool` | `true` | no | +| [auth\_token](#input\_auth\_token) | The password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true`. | `string` | `""` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | n/a | `string` | `true` | no | +| [automatic\_failover\_enabled](#input\_automatic\_failover\_enabled) | Specifies whether a read-only replica will be automatically promoted to read/write primary if the existing primary fails. If enabled, number\_cache\_clusters must be greater than 1. Must be enabled for Redis (cluster mode enabled) replication groups. | `bool` | `true` | no | +| [cluster\_mode\_enabled](#input\_cluster\_mode\_enabled) | Enable creation of a native redis cluster. | `bool` | `false` | no | +| [data\_tiering\_enabled](#input\_data\_tiering\_enabled) | Enables data tiering. Data tiering is only supported for replication groups using the r6gd node type. This parameter must be set to true when using r6gd nodes. | `bool` | `false` | no | +| [description](#input\_description) | The description of the all resources. | `string` | `"Managed by Terraform"` | no | +| [engine\_version](#input\_engine\_version) | The version number of the cache engine to be used for the cache clusters in this replication group. | `string` | `"6.x"` | no | +| [family](#input\_family) | The family of the ElastiCache parameter group. | `string` | `"redis6.x"` | no | +| [final\_snapshot\_identifier](#input\_final\_snapshot\_identifier) | The name of your final node group (shard) snapshot. ElastiCache creates the snapshot from the primary node in the cluster. If omitted, no final snapshot will be made. | `string` | `null` | no | +| [global\_replication\_group\_id](#input\_global\_replication\_group\_id) | The ID of the global replication group to which this replication group should belong. | `string` | `null` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of Ingress CIDR blocks. | `list(string)` | `[]` | no | +| [ingress\_self](#input\_ingress\_self) | Specify whether the security group itself will be added as a source to the ingress rule. | `bool` | `false` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true` | `string` | `""` | no | +| [log\_delivery\_configuration](#input\_log\_delivery\_configuration) | Log Delivery configuration for the cluster. |
list(object({
destination_type = string
destination = string
log_format = string
log_type = string
}))
| `[]` | no | +| [maintenance\_window](#input\_maintenance\_window) | Specifies the weekly time range for when maintenance on the cache cluster is performed. | `string` | `""` | no | +| [multi\_az\_enabled](#input\_multi\_az\_enabled) | Specifies whether to enable Multi-AZ Support for the replication group. If true, `automatic_failover_enabled` must also be enabled. Defaults to false. | `string` | `false` | no | +| [name\_prefix](#input\_name\_prefix) | The replication group identifier. This parameter is stored as a lowercase string. | `string` | n/a | yes | +| [node\_type](#input\_node\_type) | The compute and memory capacity of the nodes in the node group. | `string` | n/a | yes | +| [notification\_topic\_arn](#input\_notification\_topic\_arn) | An Amazon Resource Name (ARN) of an SNS topic to send ElastiCache notifications to. Example: `arn:aws:sns:us-east-1:012345678999:my_sns_topic` | `string` | `""` | no | +| [num\_cache\_clusters](#input\_num\_cache\_clusters) | The number of cache clusters (primary and replicas) this replication group will have. If Multi-AZ is enabled, the value of this parameter must be at least 2. Updates will occur before other modifications. Conflicts with num\_node\_groups. | `number` | `1` | no | +| [num\_node\_groups](#input\_num\_node\_groups) | Specify the number of node groups (shards) for this Redis replication group. Changing this number will trigger an online resizing operation before other settings modifications. | `number` | `0` | no | +| [parameter](#input\_parameter) | A list of Redis parameters to apply. Note that parameters may differ from one Redis family to another |
list(object({
name = string
value = string
}))
| `[]` | no | +| [port](#input\_port) | The port number on which each of the cache nodes will accept connections. | `number` | `6379` | no | +| [preferred\_cache\_cluster\_azs](#input\_preferred\_cache\_cluster\_azs) | A list of EC2 availability zones in which the replication group's cache clusters will be created. The order of the availability zones in the list is not important. | `list(string)` | `null` | no | +| [replicas\_per\_node\_group](#input\_replicas\_per\_node\_group) | Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will trigger an online resizing operation before other settings modifications. | `number` | `0` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of Security Groups. | `list(string)` | `[]` | no | +| [snapshot\_retention\_limit](#input\_snapshot\_retention\_limit) | The number of days for which ElastiCache will retain automatic cache cluster snapshots before deleting them. | `number` | `30` | no | +| [snapshot\_window](#input\_snapshot\_window) | The daily time range (in UTC) during which ElastiCache will begin taking a daily snapshot of your cache cluster. | `string` | `""` | no | +| [subnet\_id\_names](#input\_subnet\_id\_names) | name of subnet ID's | `string` | `"*"` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of VPC Subnet IDs for the cache subnet group. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | A mapping of tags to assign to all resources. | `map(string)` | `{}` | no | +| [transit\_encryption\_enabled](#input\_transit\_encryption\_enabled) | Whether to enable encryption in transit. | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | VPC Id to associate with Redis ElastiCache. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [elasticache\_auth\_token](#output\_elasticache\_auth\_token) | The Redis Auth Token. | +| [elasticache\_parameter\_group\_id](#output\_elasticache\_parameter\_group\_id) | The ElastiCache parameter group name. | +| [elasticache\_port](#output\_elasticache\_port) | The Redis port. | +| [elasticache\_replication\_group\_arn](#output\_elasticache\_replication\_group\_arn) | The Amazon Resource Name (ARN) of the created ElastiCache Replication Group. | +| [elasticache\_replication\_group\_id](#output\_elasticache\_replication\_group\_id) | The ID of the ElastiCache Replication Group. | +| [elasticache\_replication\_group\_member\_clusters](#output\_elasticache\_replication\_group\_member\_clusters) | The identifiers of all the nodes that are part of this replication group. | +| [elasticache\_replication\_group\_primary\_endpoint\_address](#output\_elasticache\_replication\_group\_primary\_endpoint\_address) | The address of the endpoint for the primary node in the replication group. | +| [elasticache\_replication\_group\_reader\_endpoint\_address](#output\_elasticache\_replication\_group\_reader\_endpoint\_address) | The address of the endpoint for the reader node in the replication group. | +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the Redis ElastiCache security group. | +| [security\_group\_description](#output\_security\_group\_description) | The description of the Redis ElastiCache security group. | +| [security\_group\_egress](#output\_security\_group\_egress) | The egress rules of the Redis ElastiCache security group. | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the Redis ElastiCache security group. | +| [security\_group\_ingress](#output\_security\_group\_ingress) | The ingress rules of the Redis ElastiCache security group. | +| [security\_group\_name](#output\_security\_group\_name) | The name of the Redis ElastiCache security group. | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID of the Redis ElastiCache security group. | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID of the Redis ElastiCache security group. | + \ No newline at end of file diff --git a/modules/aws-elasticache-redis/examples/redis-basic/README.md b/modules/aws-elasticache-redis/examples/redis-basic/README.md new file mode 100644 index 0000000..80cdc4d --- /dev/null +++ b/modules/aws-elasticache-redis/examples/redis-basic/README.md @@ -0,0 +1,8 @@ +## Example deployment flow + +```bash +terraform init +terraform validate +terraform plan +terraform apply --auto-approve +``` \ No newline at end of file diff --git a/modules/aws-elasticache-redis/examples/redis-basic/main.tf b/modules/aws-elasticache-redis/examples/redis-basic/main.tf new file mode 100644 index 0000000..9a35412 --- /dev/null +++ b/modules/aws-elasticache-redis/examples/redis-basic/main.tf @@ -0,0 +1,89 @@ +provider "aws" { + region = var.primary_region +} + +variable "primary_region" { + default = "eu-west-1" + type = string + description = "You can use as primary region for the AWS provider where this redis will be provisioned" +} + +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +##### +# VPC and subnets +##### +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "all" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +##### +# External Security Group +##### +resource "aws_security_group" "other_sg" { + vpc_id = data.aws_vpc.default.id +} + +##### +# Elasticache Redis +##### +module "redis" { + source = "../../" + + name_prefix = "redis-basic-example" + num_cache_clusters = 2 + node_type = "cache.t4g.small" + + engine_version = "6.x" + port = 6379 + maintenance_window = "mon:03:00-mon:04:00" + snapshot_window = "04:00-06:00" + snapshot_retention_limit = 7 + final_snapshot_identifier = "redis-final-snapshot-name" + + automatic_failover_enabled = true + multi_az_enabled = true + + at_rest_encryption_enabled = true + transit_encryption_enabled = true + auth_token = "1234567890asdfghjkl" + + apply_immediately = true + family = "redis6.x" + description = "Test elasticache redis." + + subnet_ids = data.aws_subnets.all.ids + vpc_id = data.aws_vpc.default.id + + allowed_security_groups = [aws_security_group.other_sg.id] + + ingress_cidr_blocks = ["0.0.0.0/0"] + + parameter = [ + { + name = "repl-backlog-size" + value = "16384" + } + ] + + log_delivery_configuration = [ + { + destination_type = "cloudwatch-logs" + destination = "aws_cloudwatch_log_group.henrique.name" + log_format = "json" + log_type = "engine-log" + } + ] + + tags = { + Project = "Test" + } +} diff --git a/modules/aws-elasticache-redis/examples/redis-clustered-mode/main.tf b/modules/aws-elasticache-redis/examples/redis-clustered-mode/main.tf new file mode 100644 index 0000000..92495c0 --- /dev/null +++ b/modules/aws-elasticache-redis/examples/redis-clustered-mode/main.tf @@ -0,0 +1,70 @@ +provider "aws" { + region = var.primary_region +} + +variable "primary_region" { + default = "eu-west-1" + type = string + description = "You can use as primary region for the AWS provider where this redis will be provisioned" +} +##### +# VPC and subnets +##### +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "all" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +##### +# Elasticache Redis +##### +module "redis" { + source = "../../" + + name_prefix = "redis-clustered-example" + num_cache_clusters = 2 + node_type = "cache.m5.large" + + + cluster_mode_enabled = true + replicas_per_node_group = 1 + num_node_groups = 2 + + engine_version = "6.x" + port = 6379 + maintenance_window = "mon:03:00-mon:04:00" + snapshot_window = "04:00-06:00" + snapshot_retention_limit = 7 + + automatic_failover_enabled = true + + at_rest_encryption_enabled = true + transit_encryption_enabled = true + auth_token = "1234567890asdfghjkl" + + apply_immediately = true + family = "redis6.x" + description = "Test elasticache redis." + + subnet_ids = data.aws_subnets.all.ids + vpc_id = data.aws_vpc.default.id + + ingress_cidr_blocks = ["0.0.0.0/0"] + + parameter = [ + { + name = "repl-backlog-size" + value = "16384" + } + ] + + tags = { + Project = "Test" + } +} diff --git a/modules/aws-elasticache-redis/examples/redis-clustered-mode/outputs.tf b/modules/aws-elasticache-redis/examples/redis-clustered-mode/outputs.tf new file mode 100644 index 0000000..75385ca --- /dev/null +++ b/modules/aws-elasticache-redis/examples/redis-clustered-mode/outputs.tf @@ -0,0 +1,7 @@ +output "primary_endpoint" { + value = module.redis.elasticache_replication_group_primary_endpoint_address +} + +output "reader_endpoint" { + value = module.redis.elasticache_replication_group_reader_endpoint_address +} diff --git a/modules/aws-elasticache-redis/examples/redis-replication-group/main.tf b/modules/aws-elasticache-redis/examples/redis-replication-group/main.tf new file mode 100644 index 0000000..a3561b8 --- /dev/null +++ b/modules/aws-elasticache-redis/examples/redis-replication-group/main.tf @@ -0,0 +1,86 @@ +provider "aws" { + region = var.primary_region +} + +variable "primary_region" { + default = "eu-west-1" + type = string + description = "You can use as primary region for the AWS provider where this redis will be provisioned" +} + +provider "aws" { + region = var.secondary_region + alias = "replica" +} + +variable "secondary_region" { + default = "eu-west-2" + type = string + description = "You can use as primary region for the AWS provider where this redis will be provisioned" +} + +##### +# VPC and subnets +##### + +data "aws_vpc" "main" { + default = true +} + +data "aws_vpc" "replica" { + default = true + + provider = aws.replica +} + +data "aws_subnets" "main" { + filter { + name = "vpc-id" + values = [data.aws_vpc.main.id] + } +} + +data "aws_subnets" "replica" { + filter { + name = "vpc-id" + values = [data.aws_vpc.replica.id] + } + + provider = aws.replica +} +##### +# Elasticache Redis +##### + +module "redis_main" { + source = "../../" + + name_prefix = "redis-example-main" + num_cache_clusters = 2 + node_type = "cache.m5.large" + auth_token = "1234567890asdfghjkl" + + subnet_ids = data.aws_subnets.main.ids + vpc_id = data.aws_vpc.main.id +} + +resource "aws_elasticache_global_replication_group" "this" { + global_replication_group_id_suffix = "ha" + primary_replication_group_id = module.redis_main.elasticache_replication_group_id +} + +module "redis_replica" { + source = "../../" + + name_prefix = "redis-example-replica" + num_cache_clusters = 2 + node_type = "cache.m5.large" + auth_token = "1234567890asdfghjkl" + + subnet_ids = data.aws_subnets.replica.ids + vpc_id = data.aws_vpc.replica.id + + global_replication_group_id = aws_elasticache_global_replication_group.this.global_replication_group_id + + providers = { aws = aws.replica } +} diff --git a/modules/aws-elasticache-redis/main.tf b/modules/aws-elasticache-redis/main.tf new file mode 100644 index 0000000..3d86776 --- /dev/null +++ b/modules/aws-elasticache-redis/main.tf @@ -0,0 +1,159 @@ + +resource "aws_elasticache_replication_group" "redis" { + engine = var.global_replication_group_id == null ? "redis" : null + + parameter_group_name = var.global_replication_group_id == null ? aws_elasticache_parameter_group.redis.name : null + subnet_group_name = aws_elasticache_subnet_group.redis.name + security_group_ids = concat(var.security_group_ids, [aws_security_group.redis.id]) + + preferred_cache_cluster_azs = var.preferred_cache_cluster_azs + replication_group_id = var.global_replication_group_id == null ? "${var.name_prefix}-redis" : "${var.name_prefix}-redis-replica" + num_cache_clusters = var.cluster_mode_enabled ? null : var.num_cache_clusters + node_type = var.global_replication_group_id == null ? var.node_type : null + + engine_version = var.global_replication_group_id == null ? var.engine_version : null + port = var.port + + maintenance_window = var.maintenance_window + snapshot_window = var.snapshot_window + snapshot_retention_limit = var.snapshot_retention_limit + final_snapshot_identifier = var.final_snapshot_identifier + automatic_failover_enabled = var.automatic_failover_enabled && var.num_cache_clusters >= 2 ? true : false + auto_minor_version_upgrade = var.auto_minor_version_upgrade + multi_az_enabled = var.multi_az_enabled + + at_rest_encryption_enabled = var.global_replication_group_id == null && var.at_rest_encryption_enabled ? var.at_rest_encryption_enabled : null + transit_encryption_enabled = var.global_replication_group_id == null && var.transit_encryption_enabled ? var.transit_encryption_enabled : null + auth_token = var.auth_token != "" ? var.auth_token : null + kms_key_id = var.kms_key_id + global_replication_group_id = var.global_replication_group_id + + apply_immediately = var.apply_immediately + + description = var.description + + data_tiering_enabled = var.data_tiering_enabled + + notification_topic_arn = var.notification_topic_arn + + replicas_per_node_group = var.cluster_mode_enabled ? var.replicas_per_node_group : null + num_node_groups = var.cluster_mode_enabled ? var.num_node_groups : null + + dynamic "log_delivery_configuration" { + for_each = var.log_delivery_configuration + + content { + destination_type = log_delivery_configuration.value.destination_type + destination = log_delivery_configuration.value.destination + log_format = log_delivery_configuration.value.log_format + log_type = log_delivery_configuration.value.log_type + } + } + + tags = merge( + { + "Name" = "${var.name_prefix}-redis" + }, + var.tags, + ) +} + +resource "random_id" "redis_pg" { + keepers = { + family = var.family + } + + byte_length = 2 +} + +resource "aws_elasticache_parameter_group" "redis" { + name = "${var.name_prefix}-redis-${random_id.redis_pg.hex}" + family = var.family + description = var.description + + dynamic "parameter" { + for_each = var.num_node_groups > 0 ? concat([{ name = "cluster-enabled", value = "yes" }], var.parameter) : var.parameter + content { + name = parameter.value.name + value = parameter.value.value + } + } + + lifecycle { + create_before_destroy = true + } + + tags = var.tags +} + +data "aws_subnet_ids" "private_subnets_with_cache_tag" { + vpc_id = var.vpc_id + tags = { + Name = var.subnet_id_names + } +} + +resource "aws_elasticache_subnet_group" "redis" { + name = var.global_replication_group_id == null ? "${var.name_prefix}-redis-sg" : "${var.name_prefix}-redis-sg-replica" + subnet_ids = data.aws_subnet_ids.private_subnets_with_cache_tag.ids + description = var.description + + tags = var.tags +} + +resource "aws_security_group" "redis" { + name_prefix = "${var.name_prefix}-redis-" + vpc_id = var.vpc_id + + tags = merge( + { + "Name" = "${var.name_prefix}-redis" + }, + var.tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "redis_ingress_self" { + count = var.ingress_self ? 1 : 0 + + type = "ingress" + from_port = var.port + to_port = var.port + protocol = "tcp" + self = true + security_group_id = aws_security_group.redis.id +} + +resource "aws_security_group_rule" "redis_ingress_cidr_blocks" { + count = length(var.ingress_cidr_blocks) != 0 ? 1 : 0 + + type = "ingress" + from_port = var.port + to_port = var.port + protocol = "tcp" + cidr_blocks = var.ingress_cidr_blocks + security_group_id = aws_security_group.redis.id +} + +resource "aws_security_group_rule" "redis_egress" { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.redis.id +} + +resource "aws_security_group_rule" "other_sg_ingress" { + count = length(var.allowed_security_groups) + type = "ingress" + from_port = var.port + to_port = var.port + protocol = "tcp" + source_security_group_id = element(var.allowed_security_groups, count.index) + security_group_id = aws_security_group.redis.id +} diff --git a/modules/aws-elasticache-redis/outputs.tf b/modules/aws-elasticache-redis/outputs.tf new file mode 100644 index 0000000..86652fa --- /dev/null +++ b/modules/aws-elasticache-redis/outputs.tf @@ -0,0 +1,85 @@ +output "elasticache_replication_group_arn" { + value = aws_elasticache_replication_group.redis.arn + description = "The Amazon Resource Name (ARN) of the created ElastiCache Replication Group." +} + +output "elasticache_replication_group_id" { + value = aws_elasticache_replication_group.redis.id + description = "The ID of the ElastiCache Replication Group." +} + +output "elasticache_replication_group_primary_endpoint_address" { + value = var.num_node_groups > 0 ? aws_elasticache_replication_group.redis.configuration_endpoint_address : aws_elasticache_replication_group.redis.primary_endpoint_address + description = "The address of the endpoint for the primary node in the replication group." +} + +output "elasticache_replication_group_reader_endpoint_address" { + value = var.num_node_groups > 0 ? aws_elasticache_replication_group.redis.configuration_endpoint_address : aws_elasticache_replication_group.redis.reader_endpoint_address + description = "The address of the endpoint for the reader node in the replication group." +} + +output "elasticache_configuration_endpoint_address" { + value = aws_elasticache_replication_group.redis.configuration_endpoint_address + description = "The address of the configuration endpoint" +} + +output "elasticache_replication_group_member_clusters" { + value = aws_elasticache_replication_group.redis.member_clusters + description = "The identifiers of all the nodes that are part of this replication group." +} + +output "elasticache_parameter_group_id" { + value = aws_elasticache_parameter_group.redis.id + description = "The ElastiCache parameter group name." +} + +output "security_group_id" { + value = aws_security_group.redis.id + description = "The ID of the Redis ElastiCache security group." +} + +output "security_group_arn" { + value = aws_security_group.redis.arn + description = "The ARN of the Redis ElastiCache security group." +} + +output "security_group_vpc_id" { + value = aws_security_group.redis.vpc_id + description = "The VPC ID of the Redis ElastiCache security group." +} + +output "security_group_owner_id" { + value = aws_security_group.redis.owner_id + description = "The owner ID of the Redis ElastiCache security group." +} + +output "security_group_name" { + value = aws_security_group.redis.name + description = "The name of the Redis ElastiCache security group." +} + +output "security_group_description" { + value = aws_security_group.redis.description + description = "The description of the Redis ElastiCache security group." +} + +output "security_group_ingress" { + value = aws_security_group.redis.ingress + description = "The ingress rules of the Redis ElastiCache security group." +} + +output "security_group_egress" { + value = aws_security_group.redis.egress + description = "The egress rules of the Redis ElastiCache security group." +} + +output "elasticache_auth_token" { + description = "The Redis Auth Token." + value = aws_elasticache_replication_group.redis.auth_token + sensitive = true +} + +output "elasticache_port" { + description = "The Redis port." + value = aws_elasticache_replication_group.redis.port +} diff --git a/modules/aws-elasticache-redis/variables.tf b/modules/aws-elasticache-redis/variables.tf new file mode 100644 index 0000000..58cb880 --- /dev/null +++ b/modules/aws-elasticache-redis/variables.tf @@ -0,0 +1,223 @@ +variable "name_prefix" { + type = string + description = "The replication group identifier. This parameter is stored as a lowercase string." +} + +variable "num_cache_clusters" { + type = number + default = 1 + description = "The number of cache clusters (primary and replicas) this replication group will have. If Multi-AZ is enabled, the value of this parameter must be at least 2. Updates will occur before other modifications. Conflicts with num_node_groups." +} + +variable "cluster_mode_enabled" { + type = bool + description = "Enable creation of a native redis cluster." + default = false +} + +variable "node_type" { + type = string + description = "The compute and memory capacity of the nodes in the node group." +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +variable "vpc_id" { + type = string + description = "VPC Id to associate with Redis ElastiCache." +} + +variable "ingress_cidr_blocks" { + type = list(string) + description = "List of Ingress CIDR blocks." + default = [] +} + +variable "ingress_self" { + type = bool + description = "Specify whether the security group itself will be added as a source to the ingress rule." + default = false +} + +variable "security_group_ids" { + type = list(string) + description = "List of Security Groups." + default = [] +} + +variable "engine_version" { + default = "6.x" + type = string + description = "The version number of the cache engine to be used for the cache clusters in this replication group." +} + +variable "port" { + default = 6379 + type = number + description = "The port number on which each of the cache nodes will accept connections." +} + +variable "maintenance_window" { + default = "" + type = string + description = "Specifies the weekly time range for when maintenance on the cache cluster is performed." +} + +variable "snapshot_window" { + default = "" + type = string + description = "The daily time range (in UTC) during which ElastiCache will begin taking a daily snapshot of your cache cluster." +} + +variable "snapshot_retention_limit" { + default = 30 + type = number + description = "The number of days for which ElastiCache will retain automatic cache cluster snapshots before deleting them." +} + +variable "auto_minor_version_upgrade" { + default = true + type = string +} + +variable "automatic_failover_enabled" { + default = true + type = bool + description = "Specifies whether a read-only replica will be automatically promoted to read/write primary if the existing primary fails. If enabled, number_cache_clusters must be greater than 1. Must be enabled for Redis (cluster mode enabled) replication groups." +} + +variable "at_rest_encryption_enabled" { + default = true + type = bool + description = "Whether to enable encryption at rest." +} + +variable "transit_encryption_enabled" { + default = true + type = bool + description = "Whether to enable encryption in transit." +} + +variable "apply_immediately" { + default = false + type = bool + description = "Specifies whether any modifications are applied immediately, or during the next maintenance window." +} + +variable "family" { + default = "redis6.x" + type = string + description = "The family of the ElastiCache parameter group." +} + +variable "description" { + default = "Managed by Terraform" + type = string + description = "The description of the all resources." +} + +variable "tags" { + default = {} + type = map(string) + description = "A mapping of tags to assign to all resources." +} + +variable "auth_token" { + type = string + description = "The password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true`." + default = "" +} + +variable "kms_key_id" { + type = string + description = "The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true`" + default = "" +} + +variable "parameter" { + type = list(object({ + name = string + value = string + })) + default = [] + description = "A list of Redis parameters to apply. Note that parameters may differ from one Redis family to another" +} + +variable "notification_topic_arn" { + type = string + default = "" + description = "An Amazon Resource Name (ARN) of an SNS topic to send ElastiCache notifications to. Example: `arn:aws:sns:us-east-1:012345678999:my_sns_topic`" +} + +variable "replicas_per_node_group" { + type = number + default = 0 + description = "Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will trigger an online resizing operation before other settings modifications." + + validation { + condition = var.replicas_per_node_group <= 5 + error_message = "The replicas_per_node_group value must be between 0 and 5." + } +} + +variable "num_node_groups" { + type = number + default = 0 + description = "Specify the number of node groups (shards) for this Redis replication group. Changing this number will trigger an online resizing operation before other settings modifications." +} + +variable "preferred_cache_cluster_azs" { + type = list(string) + description = "A list of EC2 availability zones in which the replication group's cache clusters will be created. The order of the availability zones in the list is not important." + default = null +} + +variable "multi_az_enabled" { + type = string + description = "Specifies whether to enable Multi-AZ Support for the replication group. If true, `automatic_failover_enabled` must also be enabled. Defaults to false." + default = false +} + +variable "final_snapshot_identifier" { + type = string + description = "The name of your final node group (shard) snapshot. ElastiCache creates the snapshot from the primary node in the cluster. If omitted, no final snapshot will be made." + default = null +} + +variable "global_replication_group_id" { + description = "The ID of the global replication group to which this replication group should belong." + type = string + default = null +} + +variable "log_delivery_configuration" { + type = list(object({ + destination_type = string + destination = string + log_format = string + log_type = string + })) + description = "Log Delivery configuration for the cluster." + default = [] + + validation { + condition = length(var.log_delivery_configuration) <= 2 + error_message = "You can set 2 targets at most for log delivery options." + } +} + +variable "allowed_security_groups" { + type = list(string) + description = "List of existing security groups that will be allowed ingress via the elaticache security group rules" + default = [] +} + +variable "data_tiering_enabled" { + type = bool + default = false + description = "Enables data tiering. Data tiering is only supported for replication groups using the r6gd node type. This parameter must be set to true when using r6gd nodes." +} diff --git a/modules/aws-elasticache-redis/versions.tf b/modules/aws-elasticache-redis/versions.tf new file mode 100644 index 0000000..8abb2a2 --- /dev/null +++ b/modules/aws-elasticache-redis/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0.11" + + required_providers { + aws = ">= 4.8.0, < 5.0.0" + random = ">= 3.3.2, < 4.0.0" + } +} diff --git a/modules/aws-elasticsearch/README.md b/modules/aws-elasticsearch/README.md new file mode 100644 index 0000000..b838dfb --- /dev/null +++ b/modules/aws-elasticsearch/README.md @@ -0,0 +1,187 @@ + + +# terraform-aws-elasticsearch + + + +Terraform module to provision an [`Elasticsearch`](https://aws.amazon.com/elasticsearch-service/) cluster with built-in integrations with [Kibana](https://aws.amazon.com/elasticsearch-service/kibana/) and [Logstash](https://aws.amazon.com/elasticsearch-service/logstash/). + + + +## Introduction + +This module will create: +- Elasticsearch cluster with the specified node count in the provided subnets in a VPC +- Elasticsearch domain policy that accepts a list of IAM role ARNs from which to permit management traffic to the cluster +- Security Group to control access to the Elasticsearch domain (inputs to the Security Group are other Security Groups or CIDRs blocks to be allowed to connect to the cluster) +- DNS hostname record for Elasticsearch cluster (if DNS Zone ID is provided) +- DNS hostname record for Kibana (if DNS Zone ID is provided) + +__NOTE:__ To enable [zone awareness](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-managedomains.html#es-managedomains-zoneawareness) to deploy Elasticsearch nodes into two different Availability Zones, you need to set `zone_awareness_enabled` to `true` and provide two different subnets in `subnet_ids`. +If you enable zone awareness for your domain, Amazon ES places an endpoint into two subnets. +The subnets must be in different Availability Zones in the same region. +If you don't enable zone awareness, Amazon ES places an endpoint into only one subnet. You also need to set `availability_zone_count` to `1`. + + + +## Usage + + + + +```hcl +module "elasticsearch" { + + domain_name = "es" + security_groups = ["sg-XXXXXXXXX", "sg-YYYYYYYY"] + vpc_id = "vpc-XXXXXXXXX" + subnet_ids = ["subnet-XXXXXXXXX", "subnet-YYYYYYYY"] + zone_awareness_enabled = "true" + elasticsearch_version = "6.5" + instance_type = "t2.small.elasticsearch" + instance_count = 4 + ebs_volume_size = 10 + iam_role_arns = ["arn:aws:iam::XXXXXXXXX:role/ops", "arn:aws:iam::XXXXXXXXX:role/dev"] + iam_actions = ["es:ESHttpGet", "es:ESHttpPut", "es:ESHttpPost"] + encrypt_at_rest_enabled = true + kibana_subdomain_name = "kibana-es" + + advanced_options = { + "rest.action.multi.allow_explicit_index" = "true" + } +} +``` + + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3.35.0 | +| [null](#requirement\_null) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.35.0 | + + +## Resources + +| Name | Type | +|------|------| +| [aws_elasticsearch_domain.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain) | resource | +| [aws_elasticsearch_domain_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain_policy) | resource | +| [aws_iam_role.elasticsearch_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_service_linked_role.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_service_linked_role) | resource | +| [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_security_groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [advanced\_options](#input\_advanced\_options) | Key-value string pairs to specify advanced configuration options | `map(string)` | `{}` | no | +| [advanced\_security\_options\_enabled](#input\_advanced\_security\_options\_enabled) | AWS Elasticsearch Kibana enchanced security plugin enabling (forces new resource) | `bool` | `false` | no | +| [advanced\_security\_options\_internal\_user\_database\_enabled](#input\_advanced\_security\_options\_internal\_user\_database\_enabled) | Whether to enable or not internal Kibana user database for ELK OpenDistro security plugin | `bool` | `false` | no | +| [advanced\_security\_options\_master\_user\_arn](#input\_advanced\_security\_options\_master\_user\_arn) | ARN of IAM user who is to be mapped to be Kibana master user (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to false) | `string` | `""` | no | +| [advanced\_security\_options\_master\_user\_name](#input\_advanced\_security\_options\_master\_user\_name) | Master user username (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to true) | `string` | `""` | no | +| [advanced\_security\_options\_master\_user\_password](#input\_advanced\_security\_options\_master\_user\_password) | Master user password (applicable if advanced\_security\_options\_internal\_user\_database\_enabled set to true) | `string` | `""` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of CIDR blocks to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [automated\_snapshot\_start\_hour](#input\_automated\_snapshot\_start\_hour) | Hour at which automated snapshots are taken, in UTC | `number` | `0` | no | +| [availability\_zone\_count](#input\_availability\_zone\_count) | Number of Availability Zones for the domain to use. | `number` | `2` | no | +| [aws\_ec2\_service\_name](#input\_aws\_ec2\_service\_name) | AWS EC2 Service Name | `list(string)` |
[
"ec2.amazonaws.com"
]
| no | +| [cognito\_authentication\_enabled](#input\_cognito\_authentication\_enabled) | Whether to enable Amazon Cognito authentication with Kibana | `bool` | `false` | no | +| [cognito\_iam\_role\_arn](#input\_cognito\_iam\_role\_arn) | ARN of the IAM role that has the AmazonESCognitoAccess policy attached | `string` | `""` | no | +| [cognito\_identity\_pool\_id](#input\_cognito\_identity\_pool\_id) | The ID of the Cognito Identity Pool to use | `string` | `""` | no | +| [cognito\_user\_pool\_id](#input\_cognito\_user\_pool\_id) | The ID of the Cognito User Pool to use | `string` | `""` | no | +| [create\_iam\_service\_linked\_role](#input\_create\_iam\_service\_linked\_role) | Whether to create `AWSServiceRoleForAmazonElasticsearchService` service-linked role. Set it to `false` if you already have an ElasticSearch cluster created in the AWS account and AWSServiceRoleForAmazonElasticsearchService already exists. See https://github.com/terraform-providers/terraform-provider-aws/issues/5218 for more info | `bool` | `true` | no | +| [custom\_endpoint](#input\_custom\_endpoint) | Fully qualified domain for custom endpoint. | `string` | `""` | no | +| [custom\_endpoint\_certificate\_arn](#input\_custom\_endpoint\_certificate\_arn) | ACM certificate ARN for custom endpoint. | `string` | `""` | no | +| [custom\_endpoint\_enabled](#input\_custom\_endpoint\_enabled) | Whether to enable custom endpoint for the Elasticsearch domain. | `bool` | `false` | no | +| [dedicated\_master\_count](#input\_dedicated\_master\_count) | Number of dedicated master nodes in the cluster | `number` | `0` | no | +| [dedicated\_master\_enabled](#input\_dedicated\_master\_enabled) | Indicates whether dedicated master nodes are enabled for the cluster | `bool` | `false` | no | +| [dedicated\_master\_type](#input\_dedicated\_master\_type) | Instance type of the dedicated master nodes in the cluster | `string` | `"t2.small.elasticsearch"` | no | +| [dns\_zone\_id](#input\_dns\_zone\_id) | Route53 DNS Zone ID to add hostname records for Elasticsearch domain and Kibana | `string` | `""` | no | +| [domain\_endpoint\_options\_enforce\_https](#input\_domain\_endpoint\_options\_enforce\_https) | Whether or not to require HTTPS | `bool` | `true` | no | +| [domain\_endpoint\_options\_tls\_security\_policy](#input\_domain\_endpoint\_options\_tls\_security\_policy) | The name of the TLS security policy that needs to be applied to the HTTPS endpoint | `string` | `"Policy-Min-TLS-1-0-2019-07"` | no | +| [domain\_hostname\_enabled](#input\_domain\_hostname\_enabled) | Explicit flag to enable creating a DNS hostname for ES. If `true`, then `var.dns_zone_id` is required. | `bool` | `false` | no | +| [ebs\_iops](#input\_ebs\_iops) | The baseline input/output (I/O) performance of EBS volumes attached to data nodes. Applicable only for the Provisioned IOPS EBS volume type | `number` | `0` | no | +| [ebs\_volume\_size](#input\_ebs\_volume\_size) | EBS volumes for data storage in GB | `number` | `0` | no | +| [ebs\_volume\_type](#input\_ebs\_volume\_type) | Storage type of EBS volumes | `string` | `"gp2"` | no | +| [elasticsearch\_subdomain\_name](#input\_elasticsearch\_subdomain\_name) | The name of the subdomain for Elasticsearch in the DNS zone (\_e.g.\_ `elasticsearch`, `ui`, `ui-es`, `search-ui`) | `string` | `""` | no | +| [elasticsearch\_version](#input\_elasticsearch\_version) | Version of Elasticsearch to deploy (\_e.g.\_ `7.4`, `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5` | `string` | `"7.4"` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [encrypt\_at\_rest\_enabled](#input\_encrypt\_at\_rest\_enabled) | Whether to enable encryption at rest | `bool` | `true` | no | +| [encrypt\_at\_rest\_kms\_key\_id](#input\_encrypt\_at\_rest\_kms\_key\_id) | The KMS key ID to encrypt the Elasticsearch domain with. If not specified, then it defaults to using the AWS/Elasticsearch service KMS key | `string` | `""` | no | +| [iam\_actions](#input\_iam\_actions) | List of actions to allow for the IAM roles, _e.g._ `es:ESHttpGet`, `es:ESHttpPut`, `es:ESHttpPost` | `list(string)` | `[]` | no | +| [iam\_authorizing\_role\_arns](#input\_iam\_authorizing\_role\_arns) | List of IAM role ARNs to permit to assume the Elasticsearch user role | `list(string)` | `[]` | no | +| [iam\_role\_arns](#input\_iam\_role\_arns) | List of IAM role ARNs to permit access to the Elasticsearch domain | `list(string)` | `[]` | no | +| [iam\_role\_max\_session\_duration](#input\_iam\_role\_max\_session\_duration) | The maximum session duration (in seconds) for the user role. Can have a value from 1 hour to 12 hours | `number` | `3600` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | The ARN of the permissions boundary policy which will be attached to the Elasticsearch user role | `string` | `null` | no | +| [ingress\_port\_range\_end](#input\_ingress\_port\_range\_end) | End number for allowed port range. (e.g. `443`) | `number` | `65535` | no | +| [ingress\_port\_range\_start](#input\_ingress\_port\_range\_start) | Start number for allowed port range. (e.g. `443`) | `number` | `0` | no | +| [instance\_count](#input\_instance\_count) | Number of data nodes in the cluster | `number` | `4` | no | +| [instance\_type](#input\_instance\_type) | Elasticsearch instance type for data nodes in the cluster | `string` | `"t2.small.elasticsearch"` | no | +| [kibana\_hostname\_enabled](#input\_kibana\_hostname\_enabled) | Explicit flag to enable creating a DNS hostname for Kibana. If `true`, then `var.dns_zone_id` is required. | `bool` | `false` | no | +| [kibana\_subdomain\_name](#input\_kibana\_subdomain\_name) | The name of the subdomain for Kibana in the DNS zone (\_e.g.\_ `kibana`, `ui`, `ui-es`, `search-ui`, `kibana.elasticsearch`) | `string` | `""` | no | +| [log\_publishing\_application\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_application\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for ES\_APPLICATION\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_application\_enabled](#input\_log\_publishing\_application\_enabled) | Specifies whether log publishing option for ES\_APPLICATION\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_audit\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_audit\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for AUDIT\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_audit\_enabled](#input\_log\_publishing\_audit\_enabled) | Specifies whether log publishing option for AUDIT\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_index\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_index\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for INDEX\_SLOW\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_index\_enabled](#input\_log\_publishing\_index\_enabled) | Specifies whether log publishing option for INDEX\_SLOW\_LOGS is enabled or not | `bool` | `false` | no | +| [log\_publishing\_search\_cloudwatch\_log\_group\_arn](#input\_log\_publishing\_search\_cloudwatch\_log\_group\_arn) | ARN of the CloudWatch log group to which log for SEARCH\_SLOW\_LOGS needs to be published | `string` | `""` | no | +| [log\_publishing\_search\_enabled](#input\_log\_publishing\_search\_enabled) | Specifies whether log publishing option for SEARCH\_SLOW\_LOGS is enabled or not | `bool` | `false` | no | +| [node\_to\_node\_encryption\_enabled](#input\_node\_to\_node\_encryption\_enabled) | Whether to enable node-to-node encryption | `bool` | `false` | no | +| [security\_groups](#input\_security\_groups) | List of security group IDs to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [subnet\_ids](#input\_subnet\_ids) | VPC Subnet IDs | `list(string)` | `[]` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [vpc\_enabled](#input\_vpc\_enabled) | Set to false if ES should be deployed outside of VPC. | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | VPC ID | `string` | `null` | no | +| [warm\_count](#input\_warm\_count) | Number of UltraWarm nodes | `number` | `2` | no | +| [warm\_enabled](#input\_warm\_enabled) | Whether AWS UltraWarm is enabled | `bool` | `false` | no | +| [warm\_type](#input\_warm\_type) | Type of UltraWarm nodes | `string` | `"ultrawarm1.medium.elasticsearch"` | no | +| [zone\_awareness\_enabled](#input\_zone\_awareness\_enabled) | Enable zone awareness for Elasticsearch cluster | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [domain\_arn](#output\_domain\_arn) | ARN of the Elasticsearch domain | +| [domain\_endpoint](#output\_domain\_endpoint) | Domain-specific endpoint used to submit index, search, and data upload requests | +| [domain\_hostname](#output\_domain\_hostname) | Elasticsearch domain hostname to submit index, search, and data upload requests | +| [domain\_id](#output\_domain\_id) | Unique identifier for the Elasticsearch domain | +| [domain\_name](#output\_domain\_name) | Name of the Elasticsearch domain | +| [elasticsearch\_user\_iam\_role\_arn](#output\_elasticsearch\_user\_iam\_role\_arn) | The ARN of the IAM role to allow access to Elasticsearch cluster | +| [elasticsearch\_user\_iam\_role\_name](#output\_elasticsearch\_user\_iam\_role\_name) | The name of the IAM role to allow access to Elasticsearch cluster | +| [kibana\_endpoint](#output\_kibana\_endpoint) | Domain-specific endpoint for Kibana without https scheme | +| [kibana\_hostname](#output\_kibana\_hostname) | Kibana hostname | +| [security\_group\_id](#output\_security\_group\_id) | Security Group ID to control access to the Elasticsearch domain | + + + + + + +## References + +For additional context, refer to some of these links. + +- [What is Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/what-is-amazon-elasticsearch-service.html) - Complete description of Amazon Elasticsearch Service +- [Amazon Elasticsearch Service Access Control](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) - Describes several ways of controlling access to Elasticsearch domains +- [VPC Support for Amazon Elasticsearch Service Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html) - Describes Elasticsearch Service VPC Support and VPC architectures with and without zone awareness +- [Creating and Configuring Amazon Elasticsearch Service Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html) - Provides a complete description on how to create and configure Amazon Elasticsearch Service (Amazon ES) domains +- [Kibana and Logstash](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-kibana.html) - Describes some considerations for using Kibana and Logstash with Amazon Elasticsearch Service +- [Amazon Cognito Authentication for Kibana](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-cognito-auth.html) - Amazon Elasticsearch Service uses Amazon Cognito to offer user name and password protection for Kibana +- [Control Access to Amazon Elasticsearch Service Domain](https://aws.amazon.com/blogs/security/how-to-control-access-to-your-amazon-elasticsearch-service-domain/) - Describes how to Control Access to Amazon Elasticsearch Service Domain +- [elasticsearch_domain](https://www.terraform.io/docs/providers/aws/r/elasticsearch_domain.html) - Terraform reference documentation for the `elasticsearch_domain` resource +- [elasticsearch_domain_policy](https://www.terraform.io/docs/providers/aws/r/elasticsearch_domain_policy.html) - Terraform reference documentation for the `elasticsearch_domain_policy` resource \ No newline at end of file diff --git a/modules/aws-elasticsearch/main.tf b/modules/aws-elasticsearch/main.tf new file mode 100644 index 0000000..a2743d4 --- /dev/null +++ b/modules/aws-elasticsearch/main.tf @@ -0,0 +1,243 @@ +data "aws_subnets" "private_subnets_with_database_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } +} + +resource "random_password" "master_user_password" { + count = var.advanced_security_options_internal_user_database_enabled ? 1 : 0 + length = 10 + min_lower = 1 + min_numeric = 1 + min_special = 1 + min_upper = 1 +} + +# https://github.com/terraform-providers/terraform-provider-aws/issues/5218 +resource "aws_iam_service_linked_role" "default" { + count = var.enabled && var.create_iam_service_linked_role ? 1 : 0 + aws_service_name = "es.amazonaws.com" + description = "AWSServiceRoleForAmazonElasticsearchService Service-Linked Role" +} + +# Role that pods can assume for access to elasticsearch and kibana +resource "aws_iam_role" "elasticsearch_user" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + name = "${var.domain_name}-es-user-role" + assume_role_policy = join("", data.aws_iam_policy_document.assume_role.*.json) + description = "IAM Role to assume to access the Elasticsearch ${var.domain_name} cluster" + tags = var.tags + + max_session_duration = var.iam_role_max_session_duration + + permissions_boundary = var.iam_role_permissions_boundary +} + +data "aws_iam_policy_document" "assume_role" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + + statement { + actions = [ + "sts:AssumeRole" + ] + + principals { + type = "Service" + identifiers = var.aws_ec2_service_name + } + + principals { + type = "AWS" + identifiers = compact(concat(var.iam_authorizing_role_arns, var.iam_role_arns)) + } + + effect = "Allow" + } +} + +resource "aws_elasticsearch_domain" "default" { + count = var.enabled ? 1 : 0 + domain_name = var.domain_name + elasticsearch_version = var.elasticsearch_version + + advanced_options = var.advanced_options + + advanced_security_options { + enabled = var.advanced_security_options_enabled + internal_user_database_enabled = var.advanced_security_options_internal_user_database_enabled + master_user_options { + master_user_arn = var.advanced_security_options_master_user_arn + master_user_name = var.advanced_security_options_master_user_name + master_user_password = random_password.master_user_password[0].result + } + } + + ebs_options { + ebs_enabled = var.ebs_volume_size > 0 ? true : false + volume_size = var.ebs_volume_size + volume_type = var.ebs_volume_type + iops = var.ebs_iops + } + + encrypt_at_rest { + enabled = var.encrypt_at_rest_enabled + kms_key_id = var.encrypt_at_rest_kms_key_id + } + + domain_endpoint_options { + enforce_https = var.domain_endpoint_options_enforce_https + tls_security_policy = var.domain_endpoint_options_tls_security_policy + custom_endpoint_enabled = var.custom_endpoint_enabled + custom_endpoint = var.custom_endpoint_enabled ? var.custom_endpoint : null + custom_endpoint_certificate_arn = var.custom_endpoint_enabled ? var.custom_endpoint_certificate_arn : null + } + + cluster_config { + instance_count = var.instance_count + instance_type = var.instance_type + dedicated_master_enabled = var.dedicated_master_enabled + dedicated_master_count = var.dedicated_master_count + dedicated_master_type = var.dedicated_master_type + zone_awareness_enabled = var.zone_awareness_enabled + warm_enabled = var.warm_enabled + warm_count = var.warm_enabled ? var.warm_count : null + warm_type = var.warm_enabled ? var.warm_type : null + + dynamic "zone_awareness_config" { + for_each = var.availability_zone_count > 1 && var.zone_awareness_enabled ? [true] : [] + content { + availability_zone_count = var.availability_zone_count + } + } + } + + node_to_node_encryption { + enabled = var.node_to_node_encryption_enabled + } + + dynamic "vpc_options" { + for_each = var.vpc_enabled ? [true] : [] + + content { + security_group_ids = var.security_groups + subnet_ids = try(data.aws_subnets.private_subnets_with_database_tag.ids, null) + } + } + + snapshot_options { + automated_snapshot_start_hour = var.automated_snapshot_start_hour + } + + dynamic "cognito_options" { + for_each = var.cognito_authentication_enabled ? [true] : [] + content { + enabled = true + user_pool_id = var.cognito_user_pool_id + identity_pool_id = var.cognito_identity_pool_id + role_arn = var.cognito_iam_role_arn + } + } + + log_publishing_options { + enabled = var.log_publishing_index_enabled + log_type = "INDEX_SLOW_LOGS" + cloudwatch_log_group_arn = var.log_publishing_index_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_search_enabled + log_type = "SEARCH_SLOW_LOGS" + cloudwatch_log_group_arn = var.log_publishing_search_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_audit_enabled + log_type = "AUDIT_LOGS" + cloudwatch_log_group_arn = var.log_publishing_audit_cloudwatch_log_group_arn + } + + log_publishing_options { + enabled = var.log_publishing_application_enabled + log_type = "ES_APPLICATION_LOGS" + cloudwatch_log_group_arn = var.log_publishing_application_cloudwatch_log_group_arn + } + + tags = var.tags + + depends_on = [aws_iam_service_linked_role.default] +} + +resource "aws_elasticsearch_domain_saml_options" "default" { + count = var.enabled && var.saml_authentication ? 1 : 0 + domain_name = aws_elasticsearch_domain.default.0.domain_name + saml_options { + enabled = true + master_backend_role = var.master_backend_role + roles_key = var.saml_roles_key + idp { + entity_id = var.saml_entity_id + metadata_content = file(var.saml_metadata_xmlfile_path) + } + } +} + +data "aws_iam_policy_document" "default" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + + statement { + effect = "Allow" + + actions = distinct(compact(var.iam_actions)) + + resources = [ + join("", aws_elasticsearch_domain.default.*.arn), + "${join("", aws_elasticsearch_domain.default.*.arn)}/*" + ] + + principals { + type = "AWS" + identifiers = distinct(compact(concat(var.iam_role_arns, aws_iam_role.elasticsearch_user.*.arn))) + } + } + + # This statement is for non VPC ES to allow anonymous access from whitelisted IP ranges without requests signing + # https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html#es-ac-types-ip + # https://aws.amazon.com/premiumsupport/knowledge-center/anonymous-not-authorized-elasticsearch/ + dynamic "statement" { + for_each = length(var.allowed_cidr_blocks) > 0 && !var.vpc_enabled ? [true] : [] + content { + effect = "Allow" + + actions = distinct(compact(var.iam_actions)) + + resources = [ + join("", aws_elasticsearch_domain.default.*.arn), + "${join("", aws_elasticsearch_domain.default.*.arn)}/*" + ] + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "IpAddress" + values = var.allowed_cidr_blocks + variable = "aws:SourceIp" + } + } + } +} + +resource "aws_elasticsearch_domain_policy" "default" { + count = var.enabled && (length(var.iam_authorizing_role_arns) > 0 || length(var.iam_role_arns) > 0) ? 1 : 0 + domain_name = var.domain_name + access_policies = join("", data.aws_iam_policy_document.default.*.json) +} + + diff --git a/modules/aws-elasticsearch/outputs.tf b/modules/aws-elasticsearch/outputs.tf new file mode 100644 index 0000000..e4e127b --- /dev/null +++ b/modules/aws-elasticsearch/outputs.tf @@ -0,0 +1,51 @@ +output "security_group_ids" { + value = var.security_groups + description = "Security Group ID to control access to the Elasticsearch domain" +} + +output "domain_arn" { + value = join("", aws_elasticsearch_domain.default.*.arn) + description = "ARN of the Elasticsearch domain" +} + +output "domain_id" { + value = join("", aws_elasticsearch_domain.default.*.domain_id) + description = "Unique identifier for the Elasticsearch domain" +} + +output "domain_name" { + value = join("", aws_elasticsearch_domain.default.*.domain_name) + description = "Name of the Elasticsearch domain" +} + +output "domain_endpoint" { + value = join("", aws_elasticsearch_domain.default.*.endpoint) + description = "Domain-specific endpoint used to submit index, search, and data upload requests" +} + +output "kibana_endpoint" { + value = join("", aws_elasticsearch_domain.default.*.kibana_endpoint) + description = "Domain-specific endpoint for Kibana without https scheme" +} + + +output "elasticsearch_user_iam_role_name" { + value = join(",", aws_iam_role.elasticsearch_user.*.name) + description = "The name of the IAM role to allow access to Elasticsearch cluster" +} + +output "elasticsearch_user_iam_role_arn" { + value = join(",", aws_iam_role.elasticsearch_user.*.arn) + description = "The ARN of the IAM role to allow access to Elasticsearch cluster" +} + +output "master_user_name" { + value = var.advanced_security_options_master_user_name + description = "The master user name to allow access to Elasticsearch cluster" +} + +output "master_user_password" { + value = random_password.master_user_password[0].result + description = "The master user name to allow access to Elasticsearch cluster" + sensitive = true +} \ No newline at end of file diff --git a/modules/aws-elasticsearch/variables.tf b/modules/aws-elasticsearch/variables.tf new file mode 100644 index 0000000..fa07320 --- /dev/null +++ b/modules/aws-elasticsearch/variables.tf @@ -0,0 +1,420 @@ +variable "domain_name" { + description = "Elastic Search domain name" + default = null + type = string +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "security_groups" { + type = list(string) + default = [] + description = "List of security group IDs to be allowed to connect to the cluster" +} + +variable "ingress_port_range_start" { + type = number + default = 0 + description = "Start number for allowed port range. (e.g. `443`)" +} + +variable "ingress_port_range_end" { + type = number + default = 65535 + description = "End number for allowed port range. (e.g. `443`)" +} + +variable "allowed_cidr_blocks" { + type = list(string) + default = [] + description = "List of CIDR blocks to be allowed to connect to the cluster" +} + +variable "vpc_enabled" { + type = bool + description = "Set to false if ES should be deployed outside of VPC." + default = true +} + +variable "vpc_id" { + type = string + description = "VPC ID" + default = null +} + +variable "subnet_ids" { + type = list(string) + description = "VPC Subnet IDs" + default = [] +} + +variable "dns_zone_id" { + type = string + default = "" + description = "Route53 DNS Zone ID to add hostname records for Elasticsearch domain and Kibana" +} + +variable "elasticsearch_version" { + type = string + default = "7.4" + description = "Version of Elasticsearch to deploy (_e.g._ `7.4`, `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5`" +} + +variable "instance_type" { + type = string + default = "t2.small.elasticsearch" + description = "Elasticsearch instance type for data nodes in the cluster" +} + +variable "instance_count" { + type = number + description = "Number of data nodes in the cluster" + default = 4 +} + +variable "warm_enabled" { + type = bool + default = false + description = "Whether AWS UltraWarm is enabled" +} + +variable "warm_count" { + type = number + default = 2 + description = "Number of UltraWarm nodes" +} + +variable "warm_type" { + type = string + default = "ultrawarm1.medium.elasticsearch" + description = "Type of UltraWarm nodes" +} + +variable "iam_role_arns" { + type = list(string) + default = [] + description = "List of IAM role ARNs to permit access to the Elasticsearch domain" +} + +variable "iam_role_permissions_boundary" { + type = string + default = null + description = "The ARN of the permissions boundary policy which will be attached to the Elasticsearch user role" +} + +variable "iam_authorizing_role_arns" { + type = list(string) + default = [] + description = "List of IAM role ARNs to permit to assume the Elasticsearch user role" +} + +variable "iam_actions" { + type = list(string) + default = [] + description = "List of actions to allow for the IAM roles, _e.g._ `es:ESHttpGet`, `es:ESHttpPut`, `es:ESHttpPost`" +} + +variable "zone_awareness_enabled" { + type = bool + default = true + description = "Enable zone awareness for Elasticsearch cluster" +} + +variable "availability_zone_count" { + type = number + default = 2 + description = "Number of Availability Zones for the domain to use." + + validation { + condition = contains([2, 3], var.availability_zone_count) + error_message = "The availibility zone count must be 2 or 3." + } +} + +variable "ebs_volume_size" { + type = number + description = "EBS volumes for data storage in GB" + default = 0 +} + +variable "ebs_volume_type" { + type = string + default = "gp2" + description = "Storage type of EBS volumes" +} + +variable "ebs_iops" { + type = number + default = 0 + description = "The baseline input/output (I/O) performance of EBS volumes attached to data nodes. Applicable only for the Provisioned IOPS EBS volume type" +} + +variable "encrypt_at_rest_enabled" { + type = bool + default = true + description = "Whether to enable encryption at rest" +} + +variable "encrypt_at_rest_kms_key_id" { + type = string + default = "" + description = "The KMS key ID to encrypt the Elasticsearch domain with. If not specified, then it defaults to using the AWS/Elasticsearch service KMS key" +} + +variable "domain_endpoint_options_enforce_https" { + type = bool + default = true + description = "Whether or not to require HTTPS" +} + +variable "domain_endpoint_options_tls_security_policy" { + type = string + default = "Policy-Min-TLS-1-0-2019-07" + description = "The name of the TLS security policy that needs to be applied to the HTTPS endpoint" +} + + +variable "log_publishing_index_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for INDEX_SLOW_LOGS is enabled or not" +} + +variable "log_publishing_search_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for SEARCH_SLOW_LOGS is enabled or not" +} + +variable "log_publishing_audit_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for AUDIT_LOGS is enabled or not" +} + +variable "log_publishing_application_enabled" { + type = bool + default = false + description = "Specifies whether log publishing option for ES_APPLICATION_LOGS is enabled or not" +} + +variable "log_publishing_index_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for INDEX_SLOW_LOGS needs to be published" +} + +variable "log_publishing_search_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for SEARCH_SLOW_LOGS needs to be published" +} + +variable "log_publishing_audit_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for AUDIT_LOGS needs to be published" +} + +variable "log_publishing_application_cloudwatch_log_group_arn" { + type = string + default = "" + description = "ARN of the CloudWatch log group to which log for ES_APPLICATION_LOGS needs to be published" +} + +variable "automated_snapshot_start_hour" { + type = number + description = "Hour at which automated snapshots are taken, in UTC" + default = 0 +} + +variable "dedicated_master_enabled" { + type = bool + default = false + description = "Indicates whether dedicated master nodes are enabled for the cluster" +} + +variable "dedicated_master_count" { + type = number + description = "Number of dedicated master nodes in the cluster" + default = 0 +} + +variable "dedicated_master_type" { + type = string + default = "t2.small.elasticsearch" + description = "Instance type of the dedicated master nodes in the cluster" +} + +variable "advanced_options" { + type = map(string) + default = {} + description = "Key-value string pairs to specify advanced configuration options" +} + +variable "elasticsearch_subdomain_name" { + type = string + default = "" + description = "The name of the subdomain for Elasticsearch in the DNS zone (_e.g._ `elasticsearch`, `ui`, `ui-es`, `search-ui`)" +} + +variable "kibana_subdomain_name" { + type = string + default = "" + description = "The name of the subdomain for Kibana in the DNS zone (_e.g._ `kibana`, `ui`, `ui-es`, `search-ui`, `kibana.elasticsearch`)" +} + +variable "create_iam_service_linked_role" { + type = bool + default = true + description = "Whether to create `AWSServiceRoleForAmazonElasticsearchService` service-linked role. Set it to `false` if you already have an ElasticSearch cluster created in the AWS account and AWSServiceRoleForAmazonElasticsearchService already exists. See https://github.com/terraform-providers/terraform-provider-aws/issues/5218 for more info" +} + +variable "node_to_node_encryption_enabled" { + type = bool + default = false + description = "Whether to enable node-to-node encryption" +} + +variable "iam_role_max_session_duration" { + type = number + default = 3600 + description = "The maximum session duration (in seconds) for the user role. Can have a value from 1 hour to 12 hours" +} + +variable "cognito_authentication_enabled" { + type = bool + default = false + description = "Whether to enable Amazon Cognito authentication with Kibana" +} + +variable "cognito_user_pool_id" { + type = string + default = "" + description = "The ID of the Cognito User Pool to use" +} + +variable "cognito_identity_pool_id" { + type = string + default = "" + description = "The ID of the Cognito Identity Pool to use" +} + +variable "cognito_iam_role_arn" { + type = string + default = "" + description = "ARN of the IAM role that has the AmazonESCognitoAccess policy attached" +} + +variable "aws_ec2_service_name" { + type = list(string) + default = ["ec2.amazonaws.com"] + description = "AWS EC2 Service Name" +} + +variable "domain_hostname_enabled" { + type = bool + description = "Explicit flag to enable creating a DNS hostname for ES. If `true`, then `var.dns_zone_id` is required." + default = false +} + +variable "kibana_hostname_enabled" { + type = bool + description = "Explicit flag to enable creating a DNS hostname for Kibana. If `true`, then `var.dns_zone_id` is required." + default = false +} + +variable "advanced_security_options_enabled" { + type = bool + default = false + description = "AWS Elasticsearch Kibana enchanced security plugin enabling (forces new resource)" +} + +variable "advanced_security_options_internal_user_database_enabled" { + type = bool + default = false + description = "Whether to enable or not internal Kibana user database for ELK OpenDistro security plugin" +} + +variable "advanced_security_options_master_user_arn" { + type = string + default = "" + description = "ARN of IAM user who is to be mapped to be Kibana master user (applicable if advanced_security_options_internal_user_database_enabled set to false)" +} + +variable "advanced_security_options_master_user_name" { + type = string + default = "" + description = "Master user username (applicable if advanced_security_options_internal_user_database_enabled set to true)" +} + +variable "advanced_security_options_master_user_password" { + type = string + default = "" + description = "Master user password (applicable if advanced_security_options_internal_user_database_enabled set to true)" +} + +variable "custom_endpoint_enabled" { + type = bool + description = "Whether to enable custom endpoint for the Elasticsearch domain." + default = false +} + +variable "custom_endpoint" { + type = string + description = "Fully qualified domain for custom endpoint." + default = "" +} + +variable "custom_endpoint_certificate_arn" { + type = string + description = "ACM certificate ARN for custom endpoint." + default = "" +} + + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +variable "saml_authentication" { + description = "SAML Authetication enable/disable for Opensearch Dashboard" + type = bool + default = false +} + +variable "master_backend_role" { + description = "(Optional) This backend role from the SAML IdP receives full permissions to the cluster, equivalent to a new master user." + type = string +} + +variable "saml_entity_id" { + description = "(Required) The unique Entity ID of the application in SAML Identity Provider." + type = string +} + +variable "saml_metadata_xmlfile_path" { + description = "(Required) The path of Metadata of the SAML application in xml format." + type = string +} + +variable "saml_roles_key" { + description = "(Optional) Element of the SAML assertion to use for backend roles. Default is roles." + type = string + default = "roles" +} \ No newline at end of file diff --git a/modules/aws-elasticsearch/versions.tf b/modules/aws-elasticsearch/versions.tf new file mode 100644 index 0000000..588d1c7 --- /dev/null +++ b/modules/aws-elasticsearch/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.35.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-guardduty-master/README.md b/modules/aws-guardduty-master/README.md new file mode 100644 index 0000000..8f3c973 --- /dev/null +++ b/modules/aws-guardduty-master/README.md @@ -0,0 +1,407 @@ + + +# terraform-aws-guardduty [![Latest Release](https://img.shields.io/github/release/cloudposse/terraform-aws-guardduty.svg)](https://github.com/cloudposse/terraform-aws-guardduty/releases/latest) [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) [![Discourse Forum](https://img.shields.io/discourse/https/ask.sweetops.com/posts.svg)](https://ask.sweetops.com/) + + +[![README Header][readme_header_img]][readme_header_link] + +[![Cloud Posse][logo]](https://cpco.io/homepage) + + + +This module enables AWS GuardDuty in one region of one account and optionally sets up an SNS topic to receive +notifications of its findings. + +--- + +This project is part of our comprehensive ["SweetOps"](https://cpco.io/sweetops) approach towards DevOps. +[][share_email] +[][share_googleplus] +[][share_facebook] +[][share_reddit] +[][share_linkedin] +[][share_twitter] + + +[![Terraform Open Source Modules](https://docs.cloudposse.com/images/terraform-open-source-modules.svg)][terraform_modules] + + + +It's 100% Open Source and licensed under the [APACHE2](LICENSE). + + + + + + + +We literally have [*hundreds of terraform modules*][terraform_modules] that are Open Source and well-maintained. Check them out! + + + + + + +## Security & Compliance [](https://bridgecrew.io/) + +Security scanning is graciously provided by Bridgecrew. Bridgecrew is the leading fully hosted, cloud-native solution providing continuous Terraform security and compliance. + +| Benchmark | Description | +|--------|---------------| +| [![Infrastructure Security](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/general)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=INFRASTRUCTURE+SECURITY) | Infrastructure Security Compliance | +| [![CIS KUBERNETES](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/cis_kubernetes)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=CIS+KUBERNETES+V1.5) | Center for Internet Security, KUBERNETES Compliance | +| [![CIS AWS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/cis_aws)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=CIS+AWS+V1.2) | Center for Internet Security, AWS Compliance | +| [![CIS AZURE](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/cis_azure)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=CIS+AZURE+V1.1) | Center for Internet Security, AZURE Compliance | +| [![PCI-DSS](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/pci)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=PCI-DSS+V3.2) | Payment Card Industry Data Security Standards Compliance | +| [![NIST-800-53](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/nist)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=NIST-800-53) | National Institute of Standards and Technology Compliance | +| [![ISO27001](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/iso)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=ISO27001) | Information Security Management System, ISO/IEC 27001 Compliance | +| [![SOC2](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/soc2)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=SOC2)| Service Organization Control 2 Compliance | +| [![CIS GCP](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/cis_gcp)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=CIS+GCP+V1.1) | Center for Internet Security, GCP Compliance | +| [![HIPAA](https://www.bridgecrew.cloud/badges/github/cloudposse/terraform-aws-guardduty/hipaa)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=cloudposse%2Fterraform-aws-guardduty&benchmark=HIPAA) | Health Insurance Portability and Accountability Compliance | + + + +## Usage + + +**IMPORTANT:** We do not pin modules to versions in our examples because of the +difficulty of keeping the versions in the documentation in sync with the latest released versions. +We highly recommend that in your code you pin the version to the exact version you are +using so that your infrastructure remains stable, and update versions in a +systematic way so that they do not catch you by surprise. + +Also, because of a bug in the Terraform registry ([hashicorp/terraform#21417](https://github.com/hashicorp/terraform/issues/21417)), +the registry shows many of our inputs as required when in fact they are optional. +The table below correctly indicates which inputs are required. + + +Here's how to invoke this example module in your projects + +```hcl +module "example" { + source = "https://github.com/cloudposse/terraform-aws-guardduty.git?ref=master" + + create_sns_topic = true + subscribers = { + opsgenie = { + protocol = "https" + endpoint = "https://api.example.com/v1/" + endpoint_auto_confirms = true + } + } +} +``` + + + + +## Examples + +Here is an example of using this module: +- [`examples/complete`](https://github.com/cloudposse/terraform-aws-guardduty/tree/master/examples/complete) - complete example of using this module + + + + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [findings\_label](#module\_findings\_label) | cloudposse/label/null | 0.25.0 | +| [sns\_topic](#module\_sns\_topic) | cloudposse/sns-topic/aws | 0.20.1 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_event_rule.findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | +| [aws_cloudwatch_event_target.imported_findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | +| [aws_guardduty_detector.guardduty](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/guardduty_detector) | resource | +| [aws_sns_topic_policy.sns_topic_publish_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | +| [aws_iam_policy_document.sns_topic_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [cloudwatch\_event\_rule\_pattern\_detail\_type](#input\_cloudwatch\_event\_rule\_pattern\_detail\_type) | The detail-type pattern used to match events that will be sent to SNS.

For more information, see:
https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html
https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html
https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html | `string` | `"GuardDuty Finding"` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_sns\_topic](#input\_create\_sns\_topic) | Flag to indicate whether an SNS topic should be created for notifications.
If you want to send findings to a new SNS topic, set this to true and provide a valid configuration for subscribers. | `bool` | `false` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_cloudwatch](#input\_enable\_cloudwatch) | Flag to indicate whether an CloudWatch logging should be enabled for GuardDuty | `bool` | `false` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [finding\_publishing\_frequency](#input\_finding\_publishing\_frequency) | The frequency of notifications sent for finding occurrences. If the detector is a GuardDuty member account, the value
is determined by the GuardDuty master account and cannot be modified, otherwise it defaults to SIX\_HOURS.

For standalone and GuardDuty master accounts, it must be configured in Terraform to enable drift detection.
Valid values for standalone and master accounts: FIFTEEN\_MINUTES, ONE\_HOUR, SIX\_HOURS."

For more information, see:
https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html#guardduty_findings_cloudwatch_notification_frequency | `string` | `null` | no | +| [findings\_notification\_arn](#input\_findings\_notification\_arn) | The ARN for an SNS topic to send findings notifications to. This is only used if create\_sns\_topic is false.
If you want to send findings to an existing SNS topic, set the value of this to the ARN of the existing topic and set
create\_sns\_topic to false. | `string` | `null` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [s3\_protection\_enabled](#input\_s3\_protection\_enabled) | Flag to indicate whether S3 protection will be turned on in GuardDuty.

For more information, see:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/guardduty_detector | `bool` | `false` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [subscribers](#input\_subscribers) | A map of subscription configurations for SNS topics

For more information, see:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription#argument-reference

protocol:
The protocol to use. The possible values for this are: sqs, sms, lambda, application. (http or https are partially
supported, see link) (email is an option but is unsupported in terraform, see link).
endpoint:
The endpoint to send data to, the contents will vary with the protocol. (see link for more information)
endpoint\_auto\_confirms:
Boolean indicating whether the end point is capable of auto confirming subscription e.g., PagerDuty. Default is
false
raw\_message\_delivery:
Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property).
Default is false |
map(object({
protocol = string
endpoint = string
endpoint_auto_confirms = bool
raw_message_delivery = bool
}))
| `{}` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [guardduty\_detector](#output\_guardduty\_detector) | GuardDuty detector | +| [sns\_topic](#output\_sns\_topic) | SNS topic | +| [sns\_topic\_subscriptions](#output\_sns\_topic\_subscriptions) | SNS topic subscriptions | + + + + +## Share the Love + +Like this project? Please give it a ★ on [our GitHub](https://github.com/cloudposse/terraform-aws-guardduty)! (it helps us **a lot**) + +Are you using this project or any of our other projects? Consider [leaving a testimonial][testimonial]. =) + + + +## Related Projects + +Check out these related projects. + +- [terraform-null-label](https://github.com/cloudposse/terraform-null-label) - Terraform module designed to generate consistent names and tags for resources. Use terraform-null-label to implement a strict naming convention. +- [terraform-aws-security-hub](https://github.com/cloudposse/terraform-aws-security-hub) - Terraform module to deploy AWS Security Hub +- [terraform-aws-config](https://github.com/cloudposse/terraform-aws-config) - Terraform module to enable AWS Config and optionally sets up an SNS topic to receive notifications of its findings. + + +## References + +For additional context, refer to some of these links. + +- [Terraform Standard Module Structure](https://www.terraform.io/docs/modules/index.html#standard-module-structure) - HashiCorp's standard module structure is a file and directory layout we recommend for reusable modules distributed in separate repositories. +- [Terraform Module Requirements](https://www.terraform.io/docs/registry/modules/publish.html#requirements) - HashiCorp's guidance on all the requirements for publishing a module. Meeting the requirements for publishing a module is extremely easy. +- [Terraform `random_integer` Resource](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) - The resource random_integer generates random values from a given range, described by the min and max attributes of a given resource. +- [Terraform Version Pinning](https://www.terraform.io/docs/configuration/terraform.html#specifying-a-required-terraform-version) - The required_version setting can be used to constrain which versions of the Terraform CLI can be used with your configuration + + +## Help + +**Got a question?** We got answers. + +File a GitHub [issue](https://github.com/cloudposse/terraform-aws-guardduty/issues), send us an [email][email] or join our [Slack Community][slack]. + +[![README Commercial Support][readme_commercial_support_img]][readme_commercial_support_link] + +## DevOps Accelerator for Startups + + +We are a [**DevOps Accelerator**][commercial_support]. We'll help you build your cloud infrastructure from the ground up so you can own it. Then we'll show you how to operate it and stick around for as long as you need us. + +[![Learn More](https://img.shields.io/badge/learn%20more-success.svg?style=for-the-badge)][commercial_support] + +Work directly with our team of DevOps experts via email, slack, and video conferencing. + +We deliver 10x the value for a fraction of the cost of a full-time engineer. Our track record is not even funny. If you want things done right and you need it done FAST, then we're your best bet. + +- **Reference Architecture.** You'll get everything you need from the ground up built using 100% infrastructure as code. +- **Release Engineering.** You'll have end-to-end CI/CD with unlimited staging environments. +- **Site Reliability Engineering.** You'll have total visibility into your apps and microservices. +- **Security Baseline.** You'll have built-in governance with accountability and audit logs for all changes. +- **GitOps.** You'll be able to operate your infrastructure via Pull Requests. +- **Training.** You'll receive hands-on training so your team can operate what we build. +- **Questions.** You'll have a direct line of communication between our teams via a Shared Slack channel. +- **Troubleshooting.** You'll get help to triage when things aren't working. +- **Code Reviews.** You'll receive constructive feedback on Pull Requests. +- **Bug Fixes.** We'll rapidly work with you to fix any bugs in our projects. + +## Slack Community + +Join our [Open Source Community][slack] on Slack. It's **FREE** for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totally *sweet* infrastructure. + +## Discourse Forums + +Participate in our [Discourse Forums][discourse]. Here you'll find answers to commonly asked questions. Most questions will be related to the enormous number of projects we support on our GitHub. Come here to collaborate on answers, find solutions, and get ideas about the products and services we value. It only takes a minute to get started! Just sign in with SSO using your GitHub account. + +## Newsletter + +Sign up for [our newsletter][newsletter] that covers everything on our technology radar. Receive updates on what we're up to on GitHub as well as awesome new projects we discover. + +## Office Hours + +[Join us every Wednesday via Zoom][office_hours] for our weekly "Lunch & Learn" sessions. It's **FREE** for everyone! + +[![zoom](https://img.cloudposse.com/fit-in/200x200/https://cloudposse.com/wp-content/uploads/2019/08/Powered-by-Zoom.png")][office_hours] + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-guardduty/issues) to report any bugs or file feature requests. + +### Developing + +If you are interested in being a contributor and want to get involved in developing this project or [help out](https://cpco.io/help-out) with our other projects, we would love to hear from you! Shoot us an [email][email]. + +In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull Request** so that we can review your changes + +**NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request! + + + +## Copyrights + +Copyright © 2020-2022 [Cloud Posse, LLC](https://cloudposse.com) + + + + + +## License + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +See [LICENSE](LICENSE) for full details. + +```text +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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 + + https://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. +``` + + + + + + + + + +## Trademarks + +All other trademarks referenced herein are the property of their respective owners. + +## About + +This project is maintained and funded by [Cloud Posse, LLC][website]. Like it? Please let us know by [leaving a testimonial][testimonial]! + +[![Cloud Posse][logo]][website] + +We're a [DevOps Professional Services][hire] company based in Los Angeles, CA. We ❤️ [Open Source Software][we_love_open_source]. + +We offer [paid support][commercial_support] on all of our projects. + +Check out [our other projects][github], [follow us on twitter][twitter], [apply for a job][jobs], or [hire us][hire] to help with your cloud strategy and implementation. + + + +### Contributors + + +| [![Matt Calhoun][mcalhoun_avatar]][mcalhoun_homepage]
[Matt Calhoun][mcalhoun_homepage] | +|---| + + + [mcalhoun_homepage]: https://github.com/mcalhoun + [mcalhoun_avatar]: https://img.cloudposse.com/150x150/https://github.com/mcalhoun.png + +[![README Footer][readme_footer_img]][readme_footer_link] +[![Beacon][beacon]][website] + + [logo]: https://cloudposse.com/logo-300x69.svg + [docs]: https://cpco.io/docs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=docs + [website]: https://cpco.io/homepage?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=website + [github]: https://cpco.io/github?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=github + [jobs]: https://cpco.io/jobs?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=jobs + [hire]: https://cpco.io/hire?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=hire + [slack]: https://cpco.io/slack?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=slack + [linkedin]: https://cpco.io/linkedin?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=linkedin + [twitter]: https://cpco.io/twitter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=twitter + [testimonial]: https://cpco.io/leave-testimonial?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=testimonial + [office_hours]: https://cloudposse.com/office-hours?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=office_hours + [newsletter]: https://cpco.io/newsletter?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=newsletter + [discourse]: https://ask.sweetops.com/?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=discourse + [email]: https://cpco.io/email?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=email + [commercial_support]: https://cpco.io/commercial-support?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=commercial_support + [we_love_open_source]: https://cpco.io/we-love-open-source?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=we_love_open_source + [terraform_modules]: https://cpco.io/terraform-modules?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=terraform_modules + [readme_header_img]: https://cloudposse.com/readme/header/img + [readme_header_link]: https://cloudposse.com/readme/header/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=readme_header_link + [readme_footer_img]: https://cloudposse.com/readme/footer/img + [readme_footer_link]: https://cloudposse.com/readme/footer/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=readme_footer_link + [readme_commercial_support_img]: https://cloudposse.com/readme/commercial-support/img + [readme_commercial_support_link]: https://cloudposse.com/readme/commercial-support/link?utm_source=github&utm_medium=readme&utm_campaign=cloudposse/terraform-aws-guardduty&utm_content=readme_commercial_support_link + [share_twitter]: https://twitter.com/intent/tweet/?text=terraform-aws-guardduty&url=https://github.com/cloudposse/terraform-aws-guardduty + [share_linkedin]: https://www.linkedin.com/shareArticle?mini=true&title=terraform-aws-guardduty&url=https://github.com/cloudposse/terraform-aws-guardduty + [share_reddit]: https://reddit.com/submit/?url=https://github.com/cloudposse/terraform-aws-guardduty + [share_facebook]: https://facebook.com/sharer/sharer.php?u=https://github.com/cloudposse/terraform-aws-guardduty + [share_googleplus]: https://plus.google.com/share?url=https://github.com/cloudposse/terraform-aws-guardduty + [share_email]: mailto:?subject=terraform-aws-guardduty&body=https://github.com/cloudposse/terraform-aws-guardduty + [beacon]: https://ga-beacon.cloudposse.com/UA-76589703-4/cloudposse/terraform-aws-guardduty?pixel&cs=github&cm=readme&an=terraform-aws-guardduty diff --git a/modules/aws-guardduty-master/README.yaml b/modules/aws-guardduty-master/README.yaml new file mode 100644 index 0000000..14d62fd --- /dev/null +++ b/modules/aws-guardduty-master/README.yaml @@ -0,0 +1,120 @@ +--- +# +# This is the canonical configuration for the `README.md` +# Run `make readme` to rebuild the `README.md` +# + +# Name of this project +name: terraform-aws-guardduty + +# Logo for this project +#logo: docs/logo.png + +# License of this project +license: "APACHE2" + +# Copyrights +copyrights: + - name: "Cloud Posse, LLC" + url: "https://cloudposse.com" + year: "2020" + +# Canonical GitHub repo +github_repo: cloudposse/terraform-aws-guardduty + +# Badges to display +badges: + - name: "Latest Release" + image: "https://img.shields.io/github/release/cloudposse/terraform-aws-guardduty.svg" + url: "https://github.com/cloudposse/terraform-aws-guardduty/releases/latest" + - name: "Slack Community" + image: "https://slack.cloudposse.com/badge.svg" + url: "https://slack.cloudposse.com" + - name: "Discourse Forum" + image: "https://img.shields.io/discourse/https/ask.sweetops.com/posts.svg" + url: "https://ask.sweetops.com/" + +# List any related terraform modules that this module may be used with or that this module depends on. +related: + - name: "terraform-null-label" + description: + "Terraform module designed to generate consistent names and tags for + resources. Use terraform-null-label to implement a strict naming + convention." + url: "https://github.com/cloudposse/terraform-null-label" + - name: "terraform-aws-security-hub" + description: "Terraform module to deploy AWS Security Hub" + url: "https://github.com/cloudposse/terraform-aws-security-hub" + - name: "terraform-aws-config" + description: "Terraform module to enable AWS Config and optionally sets up an SNS topic to receive notifications of its findings." + url: "https://github.com/cloudposse/terraform-aws-config" + +# List any resources helpful for someone to get started. For example, link to the hashicorp documentation or AWS documentation. +references: + - name: "Terraform Standard Module Structure" + description: + "HashiCorp's standard module structure is a file and directory layout we + recommend for reusable modules distributed in separate repositories." + url: "https://www.terraform.io/docs/modules/index.html#standard-module-structure" + - name: "Terraform Module Requirements" + description: + "HashiCorp's guidance on all the requirements for publishing a module. + Meeting the requirements for publishing a module is extremely easy." + url: "https://www.terraform.io/docs/registry/modules/publish.html#requirements" + - name: "Terraform `random_integer` Resource" + description: + "The resource random_integer generates random values from a given range, + described by the min and max attributes of a given resource." + url: "https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer" + - name: "Terraform Version Pinning" + description: + "The required_version setting can be used to constrain which versions of + the Terraform CLI can be used with your configuration" + url: "https://www.terraform.io/docs/configuration/terraform.html#specifying-a-required-terraform-version" + +# Short description of this project +description: |- + This module enables AWS GuardDuty in one region of one account and optionally sets up an SNS topic to receive + notifications of its findings. + +# Introduction to the project +#introduction: |- +# This is an introduction. + +# How to use this module. Should be an easy example to copy and paste. +usage: |- + Here's how to invoke this example module in your projects + + ```hcl + module "example" { + source = "https://github.com/cloudposse/terraform-aws-guardduty.git?ref=master" + + create_sns_topic = true + subscribers = { + opsgenie = { + protocol = "https" + endpoint = "https://api.example.com/v1/" + endpoint_auto_confirms = true + } + } + } + ``` + +# Example usage +examples: |- + Here is an example of using this module: + - [`examples/complete`](https://github.com/cloudposse/terraform-aws-guardduty/tree/master/examples/complete) - complete example of using this module + +# How to get started quickly +#quickstart: |- +# Here's how to get started... + +# Other files to include in this README from the project folder +include: + - "docs/targets.md" + - "docs/terraform.md" + +# Contributors to this project +contributors: + - name: "Matt Calhoun" + github: "mcalhoun" diff --git a/modules/aws-guardduty-master/context.tf b/modules/aws-guardduty-master/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-guardduty-master/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-guardduty-master/docs/targets.md b/modules/aws-guardduty-master/docs/targets.md new file mode 100644 index 0000000..3dce8b3 --- /dev/null +++ b/modules/aws-guardduty-master/docs/targets.md @@ -0,0 +1,12 @@ + +## Makefile Targets +```text +Available targets: + + help Help screen + help/all Display help for all targets + help/short This help short screen + lint Lint terraform code + +``` + diff --git a/modules/aws-guardduty-master/docs/terraform.md b/modules/aws-guardduty-master/docs/terraform.md new file mode 100644 index 0000000..9d21183 --- /dev/null +++ b/modules/aws-guardduty-master/docs/terraform.md @@ -0,0 +1,70 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [findings\_label](#module\_findings\_label) | cloudposse/label/null | 0.25.0 | +| [sns\_topic](#module\_sns\_topic) | cloudposse/sns-topic/aws | 0.20.1 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_event_rule.findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | +| [aws_cloudwatch_event_target.imported_findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | +| [aws_guardduty_detector.guardduty](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/guardduty_detector) | resource | +| [aws_sns_topic_policy.sns_topic_publish_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | +| [aws_iam_policy_document.sns_topic_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [cloudwatch\_event\_rule\_pattern\_detail\_type](#input\_cloudwatch\_event\_rule\_pattern\_detail\_type) | The detail-type pattern used to match events that will be sent to SNS.

For more information, see:
https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html
https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html
https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html | `string` | `"GuardDuty Finding"` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_sns\_topic](#input\_create\_sns\_topic) | Flag to indicate whether an SNS topic should be created for notifications.
If you want to send findings to a new SNS topic, set this to true and provide a valid configuration for subscribers. | `bool` | `false` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enable\_cloudwatch](#input\_enable\_cloudwatch) | Flag to indicate whether an CloudWatch logging should be enabled for GuardDuty | `bool` | `false` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [finding\_publishing\_frequency](#input\_finding\_publishing\_frequency) | The frequency of notifications sent for finding occurrences. If the detector is a GuardDuty member account, the value
is determined by the GuardDuty master account and cannot be modified, otherwise it defaults to SIX\_HOURS.

For standalone and GuardDuty master accounts, it must be configured in Terraform to enable drift detection.
Valid values for standalone and master accounts: FIFTEEN\_MINUTES, ONE\_HOUR, SIX\_HOURS."

For more information, see:
https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html#guardduty_findings_cloudwatch_notification_frequency | `string` | `null` | no | +| [findings\_notification\_arn](#input\_findings\_notification\_arn) | The ARN for an SNS topic to send findings notifications to. This is only used if create\_sns\_topic is false.
If you want to send findings to an existing SNS topic, set the value of this to the ARN of the existing topic and set
create\_sns\_topic to false. | `string` | `null` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [s3\_protection\_enabled](#input\_s3\_protection\_enabled) | Flag to indicate whether S3 protection will be turned on in GuardDuty.

For more information, see:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/guardduty_detector | `bool` | `false` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [subscribers](#input\_subscribers) | A map of subscription configurations for SNS topics

For more information, see:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription#argument-reference

protocol:
The protocol to use. The possible values for this are: sqs, sms, lambda, application. (http or https are partially
supported, see link) (email is an option but is unsupported in terraform, see link).
endpoint:
The endpoint to send data to, the contents will vary with the protocol. (see link for more information)
endpoint\_auto\_confirms:
Boolean indicating whether the end point is capable of auto confirming subscription e.g., PagerDuty. Default is
false
raw\_message\_delivery:
Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property).
Default is false |
map(object({
protocol = string
endpoint = string
endpoint_auto_confirms = bool
raw_message_delivery = bool
}))
| `{}` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [guardduty\_detector](#output\_guardduty\_detector) | GuardDuty detector | +| [sns\_topic](#output\_sns\_topic) | SNS topic | +| [sns\_topic\_subscriptions](#output\_sns\_topic\_subscriptions) | SNS topic subscriptions | + diff --git a/modules/aws-guardduty-master/examples/complete/context.tf b/modules/aws-guardduty-master/examples/complete/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-guardduty-master/examples/complete/fixtures.us-east-2.tfvars b/modules/aws-guardduty-master/examples/complete/fixtures.us-east-2.tfvars new file mode 100644 index 0000000..7b34058 --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/fixtures.us-east-2.tfvars @@ -0,0 +1,6 @@ +region = "us-east-2" +namespace = "eg" +environment = "ue2" +stage = "test" + +create_sns_topic = true diff --git a/modules/aws-guardduty-master/examples/complete/main.tf b/modules/aws-guardduty-master/examples/complete/main.tf new file mode 100644 index 0000000..38f591e --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/main.tf @@ -0,0 +1,11 @@ +provider "aws" { + region = var.region +} + +module "guardduty" { + source = "../.." + + create_sns_topic = var.create_sns_topic + + context = module.this.context +} diff --git a/modules/aws-guardduty-master/examples/complete/outputs.tf b/modules/aws-guardduty-master/examples/complete/outputs.tf new file mode 100644 index 0000000..81aa87b --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/outputs.tf @@ -0,0 +1,6 @@ +output "guardduty_detector" { + value = module.guardduty.guardduty_detector +} +output "sns_topic" { + value = module.guardduty.sns_topic +} diff --git a/modules/aws-guardduty-master/examples/complete/variables.tf b/modules/aws-guardduty-master/examples/complete/variables.tf new file mode 100644 index 0000000..72878c5 --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/variables.tf @@ -0,0 +1,10 @@ +variable "region" { + type = string + description = "AWS region" +} + +variable "create_sns_topic" { + description = "Flag to indicate whether an SNS topic should be created for notifications." + type = bool + default = false +} diff --git a/modules/aws-guardduty-master/examples/complete/versions.tf b/modules/aws-guardduty-master/examples/complete/versions.tf new file mode 100644 index 0000000..41f439f --- /dev/null +++ b/modules/aws-guardduty-master/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2" + } + } +} diff --git a/modules/aws-guardduty-master/main.tf b/modules/aws-guardduty-master/main.tf new file mode 100644 index 0000000..35444f2 --- /dev/null +++ b/modules/aws-guardduty-master/main.tf @@ -0,0 +1,96 @@ +#----------------------------------------------------------------------------------------------------------------------- +# Subscribe the Acccount to GuardDuty +#----------------------------------------------------------------------------------------------------------------------- +resource "aws_guardduty_detector" "guardduty" { + enable = module.this.enabled + finding_publishing_frequency = var.finding_publishing_frequency + + datasources { + s3_logs { + enable = var.s3_protection_enabled + } + } +} + +#----------------------------------------------------------------------------------------------------------------------- +# Optionally configure Event Bridge Rules and SNS subscriptions +# https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cwe-integration-types.html +# https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/resource-based-policies-cwe.html#sns-permissions +#----------------------------------------------------------------------------------------------------------------------- +module "sns_topic" { + + source = "cloudposse/sns-topic/aws" + version = "0.20.1" + count = local.create_sns_topic ? 1 : 0 + + subscribers = var.subscribers + sqs_dlq_enabled = false + + attributes = concat(module.this.attributes, ["guardduty"]) + context = module.this.context +} + +module "findings_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + attributes = concat(module.this.attributes, ["guardduty", "findings"]) + context = module.this.context +} + +resource "aws_sns_topic_policy" "sns_topic_publish_policy" { + count = module.this.enabled && local.create_sns_topic ? 1 : 0 + arn = local.findings_notification_arn + policy = data.aws_iam_policy_document.sns_topic_policy[0].json +} + +data "aws_iam_policy_document" "sns_topic_policy" { + count = module.this.enabled && local.create_sns_topic ? 1 : 0 + policy_id = "GuardDutyPublishToSNS" + statement { + sid = "" + actions = [ + "sns:Publish" + ] + principals { + type = "Service" + identifiers = ["cloudwatch.amazonaws.com"] + } + resources = [module.sns_topic[0].sns_topic.arn] + effect = "Allow" + } +} + +resource "aws_cloudwatch_event_rule" "findings" { + count = local.enable_cloudwatch == true ? 1 : 0 + name = module.findings_label.id + description = "GuardDuty Findings" + tags = module.this.tags + + event_pattern = jsonencode( + { + "source" : [ + "aws.guardduty" + ], + "detail-type" : [ + var.cloudwatch_event_rule_pattern_detail_type + ] + } + ) +} + +resource "aws_cloudwatch_event_target" "imported_findings" { + count = local.enable_notifications == true ? 1 : 0 + rule = aws_cloudwatch_event_rule.findings[0].name + arn = local.findings_notification_arn +} + +#----------------------------------------------------------------------------------------------------------------------- +# Locals and Data References +#----------------------------------------------------------------------------------------------------------------------- +locals { + enable_cloudwatch = module.this.enabled && (var.enable_cloudwatch || local.enable_notifications) + enable_notifications = module.this.enabled && (var.create_sns_topic || var.findings_notification_arn != null) + create_sns_topic = module.this.enabled && var.create_sns_topic + findings_notification_arn = local.enable_notifications ? (var.findings_notification_arn != null ? var.findings_notification_arn : module.sns_topic[0].sns_topic.arn) : null +} diff --git a/modules/aws-guardduty-master/outputs.tf b/modules/aws-guardduty-master/outputs.tf new file mode 100644 index 0000000..e36e09a --- /dev/null +++ b/modules/aws-guardduty-master/outputs.tf @@ -0,0 +1,14 @@ +output "guardduty_detector" { + description = "GuardDuty detector" + value = module.this.enabled ? aws_guardduty_detector.guardduty : null +} + +output "sns_topic" { + description = "SNS topic" + value = local.create_sns_topic ? module.sns_topic[0].sns_topic : null +} + +output "sns_topic_subscriptions" { + description = "SNS topic subscriptions" + value = local.create_sns_topic ? module.sns_topic[0].aws_sns_topic_subscriptions : null +} diff --git a/modules/aws-guardduty-master/variables.tf b/modules/aws-guardduty-master/variables.tf new file mode 100644 index 0000000..0d743c2 --- /dev/null +++ b/modules/aws-guardduty-master/variables.tf @@ -0,0 +1,94 @@ +variable "enable_cloudwatch" { + description = <<-DOC + Flag to indicate whether an CloudWatch logging should be enabled for GuardDuty + DOC + type = bool + default = false +} + +variable "cloudwatch_event_rule_pattern_detail_type" { + description = <<-DOC + The detail-type pattern used to match events that will be sent to SNS. + + For more information, see: + https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html + https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html + https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html + DOC + type = string + default = "GuardDuty Finding" +} + +variable "create_sns_topic" { + description = <<-DOC + Flag to indicate whether an SNS topic should be created for notifications. + If you want to send findings to a new SNS topic, set this to true and provide a valid configuration for subscribers. + DOC + + type = bool + default = false +} + +variable "subscribers" { + type = map(object({ + protocol = string + endpoint = string + endpoint_auto_confirms = bool + raw_message_delivery = bool + })) + description = <<-DOC + A map of subscription configurations for SNS topics + + For more information, see: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription#argument-reference + + protocol: + The protocol to use. The possible values for this are: sqs, sms, lambda, application. (http or https are partially + supported, see link) (email is an option but is unsupported in terraform, see link). + endpoint: + The endpoint to send data to, the contents will vary with the protocol. (see link for more information) + endpoint_auto_confirms: + Boolean indicating whether the end point is capable of auto confirming subscription e.g., PagerDuty. Default is + false + raw_message_delivery: + Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property). + Default is false + DOC + default = {} +} + +variable "findings_notification_arn" { + description = <<-DOC + The ARN for an SNS topic to send findings notifications to. This is only used if create_sns_topic is false. + If you want to send findings to an existing SNS topic, set the value of this to the ARN of the existing topic and set + create_sns_topic to false. + DOC + default = null + type = string +} + +variable "finding_publishing_frequency" { + description = <<-DOC + The frequency of notifications sent for finding occurrences. If the detector is a GuardDuty member account, the value + is determined by the GuardDuty master account and cannot be modified, otherwise it defaults to SIX_HOURS. + + For standalone and GuardDuty master accounts, it must be configured in Terraform to enable drift detection. + Valid values for standalone and master accounts: FIFTEEN_MINUTES, ONE_HOUR, SIX_HOURS." + + For more information, see: + https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html#guardduty_findings_cloudwatch_notification_frequency + DOC + type = string + default = null +} + +variable "s3_protection_enabled" { + description = <<-DOC + Flag to indicate whether S3 protection will be turned on in GuardDuty. + + For more information, see: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/guardduty_detector + DOC + type = bool + default = false +} diff --git a/modules/aws-guardduty-master/versions.tf b/modules/aws-guardduty-master/versions.tf new file mode 100644 index 0000000..20949c2 --- /dev/null +++ b/modules/aws-guardduty-master/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3" + } + } +} diff --git a/modules/aws-iam/README.md b/modules/aws-iam/README.md new file mode 100644 index 0000000..83a0f0d --- /dev/null +++ b/modules/aws-iam/README.md @@ -0,0 +1,338 @@ +# AWS Identity and Access Management (IAM) Terraform module + +## Features + +1. **Cross-account access.** Define IAM roles using `iam_assumable_role` or `iam_assumable_roles` submodules in "resource AWS accounts (prod, staging, dev)" and IAM groups and users using `iam-group-with-assumable-roles-policy` submodule in "IAM AWS Account" to setup access controls between accounts. See [iam-group-with-assumable-roles-policy example](https://github.com/kloia/platform-modules/tree/main/terraform-aws-iam/examples/iam-group-with-assumable-roles-policy) for more details. +2. **Individual IAM resources (users, roles, policies).** See usage snippets and [examples](https://github.com/kloia/platform-modules/tree/main/terraform-aws-iam/examples) listed below. + +## Usage + +`iam-account`: + +```hcl +module "iam_account" { + source = "terraform-aws-modules/iam/aws//modules/iam-account" + + account_alias = "awesome-company" + + minimum_password_length = 37 + require_numbers = false +} +``` + +`iam-assumable-role`: + +```hcl +module "iam_assumable_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + "arn:aws:iam::111111111111:user/kloia", + ] + + create_role = true + + role_name = "custom" + role_requires_mfa = true + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess", + ] + number_of_custom_role_policy_arns = 2 +} +``` + +`iam-assumable-role-with-oidc`: + +```hcl +module "iam_assumable_role_with_oidc" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + + create_role = true + + role_name = "role-with-oidc" + + tags = { + Role = "role-with-oidc" + } + + provider_url = "oidc.eks.eu-west-1.amazonaws.com/id/BA9E170D464AF7B92084EF72A69B9DC8" + + role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + ] + number_of_role_policy_arns = 1 +} +``` + +`iam-assumable-role-with-saml`: + +```hcl +module "iam_assumable_role_with_saml" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-saml" + + create_role = true + + role_name = "role-with-saml" + + tags = { + Role = "role-with-saml" + } + + provider_id = "arn:aws:iam::235367859851:saml-provider/idp_saml" + + role_policy_arns = [ + "arn:aws:iam::aws:policy/ReadOnlyAccess" + ] + number_of_role_policy_arns = 1 +} +``` + +`iam-assumable-roles`: + +```hcl +module "iam_assumable_roles" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-roles" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + "arn:aws:iam::111111111111:user/kloia", + ] + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + readonly_role_requires_mfa = false +} +``` + +`iam-assumable-roles-with-saml`: + +```hcl +module "iam_assumable_roles_with_saml" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-roles-with-saml" + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + + provider_id = "arn:aws:iam::235367859851:saml-provider/idp_saml" +} +``` + +`iam-eks-role`: + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + cluster_service_accounts = { + "cluster1" = ["default:my-app"] + "cluster2" = [ + "default:my-app", + "canary:my-app", + ] + } + + tags = { + Name = "eks-role" + } + + role_policy_arns = { + AmazonEKS_CNI_Policy = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + } +} +``` + +`iam-group-with-assumable-roles-policy`: + +```hcl +module "iam_group_with_assumable_roles_policy" { + source = "terraform-aws-modules/iam/aws//modules/iam-group-with-assumable-roles-policy" + + name = "production-readonly" + + assumable_roles = [ + "arn:aws:iam::835367859855:role/readonly" # these roles can be created using `iam_assumable_roles` submodule + ] + + group_users = [ + "user1", + "user2" + ] +} +``` + +`iam-group-with-policies`: + +```hcl +module "iam_group_with_policies" { + source = "terraform-aws-modules/iam/aws//modules/iam-group-with-policies" + + name = "superadmins" + + group_users = [ + "user1", + "user2" + ] + + attach_iam_self_management_policy = true + + custom_group_policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess", + ] + + custom_group_policies = [ + { + name = "AllowS3Listing" + policy = data.aws_iam_policy_document.sample.json + } + ] +} +``` + +`iam-policy`: + +```hcl +module "iam_policy" { + source = "terraform-aws-modules/iam/aws//modules/iam-policy" + + name = "example" + path = "/" + description = "My example policy" + + policy = < [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_account](#module\_iam\_account) | ../../modules/iam-account | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [caller\_identity\_account\_id](#output\_caller\_identity\_account\_id) | The ID of the AWS account | +| [iam\_account\_password\_policy\_expire\_passwords](#output\_iam\_account\_password\_policy\_expire\_passwords) | Indicates whether passwords in the account expire. Returns true if max\_password\_age contains a value greater than 0. Returns false if it is 0 or not present. | \ No newline at end of file diff --git a/modules/aws-iam/examples/iam-account/main.tf b/modules/aws-iam/examples/iam-account/main.tf new file mode 100644 index 0000000..9ac4f55 --- /dev/null +++ b/modules/aws-iam/examples/iam-account/main.tf @@ -0,0 +1,15 @@ +provider "aws" { + region = "eu-west-1" +} + +############## +# IAM account +############## +module "iam_account" { + source = "../../modules/iam-account" + + account_alias = "new-test-account-awesome-company" + + minimum_password_length = 6 + require_numbers = false +} diff --git a/modules/aws-iam/examples/iam-account/outputs.tf b/modules/aws-iam/examples/iam-account/outputs.tf new file mode 100644 index 0000000..9841072 --- /dev/null +++ b/modules/aws-iam/examples/iam-account/outputs.tf @@ -0,0 +1,9 @@ +output "caller_identity_account_id" { + description = "The ID of the AWS account" + value = module.iam_account.caller_identity_account_id +} + +output "iam_account_password_policy_expire_passwords" { + description = "Indicates whether passwords in the account expire. Returns true if max_password_age contains a value greater than 0. Returns false if it is 0 or not present." + value = module.iam_account.iam_account_password_policy_expire_passwords +} diff --git a/modules/aws-iam/examples/iam-account/variables.tf b/modules/aws-iam/examples/iam-account/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-account/versions.tf b/modules/aws-iam/examples/iam-account/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-account/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-oidc/README.md b/modules/aws-iam/examples/iam-assumable-role-with-oidc/README.md new file mode 100644 index 0000000..5c66ae4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-oidc/README.md @@ -0,0 +1,51 @@ +# Individual IAM assumable role example + +Configuration in this directory creates a single IAM role which can be assumed by trusted resources using OpenID Connect Federated Users. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_role\_admin](#module\_iam\_assumable\_role\_admin) | ../../modules/iam-assumable-role-with-oidc | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/examples/iam-assumable-role-with-oidc/main.tf b/modules/aws-iam/examples/iam-assumable-role-with-oidc/main.tf new file mode 100644 index 0000000..a3d13af --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-oidc/main.tf @@ -0,0 +1,27 @@ +provider "aws" { + region = "eu-west-1" +} + +############################### +# IAM assumable role for admin +############################### +module "iam_assumable_role_admin" { + source = "../../modules/iam-assumable-role-with-oidc" + + create_role = true + + role_name = "role-with-oidc" + + tags = { + Role = "role-with-oidc" + } + + provider_url = "oidc.eks.eu-west-1.amazonaws.com/id/BA9E170D464AF7B92084EF72A69B9DC8" + provider_urls = ["oidc.eks.eu-west-1.amazonaws.com/id/AA9E170D464AF7B92084EF72A69B9DC8"] + + role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + ] + + oidc_fully_qualified_subjects = ["system:serviceaccount:default:sa1", "system:serviceaccount:default:sa2"] +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-oidc/outputs.tf b/modules/aws-iam/examples/iam-assumable-role-with-oidc/outputs.tf new file mode 100644 index 0000000..69fbaa0 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-oidc/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.iam_assumable_role_admin.iam_role_arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.iam_assumable_role_admin.iam_role_name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.iam_assumable_role_admin.iam_role_path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_role_admin.iam_role_unique_id +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-oidc/variables.tf b/modules/aws-iam/examples/iam-assumable-role-with-oidc/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-assumable-role-with-oidc/versions.tf b/modules/aws-iam/examples/iam-assumable-role-with-oidc/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-oidc/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/README.md b/modules/aws-iam/examples/iam-assumable-role-with-saml/README.md new file mode 100644 index 0000000..d2ab974 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-saml/README.md @@ -0,0 +1,56 @@ +# Individual IAM assumable role with SAML Identity Provider example + +Configuration in this directory creates a single IAM role which can be assumed by users with a SAML Identity Provider. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_role\_admin](#module\_iam\_assumable\_role\_admin) | ../../modules/iam-assumable-role-with-saml | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_saml_provider.idp_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | +| [aws_iam_saml_provider.second_idp_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/main.tf b/modules/aws-iam/examples/iam-assumable-role-with-saml/main.tf new file mode 100644 index 0000000..902bce8 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-saml/main.tf @@ -0,0 +1,35 @@ +provider "aws" { + region = "eu-west-1" +} + +resource "aws_iam_saml_provider" "idp_saml" { + name = "idp_saml" + saml_metadata_document = file("saml-metadata.xml") +} + +resource "aws_iam_saml_provider" "second_idp_saml" { + name = "second_idp_saml" + saml_metadata_document = file("saml-metadata.xml") +} + +############################### +# IAM assumable role for admin +############################### +module "iam_assumable_role_admin" { + source = "../../modules/iam-assumable-role-with-saml" + + create_role = true + + role_name = "role-with-saml" + + tags = { + Role = "role-with-saml" + } + + provider_id = aws_iam_saml_provider.idp_saml.id + provider_ids = [aws_iam_saml_provider.second_idp_saml.id] + + role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + ] +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/outputs.tf b/modules/aws-iam/examples/iam-assumable-role-with-saml/outputs.tf new file mode 100644 index 0000000..69fbaa0 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-saml/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.iam_assumable_role_admin.iam_role_arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.iam_assumable_role_admin.iam_role_name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.iam_assumable_role_admin.iam_role_path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_role_admin.iam_role_unique_id +} diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/saml-metadata.xml b/modules/aws-iam/examples/iam-assumable-role-with-saml/saml-metadata.xml new file mode 100644 index 0000000..c68d77c --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-saml/saml-metadata.xml @@ -0,0 +1,14 @@ + + + + + + MIIErDCCA5SgAwIBAgIOAU+PT8RBAAAAAHxJXEcwDQYJKoZIhvcNAQELBQAwgZAxKDAmBgNVBAMMH1NlbGZTaWduZWRDZXJ0XzAyU2VwMjAxNV8xODI2NTMxGDAWBgNVBAsMDzAwRDI0MDAwMDAwcEFvQTEXMBUGA1UECgwOU2FsZXNmb3JjZS5jb20xFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xCzAJBgNVBAgMAkNBMQwwCgYDVQQGEwNVU0EwHhcNMTUwOTAyMTgyNjUzWhcNMTcwOTAyMTIwMDAwWjCBkDEoMCYGA1UEAwwfU2VsZlNpZ25lZENlcnRfMDJTZXAyMDE1XzE4MjY1MzEYMBYGA1UECwwPMDBEMjQwMDAwMDBwQW9BMRcwFQYDVQQKDA5TYWxlc2ZvcmNlLmNvbTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzELMAkGA1UECAwCQ0ExDDAKBgNVBAYTA1VTQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJp/wTRr9n1IWJpkRTjNpep47OKJrD2E6rGbJ18TG2RxtIz+zCn2JwH2aP3TULh0r0hhcg/pecv51RRcG7O19DBBaTQ5+KuoICQyKZy07/yDXSiZontTwkEYs06ssTwTHUcRXbcwTKv16L7omt0MjIhTTGfvtLOYiPwyvKvzAHg4eNuAcli0duVM78UIBORtdmy9C9ZcMh8yRJo5aPBq85wsE3JXU58ytyZzCHTBLH+2xFQrjYnUSEW+FOEEpI7o33MVdFBvWWg1R17HkWzcve4C30lqOHqvxBzyESZ/N1mMlmSt8gPFyB+mUXY99StJDJpnytbY8DwSzMQUo/sOVB0CAwEAAaOCAQAwgf0wHQYDVR0OBBYEFByu1EQqRQS0bYQBKS9K5qwKi+6IMA8GA1UdEwEB/wQFMAMBAf8wgcoGA1UdIwSBwjCBv4AUHK7URCpFBLRthAEpL0rmrAqL7oihgZakgZMwgZAxKDAmBgNVBAMMH1NlbGZTaWduZWRDZXJ0XzAyU2VwMjAxNV8xODI2NTMxGDAWBgNVBAsMDzAwRDI0MDAwMDAwcEFvQTEXMBUGA1UECgwOU2FsZXNmb3JjZS5jb20xFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xCzAJBgNVBAgMAkNBMQwwCgYDVQQGEwNVU0GCDgFPj0/EQQAAAAB8SVxHMA0GCSqGSIb3DQEBCwUAA4IBAQA9O5o1tC71qJnkq+ABPo4A1aFKZVT/07GcBX4/wetcbYySL4Q2nR9pMgfPYYS1j+P2E3viPsQwPIWDUBwFkNsjjX5DSGEkLAioVGKRwJshRSCSynMcsVZbQkfBUiZXqhM0wzvoa/ALvGD+aSSb1m+x7lEpDYNwQKWaUW2VYcHWv9wjujMyy7dlj8E/jqM71mw7ThNl6k4+3RQ802dMa14txm8pkF0vZgfpV3tkqhBqtjBAicVCaveqr3r3iGqjvyilBgdY+0NR8szqzm7CD/Bkb22+/IgM/mXQuL9KHD/WADlSGmYKmG3SSahmcZxznYCnzcRNN9LVuXlz5cbljmBj + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/variables.tf b/modules/aws-iam/examples/iam-assumable-role-with-saml/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-assumable-role-with-saml/versions.tf b/modules/aws-iam/examples/iam-assumable-role-with-saml/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role-with-saml/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-assumable-role/README.md b/modules/aws-iam/examples/iam-assumable-role/README.md new file mode 100644 index 0000000..921e2d2 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role/README.md @@ -0,0 +1,62 @@ +# Individual IAM assumable roles example + +Configuration in this directory creates several individual IAM roles which can be assumed from a defined list of [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns). + +The main difference between `iam-assumable-role` and `iam-assumable-roles` examples is that the former creates just a single role. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_role\_admin](#module\_iam\_assumable\_role\_admin) | ../../modules/iam-assumable-role | n/a | +| [iam\_assumable\_role\_custom](#module\_iam\_assumable\_role\_custom) | ../../modules/iam-assumable-role | n/a | +| [iam\_assumable\_role\_custom\_trust\_policy](#module\_iam\_assumable\_role\_custom\_trust\_policy) | ../../modules/iam-assumable-role | n/a | +| [iam\_assumable\_role\_sts](#module\_iam\_assumable\_role\_sts) | ../../modules/iam-assumable-role | n/a | +| [iam\_policy](#module\_iam\_policy) | ../../modules/iam-policy | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.custom_trust_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | IAM Instance profile's ID. | +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [role\_requires\_mfa](#output\_role\_requires\_mfa) | Whether admin IAM role requires MFA | diff --git a/modules/aws-iam/examples/iam-assumable-role/main.tf b/modules/aws-iam/examples/iam-assumable-role/main.tf new file mode 100644 index 0000000..ae945a2 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-role/main.tf @@ -0,0 +1,166 @@ +provider "aws" { + region = "eu-west-1" +} + +############################### +# IAM assumable role for admin +############################### +module "iam_assumable_role_admin" { + source = "../../modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + "arn:aws:iam::111111111111:user/kloia", + ] + + trusted_role_services = [ + "codedeploy.amazonaws.com" + ] + + create_role = true + create_instance_profile = true + + role_name = "admin" + role_requires_mfa = true + + attach_admin_policy = true + + tags = { + Role = "Admin" + } +} + +########################################## +# IAM assumable role with custom policies +########################################## +module "iam_assumable_role_custom" { + source = "../../modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + ] + + trusted_role_services = [ + "codedeploy.amazonaws.com" + ] + + create_role = true + + role_name = "custom" + role_requires_mfa = false + + role_sts_externalid = "some-id-goes-here" + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess", + module.iam_policy.arn + ] + # number_of_custom_role_policy_arns = 3 +} + +#################################################### +# IAM assumable role with multiple sts external ids +#################################################### +module "iam_assumable_role_sts" { + source = "../../modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + ] + + trusted_role_services = [ + "codedeploy.amazonaws.com" + ] + + create_role = true + + role_name = "custom_sts" + role_requires_mfa = true + + role_sts_externalid = [ + "some-id-goes-here", + "another-id-goes-here", + ] + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess", + module.iam_policy.arn + ] + # number_of_custom_role_policy_arns = 3 +} + +######################################### +# IAM assumable role with custom trust policy +######################################### +module "iam_assumable_role_custom_trust_policy" { + source = "../../modules/iam-assumable-role" + + create_role = true + + role_name = "iam_assumable_role_custom_trust_policy" + + custom_role_trust_policy = data.aws_iam_policy_document.custom_trust_policy.json + custom_role_policy_arns = ["arn:aws:iam::aws:policy/AmazonCognitoReadOnly"] +} + +data "aws_iam_policy_document" "custom_trust_policy" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + condition { + test = "StringEquals" + variable = "sts:ExternalId" + values = ["some-ext-id"] + } + + condition { + test = "StringEquals" + variable = "aws:PrincipalOrgID" + values = ["o-someorgid"] + } + + principals { + type = "AWS" + identifiers = ["*"] + } + } + + statement { + effect = "Deny" + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::111111111111:root"] + } + } +} + +######################################### +# IAM policy +######################################### +module "iam_policy" { + source = "../../modules/iam-policy" + + name = "example" + path = "/" + description = "My example policy" + + policy = < [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_roles\_with\_saml](#module\_iam\_assumable\_roles\_with\_saml) | ../../modules/iam-assumable-roles-with-saml | n/a | +| [iam\_assumable\_roles\_with\_saml\_custom](#module\_iam\_assumable\_roles\_with\_saml\_custom) | ../../modules/iam-assumable-roles-with-saml | n/a | +| [iam\_assumable\_roles\_with\_saml\_second\_provider](#module\_iam\_assumable\_roles\_with\_saml\_second\_provider) | ../../modules/iam-assumable-roles-with-saml | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_saml_provider.idp_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | +| [aws_iam_saml_provider.second_idp_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [admin\_iam\_role\_arn](#output\_admin\_iam\_role\_arn) | ARN of admin IAM role | +| [admin\_iam\_role\_name](#output\_admin\_iam\_role\_name) | Name of admin IAM role | +| [admin\_iam\_role\_path](#output\_admin\_iam\_role\_path) | Path of admin IAM role | +| [admin\_iam\_role\_unique\_id](#output\_admin\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [poweruser\_iam\_role\_arn](#output\_poweruser\_iam\_role\_arn) | ARN of poweruser IAM role | +| [poweruser\_iam\_role\_name](#output\_poweruser\_iam\_role\_name) | Name of poweruser IAM role | +| [poweruser\_iam\_role\_path](#output\_poweruser\_iam\_role\_path) | Path of poweruser IAM role | +| [poweruser\_iam\_role\_unique\_id](#output\_poweruser\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [readonly\_iam\_role\_arn](#output\_readonly\_iam\_role\_arn) | ARN of readonly IAM role | +| [readonly\_iam\_role\_name](#output\_readonly\_iam\_role\_name) | Name of readonly IAM role | +| [readonly\_iam\_role\_path](#output\_readonly\_iam\_role\_path) | Path of readonly IAM role | +| [readonly\_iam\_role\_unique\_id](#output\_readonly\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/examples/iam-assumable-roles-with-saml/main.tf b/modules/aws-iam/examples/iam-assumable-roles-with-saml/main.tf new file mode 100644 index 0000000..a2dcf5e --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles-with-saml/main.tf @@ -0,0 +1,60 @@ +provider "aws" { + region = "eu-west-1" +} + +resource "aws_iam_saml_provider" "idp_saml" { + name = "idp_saml" + saml_metadata_document = file("saml-metadata.xml") +} + +resource "aws_iam_saml_provider" "second_idp_saml" { + name = "second_idp_saml" + saml_metadata_document = file("saml-metadata.xml") +} + +############################### +# IAM assumable roles with SAML +############################### + +module "iam_assumable_roles_with_saml" { + source = "../../modules/iam-assumable-roles-with-saml" + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + + provider_id = aws_iam_saml_provider.idp_saml.id +} + +############################### +# IAM assumable roles with SAML +############################### + +module "iam_assumable_roles_with_saml_second_provider" { + source = "../../modules/iam-assumable-roles-with-saml" + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + + provider_ids = [aws_iam_saml_provider.idp_saml.id, aws_iam_saml_provider.second_idp_saml.id] +} + +################################################################# +# Create custom role with SAML idp trust and additional policies +################################################################# +module "iam_assumable_roles_with_saml_custom" { + source = "../../modules/iam-assumable-roles-with-saml" + + create_poweruser_role = true + poweruser_role_name = "Billing-And-Support-Access" + poweruser_role_policy_arns = ["arn:aws:iam::aws:policy/job-function/Billing", "arn:aws:iam::aws:policy/AWSSupportAccess"] + + provider_id = aws_iam_saml_provider.idp_saml.id +} diff --git a/modules/aws-iam/examples/iam-assumable-roles-with-saml/outputs.tf b/modules/aws-iam/examples/iam-assumable-roles-with-saml/outputs.tf new file mode 100644 index 0000000..138c67b --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles-with-saml/outputs.tf @@ -0,0 +1,62 @@ +# Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = module.iam_assumable_roles_with_saml.admin_iam_role_arn +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = module.iam_assumable_roles_with_saml.admin_iam_role_name +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = module.iam_assumable_roles_with_saml.admin_iam_role_path +} + +output "admin_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles_with_saml.admin_iam_role_unique_id +} + +# Poweruser +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = module.iam_assumable_roles_with_saml.poweruser_iam_role_arn +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = module.iam_assumable_roles_with_saml.poweruser_iam_role_name +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = module.iam_assumable_roles_with_saml.poweruser_iam_role_path +} + +output "poweruser_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles_with_saml.poweruser_iam_role_unique_id +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = module.iam_assumable_roles_with_saml.readonly_iam_role_arn +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = module.iam_assumable_roles_with_saml.readonly_iam_role_name +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = module.iam_assumable_roles_with_saml.readonly_iam_role_path +} + +output "readonly_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles_with_saml.readonly_iam_role_unique_id +} diff --git a/modules/aws-iam/examples/iam-assumable-roles-with-saml/saml-metadata.xml b/modules/aws-iam/examples/iam-assumable-roles-with-saml/saml-metadata.xml new file mode 100644 index 0000000..c68d77c --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles-with-saml/saml-metadata.xml @@ -0,0 +1,14 @@ + + + + + + MIIErDCCA5SgAwIBAgIOAU+PT8RBAAAAAHxJXEcwDQYJKoZIhvcNAQELBQAwgZAxKDAmBgNVBAMMH1NlbGZTaWduZWRDZXJ0XzAyU2VwMjAxNV8xODI2NTMxGDAWBgNVBAsMDzAwRDI0MDAwMDAwcEFvQTEXMBUGA1UECgwOU2FsZXNmb3JjZS5jb20xFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xCzAJBgNVBAgMAkNBMQwwCgYDVQQGEwNVU0EwHhcNMTUwOTAyMTgyNjUzWhcNMTcwOTAyMTIwMDAwWjCBkDEoMCYGA1UEAwwfU2VsZlNpZ25lZENlcnRfMDJTZXAyMDE1XzE4MjY1MzEYMBYGA1UECwwPMDBEMjQwMDAwMDBwQW9BMRcwFQYDVQQKDA5TYWxlc2ZvcmNlLmNvbTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzELMAkGA1UECAwCQ0ExDDAKBgNVBAYTA1VTQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJp/wTRr9n1IWJpkRTjNpep47OKJrD2E6rGbJ18TG2RxtIz+zCn2JwH2aP3TULh0r0hhcg/pecv51RRcG7O19DBBaTQ5+KuoICQyKZy07/yDXSiZontTwkEYs06ssTwTHUcRXbcwTKv16L7omt0MjIhTTGfvtLOYiPwyvKvzAHg4eNuAcli0duVM78UIBORtdmy9C9ZcMh8yRJo5aPBq85wsE3JXU58ytyZzCHTBLH+2xFQrjYnUSEW+FOEEpI7o33MVdFBvWWg1R17HkWzcve4C30lqOHqvxBzyESZ/N1mMlmSt8gPFyB+mUXY99StJDJpnytbY8DwSzMQUo/sOVB0CAwEAAaOCAQAwgf0wHQYDVR0OBBYEFByu1EQqRQS0bYQBKS9K5qwKi+6IMA8GA1UdEwEB/wQFMAMBAf8wgcoGA1UdIwSBwjCBv4AUHK7URCpFBLRthAEpL0rmrAqL7oihgZakgZMwgZAxKDAmBgNVBAMMH1NlbGZTaWduZWRDZXJ0XzAyU2VwMjAxNV8xODI2NTMxGDAWBgNVBAsMDzAwRDI0MDAwMDAwcEFvQTEXMBUGA1UECgwOU2FsZXNmb3JjZS5jb20xFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xCzAJBgNVBAgMAkNBMQwwCgYDVQQGEwNVU0GCDgFPj0/EQQAAAAB8SVxHMA0GCSqGSIb3DQEBCwUAA4IBAQA9O5o1tC71qJnkq+ABPo4A1aFKZVT/07GcBX4/wetcbYySL4Q2nR9pMgfPYYS1j+P2E3viPsQwPIWDUBwFkNsjjX5DSGEkLAioVGKRwJshRSCSynMcsVZbQkfBUiZXqhM0wzvoa/ALvGD+aSSb1m+x7lEpDYNwQKWaUW2VYcHWv9wjujMyy7dlj8E/jqM71mw7ThNl6k4+3RQ802dMa14txm8pkF0vZgfpV3tkqhBqtjBAicVCaveqr3r3iGqjvyilBgdY+0NR8szqzm7CD/Bkb22+/IgM/mXQuL9KHD/WADlSGmYKmG3SSahmcZxznYCnzcRNN9LVuXlz5cbljmBj + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + diff --git a/modules/aws-iam/examples/iam-assumable-roles-with-saml/variables.tf b/modules/aws-iam/examples/iam-assumable-roles-with-saml/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-assumable-roles-with-saml/versions.tf b/modules/aws-iam/examples/iam-assumable-roles-with-saml/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles-with-saml/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-assumable-roles/README.md b/modules/aws-iam/examples/iam-assumable-roles/README.md new file mode 100644 index 0000000..04b4621 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles/README.md @@ -0,0 +1,61 @@ +# IAM assumable roles example + +Configuration in this directory creates several IAM roles which can be assumed from a defined list of [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns). + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_roles](#module\_iam\_assumable\_roles) | ../../modules/iam-assumable-roles | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [admin\_iam\_role\_arn](#output\_admin\_iam\_role\_arn) | ARN of admin IAM role | +| [admin\_iam\_role\_name](#output\_admin\_iam\_role\_name) | Name of admin IAM role | +| [admin\_iam\_role\_path](#output\_admin\_iam\_role\_path) | Path of admin IAM role | +| [admin\_iam\_role\_requires\_mfa](#output\_admin\_iam\_role\_requires\_mfa) | Whether admin IAM role requires MFA | +| [admin\_iam\_role\_unique\_id](#output\_admin\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [poweruser\_iam\_role\_arn](#output\_poweruser\_iam\_role\_arn) | ARN of poweruser IAM role | +| [poweruser\_iam\_role\_name](#output\_poweruser\_iam\_role\_name) | Name of poweruser IAM role | +| [poweruser\_iam\_role\_path](#output\_poweruser\_iam\_role\_path) | Path of poweruser IAM role | +| [poweruser\_iam\_role\_requires\_mfa](#output\_poweruser\_iam\_role\_requires\_mfa) | Whether poweruser IAM role requires MFA | +| [poweruser\_iam\_role\_unique\_id](#output\_poweruser\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [readonly\_iam\_role\_arn](#output\_readonly\_iam\_role\_arn) | ARN of readonly IAM role | +| [readonly\_iam\_role\_name](#output\_readonly\_iam\_role\_name) | Name of readonly IAM role | +| [readonly\_iam\_role\_path](#output\_readonly\_iam\_role\_path) | Path of readonly IAM role | +| [readonly\_iam\_role\_requires\_mfa](#output\_readonly\_iam\_role\_requires\_mfa) | Whether readonly IAM role requires MFA | +| [readonly\_iam\_role\_unique\_id](#output\_readonly\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/examples/iam-assumable-roles/main.tf b/modules/aws-iam/examples/iam-assumable-roles/main.tf new file mode 100644 index 0000000..82928b4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles/main.tf @@ -0,0 +1,28 @@ +provider "aws" { + region = "eu-west-1" +} + +###################### +# IAM assumable roles +###################### +module "iam_assumable_roles" { + source = "../../modules/iam-assumable-roles" + + trusted_role_arns = [ + "arn:aws:iam::111111111111:root", + "arn:aws:iam::111111111111:user/kloia", + ] + + trusted_role_services = [ + "codedeploy.amazonaws.com" + ] + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "Billing-And-Support-Access" + poweruser_role_policy_arns = ["arn:aws:iam::aws:policy/job-function/Billing", "arn:aws:iam::aws:policy/AWSSupportAccess"] + + create_readonly_role = true + readonly_role_requires_mfa = false +} diff --git a/modules/aws-iam/examples/iam-assumable-roles/outputs.tf b/modules/aws-iam/examples/iam-assumable-roles/outputs.tf new file mode 100644 index 0000000..56bff3b --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles/outputs.tf @@ -0,0 +1,77 @@ +# Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = module.iam_assumable_roles.admin_iam_role_arn +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = module.iam_assumable_roles.admin_iam_role_name +} + +output "admin_iam_role_requires_mfa" { + description = "Whether admin IAM role requires MFA" + value = module.iam_assumable_roles.admin_iam_role_requires_mfa +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = module.iam_assumable_roles.admin_iam_role_path +} + +output "admin_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles.admin_iam_role_unique_id +} + +# Poweruser +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = module.iam_assumable_roles.poweruser_iam_role_arn +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = module.iam_assumable_roles.poweruser_iam_role_name +} + +output "poweruser_iam_role_requires_mfa" { + description = "Whether poweruser IAM role requires MFA" + value = module.iam_assumable_roles.poweruser_iam_role_requires_mfa +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = module.iam_assumable_roles.poweruser_iam_role_path +} + +output "poweruser_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles.poweruser_iam_role_unique_id +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = module.iam_assumable_roles.readonly_iam_role_arn +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = module.iam_assumable_roles.readonly_iam_role_name +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = module.iam_assumable_roles.readonly_iam_role_path +} + +output "readonly_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_assumable_roles.readonly_iam_role_unique_id +} + +output "readonly_iam_role_requires_mfa" { + description = "Whether readonly IAM role requires MFA" + value = module.iam_assumable_roles.readonly_iam_role_requires_mfa +} diff --git a/modules/aws-iam/examples/iam-assumable-roles/variables.tf b/modules/aws-iam/examples/iam-assumable-roles/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-assumable-roles/versions.tf b/modules/aws-iam/examples/iam-assumable-roles/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-assumable-roles/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-eks-role/README.md b/modules/aws-iam/examples/iam-eks-role/README.md new file mode 100644 index 0000000..38676f9 --- /dev/null +++ b/modules/aws-iam/examples/iam-eks-role/README.md @@ -0,0 +1,59 @@ +# IAM EKS role + +Configuration in this directory creates an IAM role that can be assumed by multiple EKS `ServiceAccount`. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.0 | +| [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_subnet_ids.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | +| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/examples/iam-eks-role/main.tf b/modules/aws-iam/examples/iam-eks-role/main.tf new file mode 100644 index 0000000..d51105f --- /dev/null +++ b/modules/aws-iam/examples/iam-eks-role/main.tf @@ -0,0 +1,51 @@ +provider "aws" { + region = "eu-west-1" +} + +module "iam_eks_role" { + source = "../../modules/iam-eks-role" + role_name = "my-app" + + cluster_service_accounts = { + (random_pet.this.id) = ["default:my-app"] + } + + tags = { + Name = "eks-role" + } + + role_policy_arns = { + AmazonEKS_CNI_Policy = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + } +} + +################## +# Extra resources +################## + +resource "random_pet" "this" { + length = 2 +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 18.0" + + cluster_name = random_pet.this.id + cluster_version = "1.21" + + vpc_id = data.aws_vpc.default.id + subnet_ids = data.aws_subnet_ids.all.ids +} + +################################################################## +# Data sources to get VPC, subnet, security group and AMI details +################################################################## + +data "aws_vpc" "default" { + default = true +} + +data "aws_subnet_ids" "all" { + vpc_id = data.aws_vpc.default.id +} diff --git a/modules/aws-iam/examples/iam-eks-role/outputs.tf b/modules/aws-iam/examples/iam-eks-role/outputs.tf new file mode 100644 index 0000000..edac4b5 --- /dev/null +++ b/modules/aws-iam/examples/iam-eks-role/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.iam_eks_role.iam_role_arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.iam_eks_role.iam_role_name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.iam_eks_role.iam_role_path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_eks_role.iam_role_unique_id +} diff --git a/modules/aws-iam/examples/iam-eks-role/variables.tf b/modules/aws-iam/examples/iam-eks-role/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-eks-role/versions.tf b/modules/aws-iam/examples/iam-eks-role/versions.tf new file mode 100644 index 0000000..981f3b1 --- /dev/null +++ b/modules/aws-iam/examples/iam-eks-role/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-group-complete/README.md b/modules/aws-iam/examples/iam-group-complete/README.md new file mode 100644 index 0000000..c185d2c --- /dev/null +++ b/modules/aws-iam/examples/iam-group-complete/README.md @@ -0,0 +1,55 @@ +# Complete IAM group example + +Configuration in this directory creates IAM group with users who are allowed to assume IAM roles and extended with IAM policies. + +This is a combination of `iam-group-with-assumable-roles-policy` and `iam-group-with-policies` exampled. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_group\_complete](#module\_iam\_group\_complete) | ../../modules/iam-group-with-assumable-roles-policy | n/a | +| [iam\_group\_complete\_with\_custom\_policy](#module\_iam\_group\_complete\_with\_custom\_policy) | ../../modules/iam-group-with-policies | n/a | +| [iam\_user1](#module\_iam\_user1) | ../../modules/iam-user | n/a | +| [iam\_user2](#module\_iam\_user2) | ../../modules/iam-user | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [assumable\_roles](#output\_assumable\_roles) | List of ARNs of IAM roles which members of IAM group can assume | +| [group\_users](#output\_group\_users) | List of IAM users in IAM group | +| [policy\_arn](#output\_policy\_arn) | Assume role policy ARN for IAM group | + diff --git a/modules/aws-iam/examples/iam-group-complete/main.tf b/modules/aws-iam/examples/iam-group-complete/main.tf new file mode 100644 index 0000000..60fc41e --- /dev/null +++ b/modules/aws-iam/examples/iam-group-complete/main.tf @@ -0,0 +1,51 @@ +############ +# IAM users +############ +module "iam_user1" { + source = "../../modules/iam-user" + + name = "user1" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +module "iam_user2" { + source = "../../modules/iam-user" + + name = "user2" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +############################################################################################# +# IAM group where user1 and user2 are allowed to assume admin role in production AWS account +############################################################################################# +module "iam_group_complete" { + source = "../../modules/iam-group-with-assumable-roles-policy" + + name = "production-admins" + + assumable_roles = ["arn:aws:iam::111111111111:role/admin"] + + group_users = [ + module.iam_user1.iam_user_name, + module.iam_user2.iam_user_name, + ] +} + +#################################################### +# Extending policies of IAM group production-admins +#################################################### +module "iam_group_complete_with_custom_policy" { + source = "../../modules/iam-group-with-policies" + + name = module.iam_group_complete.group_name + + create_group = false + + custom_group_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonS3FullAccess", + ] +} diff --git a/modules/aws-iam/examples/iam-group-complete/outputs.tf b/modules/aws-iam/examples/iam-group-complete/outputs.tf new file mode 100644 index 0000000..949a9f0 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-complete/outputs.tf @@ -0,0 +1,14 @@ +output "group_users" { + description = "List of IAM users in IAM group" + value = module.iam_group_complete.group_users +} + +output "assumable_roles" { + description = "List of ARNs of IAM roles which members of IAM group can assume" + value = module.iam_group_complete.assumable_roles +} + +output "policy_arn" { + description = "Assume role policy ARN for IAM group" + value = module.iam_group_complete.policy_arn +} diff --git a/modules/aws-iam/examples/iam-group-complete/variables.tf b/modules/aws-iam/examples/iam-group-complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-group-complete/versions.tf b/modules/aws-iam/examples/iam-group-complete/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/README.md b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/README.md new file mode 100644 index 0000000..f00bd2d --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/README.md @@ -0,0 +1,63 @@ +# IAM group with assumable roles policy example + +Configuration in this directory creates IAM group with users who are allowed to assume IAM roles. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | +| [aws.production](#provider\_aws.production) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_role\_custom](#module\_iam\_assumable\_role\_custom) | ../../modules/iam-assumable-role | n/a | +| [iam\_assumable\_roles\_in\_prod](#module\_iam\_assumable\_roles\_in\_prod) | ../../modules/iam-assumable-roles | n/a | +| [iam\_group\_with\_assumable\_roles\_policy\_production\_admin](#module\_iam\_group\_with\_assumable\_roles\_policy\_production\_admin) | ../../modules/iam-group-with-assumable-roles-policy | n/a | +| [iam\_group\_with\_assumable\_roles\_policy\_production\_custom](#module\_iam\_group\_with\_assumable\_roles\_policy\_production\_custom) | ../../modules/iam-group-with-assumable-roles-policy | n/a | +| [iam\_group\_with\_assumable\_roles\_policy\_production\_readonly](#module\_iam\_group\_with\_assumable\_roles\_policy\_production\_readonly) | ../../modules/iam-group-with-assumable-roles-policy | n/a | +| [iam\_user1](#module\_iam\_user1) | ../../modules/iam-user | n/a | +| [iam\_user2](#module\_iam\_user2) | ../../modules/iam-user | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_caller_identity.iam](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_caller_identity.production](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [assumable\_roles](#output\_assumable\_roles) | List of ARNs of IAM roles which members of IAM group can assume | +| [group\_users](#output\_group\_users) | List of IAM users in IAM group | +| [iam\_account\_id](#output\_iam\_account\_id) | IAM AWS account id (this code is managing resources in this account) | +| [policy\_arn](#output\_policy\_arn) | Assume role policy ARN for IAM group | +| [production\_account\_id](#output\_production\_account\_id) | Production AWS account id | diff --git a/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/main.tf b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/main.tf new file mode 100644 index 0000000..d78ff05 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/main.tf @@ -0,0 +1,133 @@ +provider "aws" { + region = "eu-west-1" +} + +provider "aws" { + region = "eu-west-1" + + assume_role { + role_arn = "arn:aws:iam::111111111111:role/kloia-demo" + } + + alias = "production" +} + +data "aws_caller_identity" "iam" {} + +data "aws_caller_identity" "production" { + provider = aws.production +} + +############ +# IAM users +############ +module "iam_user1" { + source = "../../modules/iam-user" + + name = "user1" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +module "iam_user2" { + source = "../../modules/iam-user" + + name = "user2" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +##################################################################################### +# Several IAM assumable roles (admin, poweruser, readonly) in production AWS account +# Note: Anyone from IAM account can assume them. +##################################################################################### +module "iam_assumable_roles_in_prod" { + source = "../../modules/iam-assumable-roles" + + trusted_role_arns = [ + "arn:aws:iam::${data.aws_caller_identity.iam.account_id}:root", + ] + + create_admin_role = true + create_poweruser_role = true + + create_readonly_role = true + readonly_role_requires_mfa = false + + providers = { + aws = aws.production + } +} + +module "iam_assumable_role_custom" { + source = "../../modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::${data.aws_caller_identity.iam.account_id}:root", + ] + + create_role = true + + role_name = "custom" + role_requires_mfa = true + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess", + ] + + providers = { + aws = aws.production + } +} + +################################################################################################ +# IAM group where user1 and user2 are allowed to assume readonly role in production AWS account +# Note: IAM AWS account is default, so there is no need to specify it here. +################################################################################################ +module "iam_group_with_assumable_roles_policy_production_readonly" { + source = "../../modules/iam-group-with-assumable-roles-policy" + + name = "production-readonly" + + assumable_roles = [module.iam_assumable_roles_in_prod.readonly_iam_role_arn] + + group_users = [ + module.iam_user1.iam_user_name, + module.iam_user2.iam_user_name, + ] +} + +################################################################################################ +# IAM group where user1 is allowed to assume admin role in production AWS account +# Note: IAM AWS account is default, so there is no need to specify it here. +################################################################################################ +module "iam_group_with_assumable_roles_policy_production_admin" { + source = "../../modules/iam-group-with-assumable-roles-policy" + + name = "production-admin" + + assumable_roles = [module.iam_assumable_roles_in_prod.admin_iam_role_arn] + + group_users = [ + module.iam_user1.iam_user_name, + ] +} + +################################################################################################ +# IAM group where user2 is allowed to assume custom role in production AWS account +# Note: IAM AWS account is default, so there is no need to specify it here. +################################################################################################ +module "iam_group_with_assumable_roles_policy_production_custom" { + source = "../../modules/iam-group-with-assumable-roles-policy" + + name = "production-custom" + + assumable_roles = [module.iam_assumable_role_custom.iam_role_arn] + + group_users = [ + module.iam_user2.iam_user_name, + ] +} diff --git a/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/outputs.tf b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/outputs.tf new file mode 100644 index 0000000..d123bd0 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/outputs.tf @@ -0,0 +1,24 @@ +output "iam_account_id" { + description = "IAM AWS account id (this code is managing resources in this account)" + value = data.aws_caller_identity.iam.account_id +} + +output "production_account_id" { + description = "Production AWS account id" + value = data.aws_caller_identity.production.account_id +} + +output "group_users" { + description = "List of IAM users in IAM group" + value = module.iam_group_with_assumable_roles_policy_production_readonly.group_users +} + +output "assumable_roles" { + description = "List of ARNs of IAM roles which members of IAM group can assume" + value = module.iam_group_with_assumable_roles_policy_production_readonly.assumable_roles +} + +output "policy_arn" { + description = "Assume role policy ARN for IAM group" + value = module.iam_group_with_assumable_roles_policy_production_readonly.policy_arn +} diff --git a/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/variables.tf b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/versions.tf b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-assumable-roles-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-group-with-policies/README.md b/modules/aws-iam/examples/iam-group-with-policies/README.md new file mode 100644 index 0000000..e4d320f --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-policies/README.md @@ -0,0 +1,58 @@ +# IAM group with policies example + +Configuration in this directory creates IAM group with users who has specified IAM policies. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_group\_superadmins](#module\_iam\_group\_superadmins) | ../../modules/iam-group-with-policies | n/a | +| [iam\_group\_with\_custom\_policies](#module\_iam\_group\_with\_custom\_policies) | ../../modules/iam-group-with-policies | n/a | +| [iam\_user1](#module\_iam\_user1) | ../../modules/iam-user | n/a | +| [iam\_user2](#module\_iam\_user2) | ../../modules/iam-user | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.sample](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [group\_arn](#output\_group\_arn) | IAM group arn | +| [group\_name](#output\_group\_name) | IAM group name | +| [group\_users](#output\_group\_users) | List of IAM users in IAM group | +| [iam\_account\_id](#output\_iam\_account\_id) | IAM AWS account id | + diff --git a/modules/aws-iam/examples/iam-group-with-policies/main.tf b/modules/aws-iam/examples/iam-group-with-policies/main.tf new file mode 100644 index 0000000..e77887b --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-policies/main.tf @@ -0,0 +1,77 @@ +############ +# IAM users +############ +module "iam_user1" { + source = "../../modules/iam-user" + + name = "user1" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +module "iam_user2" { + source = "../../modules/iam-user" + + name = "user2" + + create_iam_user_login_profile = false + create_iam_access_key = false +} + +##################################################################################### +# IAM group for superadmins with full Administrator access +##################################################################################### +module "iam_group_superadmins" { + source = "../../modules/iam-group-with-policies" + + name = "superadmins" + + group_users = [ + module.iam_user1.iam_user_name, + module.iam_user2.iam_user_name, + ] + + custom_group_policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess", + ] +} + +##################################################################################### +# IAM group for users with custom access +##################################################################################### +module "iam_group_with_custom_policies" { + source = "../../modules/iam-group-with-policies" + + name = "custom" + + group_users = [ + module.iam_user1.iam_user_name, + module.iam_user2.iam_user_name, + ] + + custom_group_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess", + ] + + custom_group_policies = [ + { + name = "AllowS3Listing" + policy = data.aws_iam_policy_document.sample.json + }, + ] +} + +###################### +# IAM policy (sample) +###################### +data "aws_iam_policy_document" "sample" { + statement { + actions = [ + "s3:ListBuckets", + ] + + resources = ["*"] + } +} diff --git a/modules/aws-iam/examples/iam-group-with-policies/outputs.tf b/modules/aws-iam/examples/iam-group-with-policies/outputs.tf new file mode 100644 index 0000000..6da717d --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-policies/outputs.tf @@ -0,0 +1,19 @@ +output "iam_account_id" { + description = "IAM AWS account id" + value = module.iam_group_superadmins.aws_account_id +} + +output "group_arn" { + description = "IAM group arn" + value = module.iam_group_superadmins.group_arn +} + +output "group_users" { + description = "List of IAM users in IAM group" + value = module.iam_group_superadmins.group_users +} + +output "group_name" { + description = "IAM group name" + value = module.iam_group_superadmins.group_name +} diff --git a/modules/aws-iam/examples/iam-group-with-policies/variables.tf b/modules/aws-iam/examples/iam-group-with-policies/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-group-with-policies/versions.tf b/modules/aws-iam/examples/iam-group-with-policies/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-group-with-policies/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-policy/README.md b/modules/aws-iam/examples/iam-policy/README.md new file mode 100644 index 0000000..12ab3d1 --- /dev/null +++ b/modules/aws-iam/examples/iam-policy/README.md @@ -0,0 +1,57 @@ +# IAM user example + +Configuration in this directory creates IAM policies. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_policy](#module\_iam\_policy) | ../../modules/iam-policy | n/a | +| [iam\_policy\_from\_data\_source](#module\_iam\_policy\_from\_data\_source) | ../../modules/iam-policy | n/a | +| [iam\_policy\_optional](#module\_iam\_policy\_optional) | ../../modules/iam-policy | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | \ No newline at end of file diff --git a/modules/aws-iam/examples/iam-policy/main.tf b/modules/aws-iam/examples/iam-policy/main.tf new file mode 100644 index 0000000..3b3b02a --- /dev/null +++ b/modules/aws-iam/examples/iam-policy/main.tf @@ -0,0 +1,61 @@ +provider "aws" { + region = "eu-west-1" +} + +data "aws_iam_policy_document" "bucket_policy" { + statement { + sid = "AllowFullS3Access" + actions = ["s3:ListAllMyBuckets"] + resources = ["*"] + } +} + +######################################### +# IAM policy +######################################### +module "iam_policy" { + source = "../../modules/iam-policy" + + name = "example" + path = "/" + description = "My example policy" + + policy = < [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [read\_only\_iam\_policy](#module\_read\_only\_iam\_policy) | ../../modules/iam-read-only-policy | n/a | +| [read\_only\_iam\_policy\_doc](#module\_read\_only\_iam\_policy\_doc) | ../../modules/iam-read-only-policy | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_ssoadmin_permission_set.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_permission_set) | resource | +| [aws_ssoadmin_permission_set_inline_policy.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_permission_set_inline_policy) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | \ No newline at end of file diff --git a/modules/aws-iam/examples/iam-read-only-policy/main.tf b/modules/aws-iam/examples/iam-read-only-policy/main.tf new file mode 100644 index 0000000..907d7f4 --- /dev/null +++ b/modules/aws-iam/examples/iam-read-only-policy/main.tf @@ -0,0 +1,66 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + allowed_services_compute = ["ec2", "ecr", "eks", "ecs", "lambda", "autoscaling", "elasticloadbalancing"] + allowed_services_networking = ["vpc", "route53", "route53domains", "route53resolver", "servicediscovery"] + allowed_services_storage = ["s3", "backup", "dynamo", "dms", "elasticfilesystem"] + allowed_services_databases = ["rds", "dynamo", "elasticache"] + allowed_services_management = ["cloudwatch", "events", "logs", "servicequotas", "ssm"] + allowed_services_analytics = ["es", "firehose", "kinesis", "kinesisanalytics", "redshift"] + allowed_services_application = ["ses", "sns", "sqs", "xray", "applicationinsights", "application-autoscaling"] + allowed_services_security = ["iam", "acm", "kms", "secretsmanager"] + + allowed_services = concat( + local.allowed_services_compute, + local.allowed_services_networking, + local.allowed_services_storage, + local.allowed_services_databases, + local.allowed_services_management, + local.allowed_services_analytics, + local.allowed_services_application, + local.allowed_services_security + ) +} + +module "read_only_iam_policy" { + source = "../../modules/iam-read-only-policy" + + name = "example" + path = "/" + description = "My read only example policy" + + allowed_services = local.allowed_services + + tags = { + PolicyDescription = "My read only example policy" + } +} + +module "read_only_iam_policy_doc" { + source = "../../modules/iam-read-only-policy" + + name = "only-doc-example" + path = "/" + description = "My read only example policy" + + create_policy = false + + allowed_services = local.allowed_services + + tags = { + PolicyDescription = "My read only example policy" + } +} + +resource "aws_ssoadmin_permission_set" "example" { + name = "Example" + instance_arn = "arn:aws:sso:::instance/example" +} + +resource "aws_ssoadmin_permission_set_inline_policy" "example" { + inline_policy = module.read_only_iam_policy_doc.policy_json + instance_arn = aws_ssoadmin_permission_set.example.instance_arn + permission_set_arn = aws_ssoadmin_permission_set.example.arn +} diff --git a/modules/aws-iam/examples/iam-read-only-policy/outputs.tf b/modules/aws-iam/examples/iam-read-only-policy/outputs.tf new file mode 100644 index 0000000..9ca3c91 --- /dev/null +++ b/modules/aws-iam/examples/iam-read-only-policy/outputs.tf @@ -0,0 +1,29 @@ +output "id" { + description = "The policy ID" + value = module.read_only_iam_policy.id +} + +output "arn" { + description = "The ARN assigned by AWS to this policy" + value = module.read_only_iam_policy.arn +} + +output "description" { + description = "The description of the policy" + value = module.read_only_iam_policy.description +} + +output "name" { + description = "The name of the policy" + value = module.read_only_iam_policy.name +} + +output "path" { + description = "The path of the policy in IAM" + value = module.read_only_iam_policy.path +} + +output "policy" { + description = "The policy document" + value = module.read_only_iam_policy.policy +} diff --git a/modules/aws-iam/examples/iam-read-only-policy/variables.tf b/modules/aws-iam/examples/iam-read-only-policy/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-read-only-policy/versions.tf b/modules/aws-iam/examples/iam-read-only-policy/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-read-only-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-role-for-service-accounts-eks/README.md b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/README.md new file mode 100644 index 0000000..2602c9f --- /dev/null +++ b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/README.md @@ -0,0 +1,74 @@ +# IAM Role for Service Accounts in EKS + +Configuration in this directory creates IAM roles that can be assumed by multiple EKS `ServiceAccount`s for various tasks. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [amazon\_managed\_service\_prometheus\_irsa\_role](#module\_amazon\_managed\_service\_prometheus\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [appmesh\_controller\_irsa\_role](#module\_appmesh\_controller\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [appmesh\_envoy\_proxy\_irsa\_role](#module\_appmesh\_envoy\_proxy\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [cert\_manager\_irsa\_role](#module\_cert\_manager\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [disabled](#module\_disabled) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [efs\_csi\_irsa\_role](#module\_efs\_csi\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.21 | +| [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [external\_secrets\_irsa\_role](#module\_external\_secrets\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [fsx\_lustre\_csi\_irsa\_role](#module\_fsx\_lustre\_csi\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [irsa\_role](#module\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [karpenter\_controller\_irsa\_role](#module\_karpenter\_controller\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [load\_balancer\_controller\_irsa\_role](#module\_load\_balancer\_controller\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [load\_balancer\_controller\_targetgroup\_binding\_only\_irsa\_role](#module\_load\_balancer\_controller\_targetgroup\_binding\_only\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [node\_termination\_handler\_irsa\_role](#module\_node\_termination\_handler\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [velero\_irsa\_role](#module\_velero\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc\_cni\_ipv4\_irsa\_role](#module\_vpc\_cni\_ipv4\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [vpc\_cni\_ipv6\_irsa\_role](#module\_vpc\_cni\_ipv6\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/examples/iam-role-for-service-accounts-eks/main.tf b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/main.tf new file mode 100644 index 0000000..dd6b4dc --- /dev/null +++ b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/main.tf @@ -0,0 +1,398 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-iam-eks-role" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-iam" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# IRSA Roles +################################################################################ + +module "disabled" { + source = "../../modules/iam-role-for-service-accounts-eks" + + create_role = false +} + +module "irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = local.name + + oidc_providers = { + one = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + two = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:blue", "canary:blue"] + } + } + + role_policy_arns = { + AmazonEKS_CNI_Policy = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + additional = aws_iam_policy.additional.arn + } + + tags = local.tags +} + +module "cert_manager_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "cert-manager" + attach_cert_manager_policy = true + cert_manager_hosted_zone_arns = ["arn:aws:route53:::hostedzone/IClearlyMadeThisUp"] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:cert-manager"] + } + } + + tags = local.tags +} + +module "cluster_autoscaler_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "cluster-autoscaler" + attach_cluster_autoscaler_policy = true + cluster_autoscaler_cluster_ids = [module.eks.cluster_id] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:cluster-autoscaler"] + } + } + + tags = local.tags +} + +module "ebs_csi_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "ebs-csi" + attach_ebs_csi_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"] + } + } + + tags = local.tags +} + +module "efs_csi_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "efs-csi" + attach_efs_csi_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:efs-csi-controller-sa"] + } + } + + tags = local.tags +} + +module "external_dns_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "external-dns" + attach_external_dns_policy = true + external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/IClearlyMadeThisUp"] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:external-dns"] + } + } + + tags = local.tags +} + +module "external_secrets_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "external-secrets" + attach_external_secrets_policy = true + external_secrets_ssm_parameter_arns = ["arn:aws:ssm:*:*:parameter/foo"] + external_secrets_secrets_manager_arns = ["arn:aws:secretsmanager:*:*:secret:bar"] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:kubernetes-external-secrets"] + } + } + + tags = local.tags +} + +module "fsx_lustre_csi_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "fsx-lustre-csi" + attach_fsx_lustre_csi_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:fsx-csi-controller-sa"] + } + } +} + +module "karpenter_controller_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "karpenter-controller" + attach_karpenter_controller_policy = true + + karpenter_controller_cluster_id = module.eks.cluster_id + karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["karpenter:karpenter"] + } + } + + tags = local.tags +} + +module "load_balancer_controller_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "load-balancer-controller" + attach_load_balancer_controller_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-load-balancer-controller"] + } + } + + tags = local.tags +} + +module "load_balancer_controller_targetgroup_binding_only_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "load-balancer-controller-targetgroup-binding-only" + attach_load_balancer_controller_targetgroup_binding_only_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-load-balancer-controller"] + } + } + + tags = local.tags +} + +module "appmesh_controller_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "appmesh-controller" + attach_appmesh_controller_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["appmesh-system:appmesh-controller"] + } + } + + tags = local.tags +} + +module "appmesh_envoy_proxy_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "appmesh-envoy-proxy" + attach_appmesh_envoy_proxy_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["appmesh-system:appmesh-envoy-proxy"] + } + } + + tags = local.tags +} + +module "amazon_managed_service_prometheus_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "amazon-managed-service-prometheus" + attach_amazon_managed_service_prometheus_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["prometheus:amp-ingest"] + } + } + + tags = local.tags +} + +module "node_termination_handler_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "node-termination-handler" + attach_node_termination_handler_policy = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-node"] + } + } + + tags = local.tags +} + +module "velero_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "velero" + attach_velero_policy = true + velero_s3_bucket_arns = ["arn:aws:s3:::velero-backups"] + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["velero:velero"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv4_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "vpc-cni-ipv4" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-node"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv6_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "vpc-cni-ipv6" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv6 = true + + oidc_providers = { + ex = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:aws-node"] + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 18.21" + + cluster_name = local.name + cluster_version = "1.22" + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + eks_managed_node_groups = { + default = {} + } + + tags = local.tags +} + +resource "aws_iam_policy" "additional" { + name = "${local.name}-additional" + description = "Additional test policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) + + tags = local.tags +} diff --git a/modules/aws-iam/examples/iam-role-for-service-accounts-eks/outputs.tf b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/outputs.tf new file mode 100644 index 0000000..f075453 --- /dev/null +++ b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.irsa_role.iam_role_arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.irsa_role.iam_role_name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.irsa_role.iam_role_path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.irsa_role.iam_role_unique_id +} diff --git a/modules/aws-iam/examples/iam-role-for-service-accounts-eks/variables.tf b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-role-for-service-accounts-eks/versions.tf b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-role-for-service-accounts-eks/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/examples/iam-user/README.md b/modules/aws-iam/examples/iam-user/README.md new file mode 100644 index 0000000..570f9c7 --- /dev/null +++ b/modules/aws-iam/examples/iam-user/README.md @@ -0,0 +1,64 @@ +# IAM user example + +Configuration in this directory creates IAM user with a random password, a pair of IAM access/secret keys and uploads IAM SSH public key. +User password and secret key is encrypted using public key of keybase.io user named `test`. + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_user](#module\_iam\_user) | ../../modules/iam-user | n/a | +| [iam\_user2](#module\_iam\_user2) | ../../modules/iam-user | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_access\_key\_encrypted\_secret](#output\_iam\_access\_key\_encrypted\_secret) | The encrypted secret, base64 encoded | +| [iam\_access\_key\_id](#output\_iam\_access\_key\_id) | The access key ID | +| [iam\_access\_key\_key\_fingerprint](#output\_iam\_access\_key\_key\_fingerprint) | The fingerprint of the PGP key used to encrypt the secret | +| [iam\_access\_key\_secret](#output\_iam\_access\_key\_secret) | The access key secret | +| [iam\_access\_key\_ses\_smtp\_password\_v4](#output\_iam\_access\_key\_ses\_smtp\_password\_v4) | The secret access key converted into an SES SMTP password | +| [iam\_access\_key\_status](#output\_iam\_access\_key\_status) | Active or Inactive. Keys are initially active, but can be made inactive by other means. | +| [iam\_user\_arn](#output\_iam\_user\_arn) | The ARN assigned by AWS for this user | +| [iam\_user\_login\_profile\_encrypted\_password](#output\_iam\_user\_login\_profile\_encrypted\_password) | The encrypted password, base64 encoded | +| [iam\_user\_login\_profile\_key\_fingerprint](#output\_iam\_user\_login\_profile\_key\_fingerprint) | The fingerprint of the PGP key used to encrypt the password | +| [iam\_user\_login\_profile\_password](#output\_iam\_user\_login\_profile\_password) | The user password | +| [iam\_user\_name](#output\_iam\_user\_name) | The user's name | +| [iam\_user\_unique\_id](#output\_iam\_user\_unique\_id) | The unique ID assigned by AWS | +| [keybase\_password\_decrypt\_command](#output\_keybase\_password\_decrypt\_command) | Decrypt user password command | +| [keybase\_password\_pgp\_message](#output\_keybase\_password\_pgp\_message) | Encrypted password | +| [keybase\_secret\_key\_decrypt\_command](#output\_keybase\_secret\_key\_decrypt\_command) | Decrypt access secret key command | +| [keybase\_secret\_key\_pgp\_message](#output\_keybase\_secret\_key\_pgp\_message) | Encrypted access secret key | +| [pgp\_key](#output\_pgp\_key) | PGP key used to encrypt sensitive data for this user (if empty - secrets are not encrypted) | \ No newline at end of file diff --git a/modules/aws-iam/examples/iam-user/main.tf b/modules/aws-iam/examples/iam-user/main.tf new file mode 100644 index 0000000..c4d5fef --- /dev/null +++ b/modules/aws-iam/examples/iam-user/main.tf @@ -0,0 +1,35 @@ +provider "aws" { + region = "eu-west-1" +} + +######################################### +# IAM user, login profile and access key +######################################### +module "iam_user" { + source = "../../modules/iam-user" + + name = "vasya.pupkin" + force_destroy = true + + # User "test" has uploaded his public key here - https://keybase.io/test/pgp_keys.asc + pgp_key = "keybase:test" + + password_reset_required = false + + # SSH public key + upload_iam_user_ssh_key = true + + ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0sUjdTEcOWYgQ7ESnHsSkvPUO2tEvZxxQHUZYh9j6BPZgfn13iYhfAP2cfZznzrV+2VMamMtfiAiWR39LKo/bMN932HOp2Qx2la14IbiZ91666FD+yZ4+vhR2IVhZMe4D+g8FmhCfw1+zZhgl8vQBgsRZIcYqpYux59FcPv0lP1EhYahoRsUt1SEU2Gj+jvgyZpe15lnWk2VzfIpIsZ++AeUqyHoJHV0RVOK4MLRssqGHye6XkA3A+dMm2Mjgi8hxoL5uuwtkIsAll0kSfL5O2G26nsxm/Fpcl+SKSO4gs01d9V83xiOwviyOxmoXzwKy4qaUGtgq1hWncDNIVG/aQ==" +} + +################################################################### +# IAM user without pgp_key (IAM access secret will be unencrypted) +################################################################### +module "iam_user2" { + source = "../../modules/iam-user" + + name = "vasya.pupkin4" + + create_iam_user_login_profile = false + create_iam_access_key = true +} diff --git a/modules/aws-iam/examples/iam-user/outputs.tf b/modules/aws-iam/examples/iam-user/outputs.tf new file mode 100644 index 0000000..72975c1 --- /dev/null +++ b/modules/aws-iam/examples/iam-user/outputs.tf @@ -0,0 +1,87 @@ +output "iam_user_name" { + description = "The user's name" + value = module.iam_user.iam_user_name +} + +output "iam_user_arn" { + description = "The ARN assigned by AWS for this user" + value = module.iam_user.iam_user_arn +} + +output "iam_user_unique_id" { + description = "The unique ID assigned by AWS" + value = module.iam_user.iam_user_unique_id +} + +output "iam_user_login_profile_key_fingerprint" { + description = "The fingerprint of the PGP key used to encrypt the password" + value = module.iam_user.iam_user_login_profile_key_fingerprint +} + +output "iam_user_login_profile_encrypted_password" { + description = "The encrypted password, base64 encoded" + value = module.iam_user.iam_user_login_profile_encrypted_password +} + +output "iam_user_login_profile_password" { + description = "The user password" + value = module.iam_user.iam_user_login_profile_password + sensitive = true +} + +output "iam_access_key_id" { + description = "The access key ID" + value = module.iam_user.iam_access_key_id +} + +output "iam_access_key_key_fingerprint" { + description = "The fingerprint of the PGP key used to encrypt the secret" + value = module.iam_user.iam_access_key_key_fingerprint +} + +output "iam_access_key_encrypted_secret" { + description = "The encrypted secret, base64 encoded" + value = module.iam_user.iam_access_key_encrypted_secret +} + +output "iam_access_key_secret" { + description = "The access key secret" + value = module.iam_user.iam_access_key_secret + sensitive = true +} + +output "iam_access_key_ses_smtp_password_v4" { + description = "The secret access key converted into an SES SMTP password" + value = module.iam_user.iam_access_key_ses_smtp_password_v4 + sensitive = true +} + +output "iam_access_key_status" { + description = "Active or Inactive. Keys are initially active, but can be made inactive by other means." + value = module.iam_user.iam_access_key_status +} + +output "pgp_key" { + description = "PGP key used to encrypt sensitive data for this user (if empty - secrets are not encrypted)" + value = module.iam_user.pgp_key +} + +output "keybase_password_decrypt_command" { + description = "Decrypt user password command" + value = module.iam_user.keybase_password_decrypt_command +} + +output "keybase_password_pgp_message" { + description = "Encrypted password" + value = module.iam_user.keybase_password_pgp_message +} + +output "keybase_secret_key_decrypt_command" { + description = "Decrypt access secret key command" + value = module.iam_user.keybase_secret_key_decrypt_command +} + +output "keybase_secret_key_pgp_message" { + description = "Encrypted access secret key" + value = module.iam_user.keybase_secret_key_pgp_message +} diff --git a/modules/aws-iam/examples/iam-user/variables.tf b/modules/aws-iam/examples/iam-user/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-iam/examples/iam-user/versions.tf b/modules/aws-iam/examples/iam-user/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/examples/iam-user/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-account/README.md b/modules/aws-iam/modules/iam-account/README.md new file mode 100644 index 0000000..d78919e --- /dev/null +++ b/modules/aws-iam/modules/iam-account/README.md @@ -0,0 +1,75 @@ +# iam-account + +Manage IAM account alias and password policy. + +## Notes + +* If IAM account alias was previously set (either via AWS console or during the creation of an account from AWS Organizations) you will see this error: +``` +* aws_iam_account_alias.this: Error creating account alias with name my-account-alias +``` + +If you want to manage IAM alias using Terraform (otherwise why are you reading this?) you need to import this resource like this: +``` +$ terraform import module.iam_account.aws_iam_account_alias.this this + +module.iam_account.aws_iam_account_alias.this: Importing from ID "this"... +module.iam_account.aws_iam_account_alias.this: Import complete! + Imported aws_iam_account_alias (ID: this) +module.iam_account.aws_iam_account_alias.this: Refreshing state... (ID: this) + +Import successful! +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_account_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_account_alias) | resource | +| [aws_iam_account_password_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_account_password_policy) | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_alias](#input\_account\_alias) | AWS IAM account alias for this account | `string` | n/a | yes | +| [allow\_users\_to\_change\_password](#input\_allow\_users\_to\_change\_password) | Whether to allow users to change their own password | `bool` | `true` | no | +| [create\_account\_password\_policy](#input\_create\_account\_password\_policy) | Whether to create AWS IAM account password policy | `bool` | `true` | no | +| [get\_caller\_identity](#input\_get\_caller\_identity) | Whether to get AWS account ID, User ID, and ARN in which Terraform is authorized | `bool` | `true` | no | +| [hard\_expiry](#input\_hard\_expiry) | Whether users are prevented from setting a new password after their password has expired (i.e. require administrator reset) | `bool` | `false` | no | +| [max\_password\_age](#input\_max\_password\_age) | The number of days that an user password is valid. | `number` | `0` | no | +| [minimum\_password\_length](#input\_minimum\_password\_length) | Minimum length to require for user passwords | `number` | `8` | no | +| [password\_reuse\_prevention](#input\_password\_reuse\_prevention) | The number of previous passwords that users are prevented from reusing | `number` | `null` | no | +| [require\_lowercase\_characters](#input\_require\_lowercase\_characters) | Whether to require lowercase characters for user passwords | `bool` | `true` | no | +| [require\_numbers](#input\_require\_numbers) | Whether to require numbers for user passwords | `bool` | `true` | no | +| [require\_symbols](#input\_require\_symbols) | Whether to require symbols for user passwords | `bool` | `true` | no | +| [require\_uppercase\_characters](#input\_require\_uppercase\_characters) | Whether to require uppercase characters for user passwords | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [caller\_identity\_account\_id](#output\_caller\_identity\_account\_id) | The AWS Account ID number of the account that owns or contains the calling entity | +| [caller\_identity\_arn](#output\_caller\_identity\_arn) | The AWS ARN associated with the calling entity | +| [caller\_identity\_user\_id](#output\_caller\_identity\_user\_id) | The unique identifier of the calling entity | +| [iam\_account\_password\_policy\_expire\_passwords](#output\_iam\_account\_password\_policy\_expire\_passwords) | Indicates whether passwords in the account expire. Returns true if max\_password\_age contains a value greater than 0. Returns false if it is 0 or not present. | + diff --git a/modules/aws-iam/modules/iam-account/main.tf b/modules/aws-iam/modules/iam-account/main.tf new file mode 100644 index 0000000..f53d596 --- /dev/null +++ b/modules/aws-iam/modules/iam-account/main.tf @@ -0,0 +1,21 @@ +data "aws_caller_identity" "this" { + count = var.get_caller_identity ? 1 : 0 +} + +resource "aws_iam_account_alias" "this" { + account_alias = var.account_alias +} + +resource "aws_iam_account_password_policy" "this" { + count = var.create_account_password_policy ? 1 : 0 + + max_password_age = var.max_password_age + minimum_password_length = var.minimum_password_length + allow_users_to_change_password = var.allow_users_to_change_password + hard_expiry = var.hard_expiry + password_reuse_prevention = var.password_reuse_prevention + require_lowercase_characters = var.require_lowercase_characters + require_uppercase_characters = var.require_uppercase_characters + require_numbers = var.require_numbers + require_symbols = var.require_symbols +} diff --git a/modules/aws-iam/modules/iam-account/outputs.tf b/modules/aws-iam/modules/iam-account/outputs.tf new file mode 100644 index 0000000..c286d1e --- /dev/null +++ b/modules/aws-iam/modules/iam-account/outputs.tf @@ -0,0 +1,19 @@ +output "caller_identity_account_id" { + description = "The AWS Account ID number of the account that owns or contains the calling entity" + value = try(data.aws_caller_identity.this[0].account_id, "") +} + +output "caller_identity_arn" { + description = "The AWS ARN associated with the calling entity" + value = try(data.aws_caller_identity.this[0].arn, "") +} + +output "caller_identity_user_id" { + description = "The unique identifier of the calling entity" + value = try(data.aws_caller_identity.this[0].user_id, "") +} + +output "iam_account_password_policy_expire_passwords" { + description = "Indicates whether passwords in the account expire. Returns true if max_password_age contains a value greater than 0. Returns false if it is 0 or not present." + value = try(aws_iam_account_password_policy.this[0].expire_passwords, "") +} diff --git a/modules/aws-iam/modules/iam-account/variables.tf b/modules/aws-iam/modules/iam-account/variables.tf new file mode 100644 index 0000000..7f58f8a --- /dev/null +++ b/modules/aws-iam/modules/iam-account/variables.tf @@ -0,0 +1,70 @@ +variable "get_caller_identity" { + description = "Whether to get AWS account ID, User ID, and ARN in which Terraform is authorized" + type = bool + default = true +} + +variable "account_alias" { + description = "AWS IAM account alias for this account" + type = string +} + +variable "create_account_password_policy" { + description = "Whether to create AWS IAM account password policy" + type = bool + default = true +} + +variable "max_password_age" { + description = "The number of days that an user password is valid." + type = number + default = 0 +} + +variable "minimum_password_length" { + description = "Minimum length to require for user passwords" + type = number + default = 8 +} + +variable "allow_users_to_change_password" { + description = "Whether to allow users to change their own password" + type = bool + default = true +} + +variable "hard_expiry" { + description = "Whether users are prevented from setting a new password after their password has expired (i.e. require administrator reset)" + type = bool + default = false +} + +variable "password_reuse_prevention" { + description = "The number of previous passwords that users are prevented from reusing" + type = number + default = null +} + +variable "require_lowercase_characters" { + description = "Whether to require lowercase characters for user passwords" + type = bool + default = true +} + +variable "require_uppercase_characters" { + description = "Whether to require uppercase characters for user passwords" + type = bool + default = true +} + +variable "require_numbers" { + description = "Whether to require numbers for user passwords" + type = bool + default = true +} + +variable "require_symbols" { + description = "Whether to require symbols for user passwords" + type = bool + default = true +} diff --git a/modules/aws-iam/modules/iam-account/versions.tf b/modules/aws-iam/modules/iam-account/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-account/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-oidc/README.md b/modules/aws-iam/modules/iam-assumable-role-with-oidc/README.md new file mode 100644 index 0000000..755bbe2 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-oidc/README.md @@ -0,0 +1,67 @@ +# iam-assumable-role-with-oidc + +Creates single IAM role which can be assumed by trusted resources using OpenID Connect Federated Users. + +[Creating IAM OIDC Identity Providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) + +This module supports IAM Roles for kubernetes service accounts as described in the [EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role_with_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_account\_id](#input\_aws\_account\_id) | The AWS account ID where the OIDC provider lives, leave empty to use the account for the AWS provider | `string` | `""` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `false` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | +| [number\_of\_role\_policy\_arns](#input\_number\_of\_role\_policy\_arns) | Number of IAM policies to attach to IAM role | `number` | `null` | no | +| [oidc\_fully\_qualified\_audiences](#input\_oidc\_fully\_qualified\_audiences) | The audience to be added to the role policy. Set to sts.amazonaws.com for cross-account assumable role. Leave empty otherwise. | `set(string)` | `[]` | no | +| [oidc\_fully\_qualified\_subjects](#input\_oidc\_fully\_qualified\_subjects) | The fully qualified OIDC subjects to be added to the role policy | `set(string)` | `[]` | no | +| [oidc\_subjects\_with\_wildcards](#input\_oidc\_subjects\_with\_wildcards) | The OIDC subject using wildcards to be added to the role policy | `set(string)` | `[]` | no | +| [provider\_url](#input\_provider\_url) | URL of the OIDC Provider. Use provider\_urls to specify several URLs. | `string` | `""` | no | +| [provider\_urls](#input\_provider\_urls) | List of URLs of the OIDC Providers | `list(string)` | `[]` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no | +| [role\_name](#input\_role\_name) | IAM role name | `string` | `null` | no | +| [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | +| [role\_policy\_arns](#input\_role\_policy\_arns) | List of ARNs of IAM policies to attach to IAM role | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to IAM role resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/modules/iam-assumable-role-with-oidc/main.tf b/modules/aws-iam/modules/iam-assumable-role-with-oidc/main.tf new file mode 100644 index 0000000..586bd50 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-oidc/main.tf @@ -0,0 +1,87 @@ +locals { + aws_account_id = var.aws_account_id != "" ? var.aws_account_id : data.aws_caller_identity.current.account_id + # clean URLs of https:// prefix + urls = [ + for url in compact(distinct(concat(var.provider_urls, [var.provider_url]))) : + replace(url, "https://", "") + ] + number_of_role_policy_arns = coalesce(var.number_of_role_policy_arns, length(var.role_policy_arns)) +} + +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +data "aws_iam_policy_document" "assume_role_with_oidc" { + count = var.create_role ? 1 : 0 + + dynamic "statement" { + for_each = local.urls + + content { + effect = "Allow" + + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + + identifiers = ["arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:oidc-provider/${statement.value}"] + } + + dynamic "condition" { + for_each = length(var.oidc_fully_qualified_subjects) > 0 ? local.urls : [] + + content { + test = "StringEquals" + variable = "${statement.value}:sub" + values = var.oidc_fully_qualified_subjects + } + } + + dynamic "condition" { + for_each = length(var.oidc_subjects_with_wildcards) > 0 ? local.urls : [] + + content { + test = "StringLike" + variable = "${statement.value}:sub" + values = var.oidc_subjects_with_wildcards + } + } + + dynamic "condition" { + for_each = length(var.oidc_fully_qualified_audiences) > 0 ? local.urls : [] + + content { + test = "StringLike" + variable = "${statement.value}:aud" + values = var.oidc_fully_qualified_audiences + } + } + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + name = var.role_name + name_prefix = var.role_name_prefix + description = var.role_description + path = var.role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.role_permissions_boundary_arn + + assume_role_policy = join("", data.aws_iam_policy_document.assume_role_with_oidc.*.json) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "custom" { + count = var.create_role ? local.number_of_role_policy_arns : 0 + + role = join("", aws_iam_role.this.*.name) + policy_arn = var.role_policy_arns[count.index] +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-oidc/outputs.tf b/modules/aws-iam/modules/iam-assumable-role-with-oidc/outputs.tf new file mode 100644 index 0000000..a680531 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-oidc/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-oidc/variables.tf b/modules/aws-iam/modules/iam-assumable-role-with-oidc/variables.tf new file mode 100644 index 0000000..5576f5f --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-oidc/variables.tf @@ -0,0 +1,101 @@ +variable "create_role" { + description = "Whether to create a role" + type = bool + default = false +} + +variable "provider_url" { + description = "URL of the OIDC Provider. Use provider_urls to specify several URLs." + type = string + default = "" +} + +variable "provider_urls" { + description = "List of URLs of the OIDC Providers" + type = list(string) + default = [] +} + +variable "aws_account_id" { + description = "The AWS account ID where the OIDC provider lives, leave empty to use the account for the AWS provider" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to IAM role resources" + type = map(string) + default = {} +} + +variable "role_name" { + description = "IAM role name" + type = string + default = null +} + +variable "role_name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = "" +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = "/" +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = "" +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 3600 +} + +variable "role_policy_arns" { + description = "List of ARNs of IAM policies to attach to IAM role" + type = list(string) + default = [] +} + +variable "number_of_role_policy_arns" { + description = "Number of IAM policies to attach to IAM role" + type = number + default = null +} + +variable "oidc_fully_qualified_subjects" { + description = "The fully qualified OIDC subjects to be added to the role policy" + type = set(string) + default = [] +} + +variable "oidc_subjects_with_wildcards" { + description = "The OIDC subject using wildcards to be added to the role policy" + type = set(string) + default = [] +} + +variable "oidc_fully_qualified_audiences" { + description = "The audience to be added to the role policy. Set to sts.amazonaws.com for cross-account assumable role. Leave empty otherwise." + type = set(string) + default = [] +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-oidc/versions.tf b/modules/aws-iam/modules/iam-assumable-role-with-oidc/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-oidc/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-saml/README.md b/modules/aws-iam/modules/iam-assumable-role-with-saml/README.md new file mode 100644 index 0000000..424b456 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-saml/README.md @@ -0,0 +1,61 @@ +# iam-assumable-role-with-saml + +Creates single IAM role which can be assumed by trusted resources using SAML Federated Users. + +[Creating IAM SAML Identity Providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml.html) +[Enabling SAML 2.0 Federated Users to Access the AWS Management Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-saml.html) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.assume_role_with_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_saml\_endpoint](#input\_aws\_saml\_endpoint) | AWS SAML Endpoint | `string` | `"https://signin.aws.amazon.com/saml"` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `false` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | +| [number\_of\_role\_policy\_arns](#input\_number\_of\_role\_policy\_arns) | Number of IAM policies to attach to IAM role | `number` | `null` | no | +| [provider\_id](#input\_provider\_id) | ID of the SAML Provider. Use provider\_ids to specify several IDs. | `string` | `""` | no | +| [provider\_ids](#input\_provider\_ids) | List of SAML Provider IDs | `list(string)` | `[]` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no | +| [role\_name](#input\_role\_name) | IAM role name | `string` | `null` | no | +| [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | +| [role\_policy\_arns](#input\_role\_policy\_arns) | List of ARNs of IAM policies to attach to IAM role | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to IAM role resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/modules/iam-assumable-role-with-saml/main.tf b/modules/aws-iam/modules/iam-assumable-role-with-saml/main.tf new file mode 100644 index 0000000..eb9d803 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-saml/main.tf @@ -0,0 +1,48 @@ +locals { + identifiers = compact(distinct(concat(var.provider_ids, [var.provider_id]))) + number_of_role_policy_arns = coalesce(var.number_of_role_policy_arns, length(var.role_policy_arns)) +} + +data "aws_iam_policy_document" "assume_role_with_saml" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRoleWithSAML"] + + principals { + type = "Federated" + + identifiers = local.identifiers + } + + condition { + test = "StringEquals" + variable = "SAML:aud" + values = [var.aws_saml_endpoint] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + name = var.role_name + name_prefix = var.role_name_prefix + description = var.role_description + path = var.role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.role_permissions_boundary_arn + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_saml.json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "custom" { + count = var.create_role ? local.number_of_role_policy_arns : 0 + + role = join("", aws_iam_role.this.*.name) + policy_arn = var.role_policy_arns[count.index] +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-saml/outputs.tf b/modules/aws-iam/modules/iam-assumable-role-with-saml/outputs.tf new file mode 100644 index 0000000..a680531 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-saml/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-saml/variables.tf b/modules/aws-iam/modules/iam-assumable-role-with-saml/variables.tf new file mode 100644 index 0000000..e363ed4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-saml/variables.tf @@ -0,0 +1,83 @@ +variable "create_role" { + description = "Whether to create a role" + type = bool + default = false +} + +variable "provider_id" { + description = "ID of the SAML Provider. Use provider_ids to specify several IDs." + type = string + default = "" +} + +variable "provider_ids" { + description = "List of SAML Provider IDs" + type = list(string) + default = [] +} + +variable "aws_saml_endpoint" { + description = "AWS SAML Endpoint" + default = "https://signin.aws.amazon.com/saml" + type = string +} + +variable "tags" { + description = "A map of tags to add to IAM role resources" + type = map(string) + default = {} +} + +variable "role_name" { + description = "IAM role name" + type = string + default = null +} + +variable "role_name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = "" +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = "/" +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = "" +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 3600 +} + +variable "role_policy_arns" { + description = "List of ARNs of IAM policies to attach to IAM role" + type = list(string) + default = [] +} + +variable "number_of_role_policy_arns" { + description = "Number of IAM policies to attach to IAM role" + type = number + default = null +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} diff --git a/modules/aws-iam/modules/iam-assumable-role-with-saml/versions.tf b/modules/aws-iam/modules/iam-assumable-role-with-saml/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role-with-saml/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-assumable-role/README.md b/modules/aws-iam/modules/iam-assumable-role/README.md new file mode 100644 index 0000000..9b6d4e8 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role/README.md @@ -0,0 +1,80 @@ +# iam-assumable-role + +Creates single IAM role which can be assumed by trusted resources. + +Trusted resources can be any [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns) - typically, AWS accounts and users. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_with_mfa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_role\_policy\_arn](#input\_admin\_role\_policy\_arn) | Policy ARN to use for admin role | `string` | `"arn:aws:iam::aws:policy/AdministratorAccess"` | no | +| [attach\_admin\_policy](#input\_attach\_admin\_policy) | Whether to attach an admin policy to a role | `bool` | `false` | no | +| [attach\_poweruser\_policy](#input\_attach\_poweruser\_policy) | Whether to attach a poweruser policy to a role | `bool` | `false` | no | +| [attach\_readonly\_policy](#input\_attach\_readonly\_policy) | Whether to attach a readonly policy to a role | `bool` | `false` | no | +| [create\_instance\_profile](#input\_create\_instance\_profile) | Whether to create an instance profile | `bool` | `false` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `false` | no | +| [custom\_role\_policy\_arns](#input\_custom\_role\_policy\_arns) | List of ARNs of IAM policies to attach to IAM role | `list(string)` | `[]` | no | +| [custom\_role\_trust\_policy](#input\_custom\_role\_trust\_policy) | A custom role trust policy | `string` | `""` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | +| [mfa\_age](#input\_mfa\_age) | Max age of valid MFA (in seconds) for roles which require MFA | `number` | `86400` | no | +| [number\_of\_custom\_role\_policy\_arns](#input\_number\_of\_custom\_role\_policy\_arns) | Number of IAM policies to attach to IAM role | `number` | `null` | no | +| [poweruser\_role\_policy\_arn](#input\_poweruser\_role\_policy\_arn) | Policy ARN to use for poweruser role | `string` | `"arn:aws:iam::aws:policy/PowerUserAccess"` | no | +| [readonly\_role\_policy\_arn](#input\_readonly\_role\_policy\_arn) | Policy ARN to use for readonly role | `string` | `"arn:aws:iam::aws:policy/ReadOnlyAccess"` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no | +| [role\_name](#input\_role\_name) | IAM role name | `string` | `""` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | +| [role\_requires\_mfa](#input\_role\_requires\_mfa) | Whether role requires MFA | `bool` | `true` | no | +| [role\_sts\_externalid](#input\_role\_sts\_externalid) | STS ExternalId condition values to use with a role (when MFA is not required) | `any` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to IAM role resources | `map(string)` | `{}` | no | +| [trusted\_role\_actions](#input\_trusted\_role\_actions) | Actions of STS | `list(string)` |
[
"sts:AssumeRole"
]
| no | +| [trusted\_role\_arns](#input\_trusted\_role\_arns) | ARNs of AWS entities who can assume these roles | `list(string)` | `[]` | no | +| [trusted\_role\_services](#input\_trusted\_role\_services) | AWS Services that can assume these roles | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_instance\_profile\_arn](#output\_iam\_instance\_profile\_arn) | ARN of IAM instance profile | +| [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | IAM Instance profile's ID. | +| [iam\_instance\_profile\_name](#output\_iam\_instance\_profile\_name) | Name of IAM instance profile | +| [iam\_instance\_profile\_path](#output\_iam\_instance\_profile\_path) | Path of IAM instance profile | +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [role\_requires\_mfa](#output\_role\_requires\_mfa) | Whether IAM role requires MFA | +| [role\_sts\_externalid](#output\_role\_sts\_externalid) | STS ExternalId condition value to use with a role | diff --git a/modules/aws-iam/modules/iam-assumable-role/main.tf b/modules/aws-iam/modules/iam-assumable-role/main.tf new file mode 100644 index 0000000..6255dae --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role/main.tf @@ -0,0 +1,141 @@ +locals { + role_sts_externalid = flatten([var.role_sts_externalid]) + role_sts_orgid = flatten([var.role_sts_orgid]) +} + +data "aws_iam_policy_document" "assume_role" { + count = var.custom_role_trust_policy == "" && var.role_requires_mfa ? 0 : 1 + + statement { + effect = "Allow" + + actions = var.trusted_role_actions + + principals { + type = "AWS" + identifiers = var.trusted_role_arns + } + + principals { + type = "Service" + identifiers = var.trusted_role_services + } + + dynamic "condition" { + for_each = length(local.role_sts_orgid) != 0 ? [true] : [] + content { + test = "StringEquals" + variable = "aws:PrincipalOrgID" + values = local.role_sts_orgid + } + } + + dynamic "condition" { + for_each = length(local.role_sts_externalid) != 0 ? [true] : [] + content { + test = "StringEquals" + variable = "sts:ExternalId" + values = local.role_sts_externalid + } + } + } +} + +data "aws_iam_policy_document" "assume_role_with_mfa" { + count = var.custom_role_trust_policy == "" && var.role_requires_mfa ? 1 : 0 + + statement { + effect = "Allow" + + actions = var.trusted_role_actions + + principals { + type = "AWS" + identifiers = var.trusted_role_arns + } + + principals { + type = "Service" + identifiers = var.trusted_role_services + } + + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + + condition { + test = "NumericLessThan" + variable = "aws:MultiFactorAuthAge" + values = [var.mfa_age] + } + + dynamic "condition" { + for_each = length(local.role_sts_externalid) != 0 ? [true] : [] + content { + test = "StringEquals" + variable = "sts:ExternalId" + values = local.role_sts_externalid + } + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + name = var.role_name + path = var.role_path + max_session_duration = var.max_session_duration + description = var.role_description + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.role_permissions_boundary_arn + + assume_role_policy = coalesce( + var.custom_role_trust_policy, + try(data.aws_iam_policy_document.assume_role_with_mfa[0].json, + data.aws_iam_policy_document.assume_role[0].json + ) + ) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "custom" { + count = var.create_role ? coalesce(var.number_of_custom_role_policy_arns, length(var.custom_role_policy_arns)) : 0 + + role = aws_iam_role.this[0].name + policy_arn = element(var.custom_role_policy_arns, count.index) +} + +resource "aws_iam_role_policy_attachment" "admin" { + count = var.create_role && var.attach_admin_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = var.admin_role_policy_arn +} + +resource "aws_iam_role_policy_attachment" "poweruser" { + count = var.create_role && var.attach_poweruser_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = var.poweruser_role_policy_arn +} + +resource "aws_iam_role_policy_attachment" "readonly" { + count = var.create_role && var.attach_readonly_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = var.readonly_role_policy_arn +} + +resource "aws_iam_instance_profile" "this" { + count = var.create_role && var.create_instance_profile ? 1 : 0 + name = var.role_name + path = var.role_path + role = aws_iam_role.this[0].name + + tags = var.tags +} diff --git a/modules/aws-iam/modules/iam-assumable-role/outputs.tf b/modules/aws-iam/modules/iam-assumable-role/outputs.tf new file mode 100644 index 0000000..117ff07 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role/outputs.tf @@ -0,0 +1,49 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} + +output "role_requires_mfa" { + description = "Whether IAM role requires MFA" + value = var.role_requires_mfa +} + +output "iam_instance_profile_arn" { + description = "ARN of IAM instance profile" + value = try(aws_iam_instance_profile.this[0].arn, "") +} + +output "iam_instance_profile_name" { + description = "Name of IAM instance profile" + value = try(aws_iam_instance_profile.this[0].name, "") +} + +output "iam_instance_profile_id" { + description = "IAM Instance profile's ID." + value = try(aws_iam_instance_profile.this[0].id, "") +} + +output "iam_instance_profile_path" { + description = "Path of IAM instance profile" + value = try(aws_iam_instance_profile.this[0].path, "") +} + +output "role_sts_externalid" { + description = "STS ExternalId condition value to use with a role" + value = var.role_sts_externalid +} diff --git a/modules/aws-iam/modules/iam-assumable-role/variables.tf b/modules/aws-iam/modules/iam-assumable-role/variables.tf new file mode 100644 index 0000000..747e5de --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role/variables.tf @@ -0,0 +1,151 @@ +variable "trusted_role_actions" { + description = "Actions of STS" + type = list(string) + default = ["sts:AssumeRole"] +} + +variable "trusted_role_arns" { + description = "ARNs of AWS entities who can assume these roles" + type = list(string) + default = [] +} + +variable "trusted_role_services" { + description = "AWS Services that can assume these roles" + type = list(string) + default = [] +} + +variable "mfa_age" { + description = "Max age of valid MFA (in seconds) for roles which require MFA" + type = number + default = 86400 +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 3600 +} + +variable "create_role" { + description = "Whether to create a role" + type = bool + default = false +} + +variable "create_instance_profile" { + description = "Whether to create an instance profile" + type = bool + default = false +} + +variable "role_name" { + description = "IAM role name" + type = string + default = "" +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = "/" +} + +variable "role_requires_mfa" { + description = "Whether role requires MFA" + type = bool + default = true +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to IAM role resources" + type = map(string) + default = {} +} + +variable "custom_role_policy_arns" { + description = "List of ARNs of IAM policies to attach to IAM role" + type = list(string) + default = [] +} + +variable "custom_role_trust_policy" { + description = "A custom role trust policy" + type = string + default = "" +} + +variable "number_of_custom_role_policy_arns" { + description = "Number of IAM policies to attach to IAM role" + type = number + default = null +} + +# Pre-defined policies +variable "admin_role_policy_arn" { + description = "Policy ARN to use for admin role" + type = string + default = "arn:aws:iam::aws:policy/AdministratorAccess" +} + +variable "poweruser_role_policy_arn" { + description = "Policy ARN to use for poweruser role" + type = string + default = "arn:aws:iam::aws:policy/PowerUserAccess" +} + +variable "readonly_role_policy_arn" { + description = "Policy ARN to use for readonly role" + type = string + default = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +variable "attach_admin_policy" { + description = "Whether to attach an admin policy to a role" + type = bool + default = false +} + +variable "attach_poweruser_policy" { + description = "Whether to attach a poweruser policy to a role" + type = bool + default = false +} + +variable "attach_readonly_policy" { + description = "Whether to attach a readonly policy to a role" + type = bool + default = false +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = "" +} + +variable "role_sts_externalid" { + description = "STS ExternalId condition values to use with a role (when MFA is not required)" + type = any + default = [] +} + + +variable "role_sts_orgid" { + description = "STS Organization ID condition values to use with a role (when MFA is not required)" + type = any + default = [] +} \ No newline at end of file diff --git a/modules/aws-iam/modules/iam-assumable-role/versions.tf b/modules/aws-iam/modules/iam-assumable-role/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-role/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-assumable-roles-with-saml/README.md b/modules/aws-iam/modules/iam-assumable-roles-with-saml/README.md new file mode 100644 index 0000000..c665f99 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles-with-saml/README.md @@ -0,0 +1,82 @@ +# iam-assumable-roles-with-saml + +Creates predefined IAM roles (admin, poweruser and readonly) which can be assumed by trusted resources using SAML Federated Users. + + +[Creating IAM SAML Identity Providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml.html) +[Enabling SAML 2.0 Federated Users to Access the AWS Management Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-saml.html) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.assume_role_with_saml](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_role\_name](#input\_admin\_role\_name) | IAM role with admin access | `string` | `"admin"` | no | +| [admin\_role\_path](#input\_admin\_role\_path) | Path of admin IAM role | `string` | `"/"` | no | +| [admin\_role\_permissions\_boundary\_arn](#input\_admin\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for admin role | `string` | `""` | no | +| [admin\_role\_policy\_arns](#input\_admin\_role\_policy\_arns) | List of policy ARNs to use for admin role | `list(string)` |
[
"arn:aws:iam::aws:policy/AdministratorAccess"
]
| no | +| [admin\_role\_tags](#input\_admin\_role\_tags) | A map of tags to add to admin role resource. | `map(string)` | `{}` | no | +| [aws\_saml\_endpoint](#input\_aws\_saml\_endpoint) | AWS SAML Endpoint | `string` | `"https://signin.aws.amazon.com/saml"` | no | +| [create\_admin\_role](#input\_create\_admin\_role) | Whether to create admin role | `bool` | `false` | no | +| [create\_poweruser\_role](#input\_create\_poweruser\_role) | Whether to create poweruser role | `bool` | `false` | no | +| [create\_readonly\_role](#input\_create\_readonly\_role) | Whether to create readonly role | `bool` | `false` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | +| [poweruser\_role\_name](#input\_poweruser\_role\_name) | IAM role with poweruser access | `string` | `"poweruser"` | no | +| [poweruser\_role\_path](#input\_poweruser\_role\_path) | Path of poweruser IAM role | `string` | `"/"` | no | +| [poweruser\_role\_permissions\_boundary\_arn](#input\_poweruser\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for poweruser role | `string` | `""` | no | +| [poweruser\_role\_policy\_arns](#input\_poweruser\_role\_policy\_arns) | List of policy ARNs to use for poweruser role | `list(string)` |
[
"arn:aws:iam::aws:policy/PowerUserAccess"
]
| no | +| [poweruser\_role\_tags](#input\_poweruser\_role\_tags) | A map of tags to add to poweruser role resource. | `map(string)` | `{}` | no | +| [provider\_id](#input\_provider\_id) | ID of the SAML Provider. Use provider\_ids to specify several IDs. | `string` | `""` | no | +| [provider\_ids](#input\_provider\_ids) | List of SAML Provider IDs | `list(string)` | `[]` | no | +| [readonly\_role\_name](#input\_readonly\_role\_name) | IAM role with readonly access | `string` | `"readonly"` | no | +| [readonly\_role\_path](#input\_readonly\_role\_path) | Path of readonly IAM role | `string` | `"/"` | no | +| [readonly\_role\_permissions\_boundary\_arn](#input\_readonly\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for readonly role | `string` | `""` | no | +| [readonly\_role\_policy\_arns](#input\_readonly\_role\_policy\_arns) | List of policy ARNs to use for readonly role | `list(string)` |
[
"arn:aws:iam::aws:policy/ReadOnlyAccess"
]
| no | +| [readonly\_role\_tags](#input\_readonly\_role\_tags) | A map of tags to add to readonly role resource. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [admin\_iam\_role\_arn](#output\_admin\_iam\_role\_arn) | ARN of admin IAM role | +| [admin\_iam\_role\_name](#output\_admin\_iam\_role\_name) | Name of admin IAM role | +| [admin\_iam\_role\_path](#output\_admin\_iam\_role\_path) | Path of admin IAM role | +| [admin\_iam\_role\_unique\_id](#output\_admin\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [poweruser\_iam\_role\_arn](#output\_poweruser\_iam\_role\_arn) | ARN of poweruser IAM role | +| [poweruser\_iam\_role\_name](#output\_poweruser\_iam\_role\_name) | Name of poweruser IAM role | +| [poweruser\_iam\_role\_path](#output\_poweruser\_iam\_role\_path) | Path of poweruser IAM role | +| [poweruser\_iam\_role\_unique\_id](#output\_poweruser\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [readonly\_iam\_role\_arn](#output\_readonly\_iam\_role\_arn) | ARN of readonly IAM role | +| [readonly\_iam\_role\_name](#output\_readonly\_iam\_role\_name) | Name of readonly IAM role | +| [readonly\_iam\_role\_path](#output\_readonly\_iam\_role\_path) | Path of readonly IAM role | +| [readonly\_iam\_role\_unique\_id](#output\_readonly\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/modules/iam-assumable-roles-with-saml/main.tf b/modules/aws-iam/modules/iam-assumable-roles-with-saml/main.tf new file mode 100644 index 0000000..fdfb687 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles-with-saml/main.tf @@ -0,0 +1,92 @@ +locals { + identifiers = compact(distinct(concat(var.provider_ids, [var.provider_id]))) +} + +data "aws_iam_policy_document" "assume_role_with_saml" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRoleWithSAML"] + + principals { + type = "Federated" + + identifiers = local.identifiers + } + + condition { + test = "StringEquals" + variable = "SAML:aud" + values = [var.aws_saml_endpoint] + } + } +} + +# Admin +resource "aws_iam_role" "admin" { + count = var.create_admin_role ? 1 : 0 + + name = var.admin_role_name + path = var.admin_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.admin_role_permissions_boundary_arn + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_saml.json + + tags = var.admin_role_tags +} + +resource "aws_iam_role_policy_attachment" "admin" { + count = var.create_admin_role ? length(var.admin_role_policy_arns) : 0 + + role = aws_iam_role.admin[0].name + policy_arn = element(var.admin_role_policy_arns, count.index) +} + +# Poweruser +resource "aws_iam_role" "poweruser" { + count = var.create_poweruser_role ? 1 : 0 + + name = var.poweruser_role_name + path = var.poweruser_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.poweruser_role_permissions_boundary_arn + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_saml.json + + tags = var.poweruser_role_tags +} + +resource "aws_iam_role_policy_attachment" "poweruser" { + count = var.create_poweruser_role ? length(var.poweruser_role_policy_arns) : 0 + + role = aws_iam_role.poweruser[0].name + policy_arn = element(var.poweruser_role_policy_arns, count.index) +} + +# Readonly +resource "aws_iam_role" "readonly" { + count = var.create_readonly_role ? 1 : 0 + + name = var.readonly_role_name + path = var.readonly_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.readonly_role_permissions_boundary_arn + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_saml.json + + tags = var.readonly_role_tags +} + +resource "aws_iam_role_policy_attachment" "readonly" { + count = var.create_readonly_role ? length(var.readonly_role_policy_arns) : 0 + + role = aws_iam_role.readonly[0].name + policy_arn = element(var.readonly_role_policy_arns, count.index) +} diff --git a/modules/aws-iam/modules/iam-assumable-roles-with-saml/outputs.tf b/modules/aws-iam/modules/iam-assumable-roles-with-saml/outputs.tf new file mode 100644 index 0000000..3b1c4d1 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles-with-saml/outputs.tf @@ -0,0 +1,61 @@ +#Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = try(aws_iam_role.admin[0].arn, "") +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = try(aws_iam_role.admin[0].name, "") +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = try(aws_iam_role.admin[0].path, "") +} + +output "admin_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.admin[0].unique_id, "") +} + +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].arn, "") +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].name, "") +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].path, "") +} + +output "poweruser_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.poweruser[0].unique_id, "") +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = try(aws_iam_role.readonly[0].arn, "") +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = try(aws_iam_role.readonly[0].name, "") +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = try(aws_iam_role.readonly[0].path, "") +} + +output "readonly_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.readonly[0].unique_id, "") +} diff --git a/modules/aws-iam/modules/iam-assumable-roles-with-saml/variables.tf b/modules/aws-iam/modules/iam-assumable-roles-with-saml/variables.tf new file mode 100644 index 0000000..4f2af0f --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles-with-saml/variables.tf @@ -0,0 +1,140 @@ +variable "provider_id" { + description = "ID of the SAML Provider. Use provider_ids to specify several IDs." + type = string + default = "" +} + +variable "provider_ids" { + description = "List of SAML Provider IDs" + type = list(string) + default = [] +} + +variable "aws_saml_endpoint" { + description = "AWS SAML Endpoint" + default = "https://signin.aws.amazon.com/saml" + type = string +} + +# Admin +variable "create_admin_role" { + description = "Whether to create admin role" + type = bool + default = false +} + +variable "admin_role_name" { + description = "IAM role with admin access" + type = string + default = "admin" +} + +variable "admin_role_path" { + description = "Path of admin IAM role" + type = string + default = "/" +} + +variable "admin_role_policy_arns" { + description = "List of policy ARNs to use for admin role" + type = list(string) + default = ["arn:aws:iam::aws:policy/AdministratorAccess"] +} + +variable "admin_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for admin role" + type = string + default = "" +} + +variable "admin_role_tags" { + description = "A map of tags to add to admin role resource." + type = map(string) + default = {} +} + +# Poweruser +variable "create_poweruser_role" { + description = "Whether to create poweruser role" + type = bool + default = false +} + +variable "poweruser_role_name" { + description = "IAM role with poweruser access" + type = string + default = "poweruser" +} + +variable "poweruser_role_path" { + description = "Path of poweruser IAM role" + type = string + default = "/" +} + +variable "poweruser_role_policy_arns" { + description = "List of policy ARNs to use for poweruser role" + type = list(string) + default = ["arn:aws:iam::aws:policy/PowerUserAccess"] +} + +variable "poweruser_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for poweruser role" + type = string + default = "" +} + +variable "poweruser_role_tags" { + description = "A map of tags to add to poweruser role resource." + type = map(string) + default = {} +} + +# Readonly +variable "create_readonly_role" { + description = "Whether to create readonly role" + type = bool + default = false +} + +variable "readonly_role_name" { + description = "IAM role with readonly access" + type = string + default = "readonly" +} + +variable "readonly_role_path" { + description = "Path of readonly IAM role" + type = string + default = "/" +} + +variable "readonly_role_policy_arns" { + description = "List of policy ARNs to use for readonly role" + type = list(string) + default = ["arn:aws:iam::aws:policy/ReadOnlyAccess"] +} + +variable "readonly_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for readonly role" + type = string + default = "" +} + +variable "readonly_role_tags" { + description = "A map of tags to add to readonly role resource." + type = map(string) + default = {} +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 3600 +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} diff --git a/modules/aws-iam/modules/iam-assumable-roles-with-saml/versions.tf b/modules/aws-iam/modules/iam-assumable-roles-with-saml/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles-with-saml/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-assumable-roles/README.md b/modules/aws-iam/modules/iam-assumable-roles/README.md new file mode 100644 index 0000000..5facdd0 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles/README.md @@ -0,0 +1,87 @@ +# iam-assumable-roles + +Creates predefined IAM roles (admin, poweruser and readonly) which can be assumed by trusted resources. + +Trusted resources can be any [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns) - typically, AWS accounts and users. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_with_mfa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_role\_name](#input\_admin\_role\_name) | IAM role with admin access | `string` | `"admin"` | no | +| [admin\_role\_path](#input\_admin\_role\_path) | Path of admin IAM role | `string` | `"/"` | no | +| [admin\_role\_permissions\_boundary\_arn](#input\_admin\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for admin role | `string` | `""` | no | +| [admin\_role\_policy\_arns](#input\_admin\_role\_policy\_arns) | List of policy ARNs to use for admin role | `list(string)` |
[
"arn:aws:iam::aws:policy/AdministratorAccess"
]
| no | +| [admin\_role\_requires\_mfa](#input\_admin\_role\_requires\_mfa) | Whether admin role requires MFA | `bool` | `true` | no | +| [admin\_role\_tags](#input\_admin\_role\_tags) | A map of tags to add to admin role resource. | `map(string)` | `{}` | no | +| [create\_admin\_role](#input\_create\_admin\_role) | Whether to create admin role | `bool` | `false` | no | +| [create\_poweruser\_role](#input\_create\_poweruser\_role) | Whether to create poweruser role | `bool` | `false` | no | +| [create\_readonly\_role](#input\_create\_readonly\_role) | Whether to create readonly role | `bool` | `false` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | +| [mfa\_age](#input\_mfa\_age) | Max age of valid MFA (in seconds) for roles which require MFA | `number` | `86400` | no | +| [poweruser\_role\_name](#input\_poweruser\_role\_name) | IAM role with poweruser access | `string` | `"poweruser"` | no | +| [poweruser\_role\_path](#input\_poweruser\_role\_path) | Path of poweruser IAM role | `string` | `"/"` | no | +| [poweruser\_role\_permissions\_boundary\_arn](#input\_poweruser\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for poweruser role | `string` | `""` | no | +| [poweruser\_role\_policy\_arns](#input\_poweruser\_role\_policy\_arns) | List of policy ARNs to use for poweruser role | `list(string)` |
[
"arn:aws:iam::aws:policy/PowerUserAccess"
]
| no | +| [poweruser\_role\_requires\_mfa](#input\_poweruser\_role\_requires\_mfa) | Whether poweruser role requires MFA | `bool` | `true` | no | +| [poweruser\_role\_tags](#input\_poweruser\_role\_tags) | A map of tags to add to poweruser role resource. | `map(string)` | `{}` | no | +| [readonly\_role\_name](#input\_readonly\_role\_name) | IAM role with readonly access | `string` | `"readonly"` | no | +| [readonly\_role\_path](#input\_readonly\_role\_path) | Path of readonly IAM role | `string` | `"/"` | no | +| [readonly\_role\_permissions\_boundary\_arn](#input\_readonly\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for readonly role | `string` | `""` | no | +| [readonly\_role\_policy\_arns](#input\_readonly\_role\_policy\_arns) | List of policy ARNs to use for readonly role | `list(string)` |
[
"arn:aws:iam::aws:policy/ReadOnlyAccess"
]
| no | +| [readonly\_role\_requires\_mfa](#input\_readonly\_role\_requires\_mfa) | Whether readonly role requires MFA | `bool` | `true` | no | +| [readonly\_role\_tags](#input\_readonly\_role\_tags) | A map of tags to add to readonly role resource. | `map(string)` | `{}` | no | +| [trusted\_role\_arns](#input\_trusted\_role\_arns) | ARNs of AWS entities who can assume these roles | `list(string)` | `[]` | no | +| [trusted\_role\_services](#input\_trusted\_role\_services) | AWS Services that can assume these roles | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [admin\_iam\_role\_arn](#output\_admin\_iam\_role\_arn) | ARN of admin IAM role | +| [admin\_iam\_role\_name](#output\_admin\_iam\_role\_name) | Name of admin IAM role | +| [admin\_iam\_role\_path](#output\_admin\_iam\_role\_path) | Path of admin IAM role | +| [admin\_iam\_role\_requires\_mfa](#output\_admin\_iam\_role\_requires\_mfa) | Whether admin IAM role requires MFA | +| [admin\_iam\_role\_unique\_id](#output\_admin\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [poweruser\_iam\_role\_arn](#output\_poweruser\_iam\_role\_arn) | ARN of poweruser IAM role | +| [poweruser\_iam\_role\_name](#output\_poweruser\_iam\_role\_name) | Name of poweruser IAM role | +| [poweruser\_iam\_role\_path](#output\_poweruser\_iam\_role\_path) | Path of poweruser IAM role | +| [poweruser\_iam\_role\_requires\_mfa](#output\_poweruser\_iam\_role\_requires\_mfa) | Whether poweruser IAM role requires MFA | +| [poweruser\_iam\_role\_unique\_id](#output\_poweruser\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [readonly\_iam\_role\_arn](#output\_readonly\_iam\_role\_arn) | ARN of readonly IAM role | +| [readonly\_iam\_role\_name](#output\_readonly\_iam\_role\_name) | Name of readonly IAM role | +| [readonly\_iam\_role\_path](#output\_readonly\_iam\_role\_path) | Path of readonly IAM role | +| [readonly\_iam\_role\_requires\_mfa](#output\_readonly\_iam\_role\_requires\_mfa) | Whether readonly IAM role requires MFA | +| [readonly\_iam\_role\_unique\_id](#output\_readonly\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/modules/iam-assumable-roles/main.tf b/modules/aws-iam/modules/iam-assumable-roles/main.tf new file mode 100644 index 0000000..c0d935d --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles/main.tf @@ -0,0 +1,116 @@ +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = var.trusted_role_arns + } + + principals { + type = "Service" + identifiers = var.trusted_role_services + } + } +} + +data "aws_iam_policy_document" "assume_role_with_mfa" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = var.trusted_role_arns + } + + principals { + type = "Service" + identifiers = var.trusted_role_services + } + + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + + condition { + test = "NumericLessThan" + variable = "aws:MultiFactorAuthAge" + values = [var.mfa_age] + } + } +} + +# Admin +resource "aws_iam_role" "admin" { + count = var.create_admin_role ? 1 : 0 + + name = var.admin_role_name + path = var.admin_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.admin_role_permissions_boundary_arn + + assume_role_policy = var.admin_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json + + tags = var.admin_role_tags +} + +resource "aws_iam_role_policy_attachment" "admin" { + count = var.create_admin_role ? length(var.admin_role_policy_arns) : 0 + + role = aws_iam_role.admin[0].name + policy_arn = element(var.admin_role_policy_arns, count.index) +} + +# Poweruser +resource "aws_iam_role" "poweruser" { + count = var.create_poweruser_role ? 1 : 0 + + name = var.poweruser_role_name + path = var.poweruser_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.poweruser_role_permissions_boundary_arn + + assume_role_policy = var.poweruser_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json + + tags = var.poweruser_role_tags +} + +resource "aws_iam_role_policy_attachment" "poweruser" { + count = var.create_poweruser_role ? length(var.poweruser_role_policy_arns) : 0 + + role = aws_iam_role.poweruser[0].name + policy_arn = element(var.poweruser_role_policy_arns, count.index) +} + +# Readonly +resource "aws_iam_role" "readonly" { + count = var.create_readonly_role ? 1 : 0 + + name = var.readonly_role_name + path = var.readonly_role_path + max_session_duration = var.max_session_duration + + force_detach_policies = var.force_detach_policies + permissions_boundary = var.readonly_role_permissions_boundary_arn + + assume_role_policy = var.readonly_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json + + tags = var.readonly_role_tags +} + +resource "aws_iam_role_policy_attachment" "readonly" { + count = var.create_readonly_role ? length(var.readonly_role_policy_arns) : 0 + + role = aws_iam_role.readonly[0].name + policy_arn = element(var.readonly_role_policy_arns, count.index) +} diff --git a/modules/aws-iam/modules/iam-assumable-roles/outputs.tf b/modules/aws-iam/modules/iam-assumable-roles/outputs.tf new file mode 100644 index 0000000..5918ba4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles/outputs.tf @@ -0,0 +1,77 @@ +#Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = try(aws_iam_role.admin[0].arn, "") +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = try(aws_iam_role.admin[0].name, "") +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = try(aws_iam_role.admin[0].path, "") +} + +output "admin_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.admin[0].unique_id, "") +} + +output "admin_iam_role_requires_mfa" { + description = "Whether admin IAM role requires MFA" + value = var.admin_role_requires_mfa +} + +# Poweruser +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].arn, "") +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].name, "") +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = try(aws_iam_role.poweruser[0].path, "") +} + +output "poweruser_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.poweruser[0].unique_id, "") +} + +output "poweruser_iam_role_requires_mfa" { + description = "Whether poweruser IAM role requires MFA" + value = var.poweruser_role_requires_mfa +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = try(aws_iam_role.readonly[0].arn, "") +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = try(aws_iam_role.readonly[0].name, "") +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = try(aws_iam_role.readonly[0].path, "") +} + +output "readonly_iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.readonly[0].unique_id, "") +} + +output "readonly_iam_role_requires_mfa" { + description = "Whether readonly IAM role requires MFA" + value = var.readonly_role_requires_mfa +} diff --git a/modules/aws-iam/modules/iam-assumable-roles/variables.tf b/modules/aws-iam/modules/iam-assumable-roles/variables.tf new file mode 100644 index 0000000..f824502 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles/variables.tf @@ -0,0 +1,158 @@ +variable "trusted_role_arns" { + description = "ARNs of AWS entities who can assume these roles" + type = list(string) + default = [] +} + +variable "trusted_role_services" { + description = "AWS Services that can assume these roles" + type = list(string) + default = [] +} + +variable "mfa_age" { + description = "Max age of valid MFA (in seconds) for roles which require MFA" + type = number + default = 86400 +} + +# Admin +variable "create_admin_role" { + description = "Whether to create admin role" + type = bool + default = false +} + +variable "admin_role_name" { + description = "IAM role with admin access" + type = string + default = "admin" +} + +variable "admin_role_path" { + description = "Path of admin IAM role" + type = string + default = "/" +} + +variable "admin_role_requires_mfa" { + description = "Whether admin role requires MFA" + type = bool + default = true +} + +variable "admin_role_policy_arns" { + description = "List of policy ARNs to use for admin role" + type = list(string) + default = ["arn:aws:iam::aws:policy/AdministratorAccess"] +} + +variable "admin_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for admin role" + type = string + default = "" +} + +variable "admin_role_tags" { + description = "A map of tags to add to admin role resource." + type = map(string) + default = {} +} + +# Poweruser +variable "create_poweruser_role" { + description = "Whether to create poweruser role" + type = bool + default = false +} + +variable "poweruser_role_name" { + description = "IAM role with poweruser access" + type = string + default = "poweruser" +} + +variable "poweruser_role_path" { + description = "Path of poweruser IAM role" + type = string + default = "/" +} + +variable "poweruser_role_requires_mfa" { + description = "Whether poweruser role requires MFA" + type = bool + default = true +} + +variable "poweruser_role_policy_arns" { + description = "List of policy ARNs to use for poweruser role" + type = list(string) + default = ["arn:aws:iam::aws:policy/PowerUserAccess"] +} + +variable "poweruser_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for poweruser role" + type = string + default = "" +} + +variable "poweruser_role_tags" { + description = "A map of tags to add to poweruser role resource." + type = map(string) + default = {} +} + +# Readonly +variable "create_readonly_role" { + description = "Whether to create readonly role" + type = bool + default = false +} + +variable "readonly_role_name" { + description = "IAM role with readonly access" + type = string + default = "readonly" +} + +variable "readonly_role_path" { + description = "Path of readonly IAM role" + type = string + default = "/" +} + +variable "readonly_role_requires_mfa" { + description = "Whether readonly role requires MFA" + type = bool + default = true +} + +variable "readonly_role_policy_arns" { + description = "List of policy ARNs to use for readonly role" + type = list(string) + default = ["arn:aws:iam::aws:policy/ReadOnlyAccess"] +} + +variable "readonly_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for readonly role" + type = string + default = "" +} + +variable "readonly_role_tags" { + description = "A map of tags to add to readonly role resource." + type = map(string) + default = {} +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 3600 +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} diff --git a/modules/aws-iam/modules/iam-assumable-roles/versions.tf b/modules/aws-iam/modules/iam-assumable-roles/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-assumable-roles/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-eks-role/README.md b/modules/aws-iam/modules/iam-eks-role/README.md new file mode 100644 index 0000000..1e0a22f --- /dev/null +++ b/modules/aws-iam/modules/iam-eks-role/README.md @@ -0,0 +1,129 @@ +# iam-eks-role + +Creates an IAM role that can be assumed by one or more EKS `ServiceAccount` in one or more EKS clusters. Unlike [iam-assumable-role-with-oidc](https://github.com/kloia/platform-modules/tree/main/terraform-aws-iam/modules/iam-assumable-role-with-oidc/), this module: + +- Does not require any knowledge of cluster OIDC information as `data` resources are used +- Supports assuming the role from multiple EKS clusters, for example used in DR or when a workload is spread across clusters +- Support multiple `ServiceAccount` in the same cluster, for example when a workload runs in multiple namespaces +- More suitable for non-cluster admins as implementation is simpler +- More suitable for when the IAM role and cluster resources are in separate Terraform configurations + +This module is for use with AWS EKS. For details of how a `ServiceAccount` in EKS can assume an IAM role, see the [EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + +Implementation notes: + +- The EKS cluster needs to exist first, in the current AWS account and region +- The key in the `cluster_service_accounts` is the exact name of the EKS cluster + +## Basic example + +To create an IAM role named `my-app` that can be assumed in EKS cluster `cluster1` by a `ServiceAccount` called `my-serviceaccount` in the `default` namespace: + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + cluster_service_accounts = { + "cluster1" = ["default:my-serviceaccount"] + } +} +``` + +## Multi cluster example: + +To create an IAM role named `my-app` that can be assumed from: + +- EKS cluster `staging-main-1`, namespace `default`, `ServiceAccount` called `my-app-staging` +- EKS cluster `staging-backup-1`, namespace `default`, `ServiceAccount` called `my-app-staging` + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + cluster_service_accounts = { + "staging-main-1" = ["default:my-app-staging"] + "staging-backup-1" = ["default:my-app-staging"] + } +} +``` + +## Multi `ServiceAccount` example + +To create an IAM role named `cloudwatch-exporter` that can be assumed in EKS cluster `production-main-1` from: + +- namespace `kube-system`, `ServiceAccount` called `cloudwatch-exporter` +- namespace `app1`, `ServiceAccount` called `cloudwatch-exporter` + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + cluster_service_accounts = { + "production-main-1" = [ + "kube-system:cloudwatch-exporter", + "app1:cloudwatch-exporter", + ] + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_eks_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster) | data source | +| [aws_iam_policy_document.assume_role_with_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_service\_accounts](#input\_cluster\_service\_accounts) | EKS cluster and k8s ServiceAccount pairs. Each EKS cluster can have multiple k8s ServiceAccount. See README for details | `map(list(string))` | `{}` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `43200` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no | +| [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no | +| [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | +| [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `map(string)` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/aws-iam/modules/iam-eks-role/main.tf b/modules/aws-iam/modules/iam-eks-role/main.tf new file mode 100644 index 0000000..31dac45 --- /dev/null +++ b/modules/aws-iam/modules/iam-eks-role/main.tf @@ -0,0 +1,56 @@ +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +data "aws_eks_cluster" "main" { + for_each = var.cluster_service_accounts + + name = each.key +} + +data "aws_iam_policy_document" "assume_role_with_oidc" { + dynamic "statement" { + for_each = var.cluster_service_accounts + + content { + effect = "Allow" + + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + + identifiers = [ + "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.main[statement.key].identity[0].oidc[0].issuer, "https://", "")}" + ] + } + + condition { + test = "StringEquals" + variable = "${replace(data.aws_eks_cluster.main[statement.key].identity[0].oidc[0].issuer, "https://", "")}:sub" + values = [for s in statement.value : "system:serviceaccount:${s}"] + } + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc.json + description = var.role_description + force_detach_policies = var.force_detach_policies + max_session_duration = var.max_session_duration + name = var.role_name + name_prefix = var.role_name_prefix + path = var.role_path + permissions_boundary = var.role_permissions_boundary_arn + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in var.role_policy_arns : k => v if var.create_role } + + role = aws_iam_role.this[0].name + policy_arn = each.value +} diff --git a/modules/aws-iam/modules/iam-eks-role/outputs.tf b/modules/aws-iam/modules/iam-eks-role/outputs.tf new file mode 100644 index 0000000..a680531 --- /dev/null +++ b/modules/aws-iam/modules/iam-eks-role/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/aws-iam/modules/iam-eks-role/variables.tf b/modules/aws-iam/modules/iam-eks-role/variables.tf new file mode 100644 index 0000000..5f69d75 --- /dev/null +++ b/modules/aws-iam/modules/iam-eks-role/variables.tf @@ -0,0 +1,65 @@ +variable "create_role" { + description = "Whether to create a role" + type = bool + default = true +} + +variable "role_name" { + description = "Name of IAM role" + type = string + default = null +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = "/" +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = "" +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = "" +} + +variable "role_name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "role_policy_arns" { + description = "ARNs of any policies to attach to the IAM role" + type = map(string) + default = {} +} + +variable "cluster_service_accounts" { + description = "EKS cluster and k8s ServiceAccount pairs. Each EKS cluster can have multiple k8s ServiceAccount. See README for details" + type = map(list(string)) + default = {} +} + +variable "tags" { + description = "A map of tags to add the the IAM role" + type = map(any) + default = {} +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = false +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = 43200 +} diff --git a/modules/aws-iam/modules/iam-eks-role/versions.tf b/modules/aws-iam/modules/iam-eks-role/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-eks-role/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/README.md b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/README.md new file mode 100644 index 0000000..fecc7a5 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/README.md @@ -0,0 +1,51 @@ +# iam-group-with-assumable-roles-policy + +Creates IAM group with users who are allowed to assume IAM roles. This is typically done in resource AWS account where IAM users can jump into from IAM AWS account. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource | +| [aws_iam_group_membership.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_membership) | resource | +| [aws_iam_group_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [assumable\_roles](#input\_assumable\_roles) | List of IAM roles ARNs which can be assumed by the group | `list(string)` | `[]` | no | +| [group\_users](#input\_group\_users) | List of IAM users to have in an IAM group which can assume the role | `list(string)` | `[]` | no | +| [name](#input\_name) | Name of IAM policy and IAM group | `string` | n/a | yes | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [assumable\_roles](#output\_assumable\_roles) | List of ARNs of IAM roles which members of IAM group can assume | +| [group\_arn](#output\_group\_arn) | IAM group arn | +| [group\_name](#output\_group\_name) | IAM group name | +| [group\_users](#output\_group\_users) | List of IAM users in IAM group | +| [policy\_arn](#output\_policy\_arn) | Assume role policy ARN of IAM group | + diff --git a/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/main.tf b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/main.tf new file mode 100644 index 0000000..f17e738 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/main.tf @@ -0,0 +1,32 @@ +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + resources = var.assumable_roles + } +} + +resource "aws_iam_policy" "this" { + name = var.name + description = "Allows to assume role in another AWS account" + policy = data.aws_iam_policy_document.assume_role.json + + tags = var.tags +} + +resource "aws_iam_group" "this" { + name = var.name +} + +resource "aws_iam_group_policy_attachment" "this" { + group = aws_iam_group.this.id + policy_arn = aws_iam_policy.this.id +} + +resource "aws_iam_group_membership" "this" { + count = length(var.group_users) > 0 ? 1 : 0 + + group = aws_iam_group.this.id + name = var.name + users = var.group_users +} diff --git a/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/outputs.tf b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/outputs.tf new file mode 100644 index 0000000..a172a66 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/outputs.tf @@ -0,0 +1,24 @@ +output "group_users" { + description = "List of IAM users in IAM group" + value = flatten(aws_iam_group_membership.this[*].users) +} + +output "assumable_roles" { + description = "List of ARNs of IAM roles which members of IAM group can assume" + value = var.assumable_roles +} + +output "policy_arn" { + description = "Assume role policy ARN of IAM group" + value = aws_iam_policy.this.arn +} + +output "group_name" { + description = "IAM group name" + value = aws_iam_group.this.name +} + +output "group_arn" { + description = "IAM group arn" + value = aws_iam_group.this.arn +} diff --git a/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/variables.tf b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/variables.tf new file mode 100644 index 0000000..3cc3a6d --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/variables.tf @@ -0,0 +1,22 @@ +variable "name" { + description = "Name of IAM policy and IAM group" + type = string +} + +variable "assumable_roles" { + description = "List of IAM roles ARNs which can be assumed by the group" + type = list(string) + default = [] +} + +variable "group_users" { + description = "List of IAM users to have in an IAM group which can assume the role" + type = list(string) + default = [] +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} diff --git a/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/versions.tf b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-assumable-roles-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-group-with-policies/README.md b/modules/aws-iam/modules/iam-group-with-policies/README.md new file mode 100644 index 0000000..683f11d --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/README.md @@ -0,0 +1,60 @@ +# iam-group-with-policies + +Creates IAM group with specified IAM policies, and add users into a group. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource | +| [aws_iam_group_membership.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_membership) | resource | +| [aws_iam_group_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_group_policy_attachment.custom_arns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_group_policy_attachment.iam_self_management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_policy.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.iam_self_management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.iam_self_management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [attach\_iam\_self\_management\_policy](#input\_attach\_iam\_self\_management\_policy) | Whether to attach IAM policy which allows IAM users to manage their credentials and MFA | `bool` | `true` | no | +| [aws\_account\_id](#input\_aws\_account\_id) | AWS account id to use inside IAM policies. If empty, current AWS account ID will be used. | `string` | `""` | no | +| [create\_group](#input\_create\_group) | Whether to create IAM group | `bool` | `true` | no | +| [custom\_group\_policies](#input\_custom\_group\_policies) | List of maps of inline IAM policies to attach to IAM group. Should have `name` and `policy` keys in each element. | `list(map(string))` | `[]` | no | +| [custom\_group\_policy\_arns](#input\_custom\_group\_policy\_arns) | List of IAM policies ARNs to attach to IAM group | `list(string)` | `[]` | no | +| [group\_users](#input\_group\_users) | List of IAM users to have in an IAM group which can assume the role | `list(string)` | `[]` | no | +| [iam\_self\_management\_policy\_name\_prefix](#input\_iam\_self\_management\_policy\_name\_prefix) | Name prefix for IAM policy to create with IAM self-management permissions | `string` | `"IAMSelfManagement-"` | no | +| [name](#input\_name) | Name of IAM group | `string` | `""` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_account\_id](#output\_aws\_account\_id) | IAM AWS account id | +| [group\_arn](#output\_group\_arn) | IAM group arn | +| [group\_name](#output\_group\_name) | IAM group name | +| [group\_users](#output\_group\_users) | List of IAM users in IAM group | + diff --git a/modules/aws-iam/modules/iam-group-with-policies/main.tf b/modules/aws-iam/modules/iam-group-with-policies/main.tf new file mode 100644 index 0000000..00571a9 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/main.tf @@ -0,0 +1,63 @@ +locals { + group_name = element(concat(aws_iam_group.this.*.id, [var.name]), 0) +} + +resource "aws_iam_group" "this" { + count = var.create_group ? 1 : 0 + + name = var.name +} + +resource "aws_iam_group_membership" "this" { + count = length(var.group_users) > 0 ? 1 : 0 + + group = local.group_name + name = var.name + users = var.group_users +} + +################################ +# IAM group policy attachements +################################ +resource "aws_iam_group_policy_attachment" "iam_self_management" { + count = var.attach_iam_self_management_policy ? 1 : 0 + + group = local.group_name + policy_arn = aws_iam_policy.iam_self_management[0].arn +} + +resource "aws_iam_group_policy_attachment" "custom_arns" { + count = length(var.custom_group_policy_arns) + + group = local.group_name + policy_arn = element(var.custom_group_policy_arns, count.index) +} + +resource "aws_iam_group_policy_attachment" "custom" { + count = length(var.custom_group_policies) + + group = local.group_name + policy_arn = element(aws_iam_policy.custom.*.arn, count.index) +} + +############### +# IAM policies +############### +resource "aws_iam_policy" "iam_self_management" { + count = var.attach_iam_self_management_policy ? 1 : 0 + + name_prefix = var.iam_self_management_policy_name_prefix + policy = data.aws_iam_policy_document.iam_self_management.json + + tags = var.tags +} + +resource "aws_iam_policy" "custom" { + count = length(var.custom_group_policies) + + name = var.custom_group_policies[count.index]["name"] + policy = var.custom_group_policies[count.index]["policy"] + description = lookup(var.custom_group_policies[count.index], "description", null) + + tags = var.tags +} diff --git a/modules/aws-iam/modules/iam-group-with-policies/outputs.tf b/modules/aws-iam/modules/iam-group-with-policies/outputs.tf new file mode 100644 index 0000000..ee0835c --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/outputs.tf @@ -0,0 +1,19 @@ +output "aws_account_id" { + description = "IAM AWS account id" + value = local.aws_account_id +} + +output "group_arn" { + description = "IAM group arn" + value = try(aws_iam_group.this[0].arn, "") +} + +output "group_users" { + description = "List of IAM users in IAM group" + value = flatten(aws_iam_group_membership.this[*].users) +} + +output "group_name" { + description = "IAM group name" + value = try(aws_iam_group.this[0].name, var.name) +} diff --git a/modules/aws-iam/modules/iam-group-with-policies/policies.tf b/modules/aws-iam/modules/iam-group-with-policies/policies.tf new file mode 100644 index 0000000..537b19c --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/policies.tf @@ -0,0 +1,93 @@ +data "aws_caller_identity" "current" { + count = var.aws_account_id == "" ? 1 : 0 +} + +data "aws_partition" "current" {} + +locals { + aws_account_id = element( + concat( + data.aws_caller_identity.current.*.account_id, + [var.aws_account_id], + ), + 0, + ) +} + +data "aws_iam_policy_document" "iam_self_management" { + statement { + sid = "AllowSelfManagement" + + effect = "Allow" + + actions = [ + "iam:ChangePassword", + "iam:CreateAccessKey", + "iam:CreateLoginProfile", + "iam:CreateVirtualMFADevice", + "iam:DeleteAccessKey", + "iam:DeleteLoginProfile", + "iam:DeleteVirtualMFADevice", + "iam:EnableMFADevice", + "iam:GenerateCredentialReport", + "iam:GenerateServiceLastAccessedDetails", + "iam:Get*", + "iam:List*", + "iam:ResyncMFADevice", + "iam:UpdateAccessKey", + "iam:UpdateLoginProfile", + "iam:UpdateUser", + "iam:UploadSigningCertificate", + "iam:UploadSSHPublicKey", + ] + + # Allow for both users with "path" and without it + resources = [ + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:user/*/$${aws:username}", + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:user/$${aws:username}", + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:mfa/$${aws:username}", + ] + } + + statement { + sid = "AllowIAMReadOnly" + + actions = [ + "iam:Get*", + "iam:List*", + ] + + resources = ["*"] + effect = "Allow" + } + + # Allow to deactivate MFA only when logging in with MFA + statement { + sid = "AllowDeactivateMFADevice" + + effect = "Allow" + + actions = [ + "iam:DeactivateMFADevice", + ] + + # Allow for both users with "path" and without it + resources = [ + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:user/*/$${aws:username}", + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:user/$${aws:username}", + "arn:${data.aws_partition.current.partition}:iam::${local.aws_account_id}:mfa/$${aws:username}", + ] + + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + + condition { + test = "NumericLessThan" + variable = "aws:MultiFactorAuthAge" + values = ["3600"] + } + } +} diff --git a/modules/aws-iam/modules/iam-group-with-policies/variables.tf b/modules/aws-iam/modules/iam-group-with-policies/variables.tf new file mode 100644 index 0000000..7c5d764 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/variables.tf @@ -0,0 +1,53 @@ +variable "create_group" { + description = "Whether to create IAM group" + type = bool + default = true +} + +variable "name" { + description = "Name of IAM group" + type = string + default = "" +} + +variable "group_users" { + description = "List of IAM users to have in an IAM group which can assume the role" + type = list(string) + default = [] +} + +variable "custom_group_policy_arns" { + description = "List of IAM policies ARNs to attach to IAM group" + type = list(string) + default = [] +} + +variable "custom_group_policies" { + description = "List of maps of inline IAM policies to attach to IAM group. Should have `name` and `policy` keys in each element." + type = list(map(string)) + default = [] +} + +variable "attach_iam_self_management_policy" { + description = "Whether to attach IAM policy which allows IAM users to manage their credentials and MFA" + type = bool + default = true +} + +variable "iam_self_management_policy_name_prefix" { + description = "Name prefix for IAM policy to create with IAM self-management permissions" + type = string + default = "IAMSelfManagement-" +} + +variable "aws_account_id" { + description = "AWS account id to use inside IAM policies. If empty, current AWS account ID will be used." + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} diff --git a/modules/aws-iam/modules/iam-group-with-policies/versions.tf b/modules/aws-iam/modules/iam-group-with-policies/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-group-with-policies/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-policy/README.md b/modules/aws-iam/modules/iam-policy/README.md new file mode 100644 index 0000000..482a49b --- /dev/null +++ b/modules/aws-iam/modules/iam-policy/README.md @@ -0,0 +1,50 @@ +# iam-policy + +Creates IAM policy. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create\_policy](#input\_create\_policy) | Whether to create the IAM policy | `bool` | `true` | no | +| [description](#input\_description) | The description of the policy | `string` | `"IAM Policy"` | no | +| [name](#input\_name) | The name of the policy | `string` | `""` | no | +| [path](#input\_path) | The path of the policy in IAM | `string` | `"/"` | no | +| [policy](#input\_policy) | The path of the policy in IAM (tpl file) | `string` | `""` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy's ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | + diff --git a/modules/aws-iam/modules/iam-policy/main.tf b/modules/aws-iam/modules/iam-policy/main.tf new file mode 100644 index 0000000..01ced98 --- /dev/null +++ b/modules/aws-iam/modules/iam-policy/main.tf @@ -0,0 +1,11 @@ +resource "aws_iam_policy" "policy" { + count = var.create_policy ? 1 : 0 + + name = var.name + path = var.path + description = var.description + + policy = var.policy + + tags = var.tags +} diff --git a/modules/aws-iam/modules/iam-policy/outputs.tf b/modules/aws-iam/modules/iam-policy/outputs.tf new file mode 100644 index 0000000..d890080 --- /dev/null +++ b/modules/aws-iam/modules/iam-policy/outputs.tf @@ -0,0 +1,29 @@ +output "id" { + description = "The policy's ID" + value = try(aws_iam_policy.policy[0].id, "") +} + +output "arn" { + description = "The ARN assigned by AWS to this policy" + value = try(aws_iam_policy.policy[0].arn, "") +} + +output "description" { + description = "The description of the policy" + value = try(aws_iam_policy.policy[0].description, "") +} + +output "name" { + description = "The name of the policy" + value = try(aws_iam_policy.policy[0].name, "") +} + +output "path" { + description = "The path of the policy in IAM" + value = try(aws_iam_policy.policy[0].path, "") +} + +output "policy" { + description = "The policy document" + value = try(aws_iam_policy.policy[0].policy, "") +} diff --git a/modules/aws-iam/modules/iam-policy/variables.tf b/modules/aws-iam/modules/iam-policy/variables.tf new file mode 100644 index 0000000..f01443c --- /dev/null +++ b/modules/aws-iam/modules/iam-policy/variables.tf @@ -0,0 +1,35 @@ +variable "create_policy" { + description = "Whether to create the IAM policy" + type = bool + default = true +} + +variable "name" { + description = "The name of the policy" + type = string + default = "" +} + +variable "path" { + description = "The path of the policy in IAM" + type = string + default = "/" +} + +variable "description" { + description = "The description of the policy" + type = string + default = "IAM Policy" +} + +variable "policy" { + description = "The path of the policy in IAM (tpl file)" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} diff --git a/modules/aws-iam/modules/iam-policy/versions.tf b/modules/aws-iam/modules/iam-policy/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-read-only-policy/README.md b/modules/aws-iam/modules/iam-read-only-policy/README.md new file mode 100644 index 0000000..9f97ee7 --- /dev/null +++ b/modules/aws-iam/modules/iam-read-only-policy/README.md @@ -0,0 +1,62 @@ +# iam-read-only-policy + +Creates IAM read-only policy for specified services. Default AWS read-only policies (arn:aws:iam::aws:policy/job-function/ViewOnlyAccess, arn:aws:iam::aws:policy/ReadOnlyAccess), being a one-size-fits-all type of policies, have a lot of things missing as well as something that you might not need. Also, AWS default policies are known for having [security issues](https://securityboulevard.com/2020/12/the-aws-managed-policies-trap/) +Thus this module is an attempt to build a better base for a customizable usable read-only policy. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy_document.allowed_services](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.console_services](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logs_query](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.sts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_policy\_json](#input\_additional\_policy\_json) | JSON policy document if you want to add custom actions | `string` | `"{}"` | no | +| [allow\_cloudwatch\_logs\_query](#input\_allow\_cloudwatch\_logs\_query) | Allows StartQuery/StopQuery/FilterLogEvents CloudWatch actions | `bool` | `true` | no | +| [allow\_predefined\_sts\_actions](#input\_allow\_predefined\_sts\_actions) | Allows GetCallerIdentity/GetSessionToken/GetAccessKeyInfo sts actions | `bool` | `true` | no | +| [allow\_web\_console\_services](#input\_allow\_web\_console\_services) | Allows List/Get/Describe/View actions for services used when browsing AWS console (e.g. resource-groups, tag, health services) | `bool` | `true` | no | +| [allowed\_services](#input\_allowed\_services) | List of services to allow Get/List/Describe/View options. Service name should be the same as corresponding service IAM prefix. See what it is for each service here https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html | `list(string)` | n/a | yes | +| [create\_policy](#input\_create\_policy) | Whether to create the IAM policy | `bool` | `true` | no | +| [description](#input\_description) | The description of the policy | `string` | `"IAM Policy"` | no | +| [name](#input\_name) | The name of the policy | `string` | `""` | no | +| [path](#input\_path) | The path of the policy in IAM | `string` | `"/"` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | +| [web\_console\_services](#input\_web\_console\_services) | List of web console services to allow | `list(string)` |
[
"resource-groups",
"tag",
"health",
"ce"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy's ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | +| [policy\_json](#output\_policy\_json) | Policy document as json. Useful if you need document but do not want to create IAM policy itself. For example for SSO Permission Set inline policies | + diff --git a/modules/aws-iam/modules/iam-read-only-policy/main.tf b/modules/aws-iam/modules/iam-read-only-policy/main.tf new file mode 100644 index 0000000..455c15e --- /dev/null +++ b/modules/aws-iam/modules/iam-read-only-policy/main.tf @@ -0,0 +1,90 @@ +resource "aws_iam_policy" "policy" { + count = var.create_policy ? 1 : 0 + + name = var.name + path = var.path + description = var.description + + policy = data.aws_iam_policy_document.combined.json + + tags = var.tags +} + +locals { + allowed_services = distinct(var.allowed_services) +} + +data "aws_iam_policy_document" "allowed_services" { + + dynamic "statement" { + for_each = toset(local.allowed_services) + content { + sid = replace(statement.value, "-", "") + + actions = [ + "${statement.value}:List*", + "${statement.value}:Get*", + "${statement.value}:Describe*", + "${statement.value}:View*", + ] + resources = ["*"] + } + } +} + +data "aws_iam_policy_document" "console_services" { + count = var.allow_web_console_services ? 1 : 0 + + dynamic "statement" { + for_each = toset(var.web_console_services) + content { + sid = replace(statement.value, "-", "") + + actions = [ + "${statement.value}:List*", + "${statement.value}:Get*", + "${statement.value}:Describe*", + "${statement.value}:View*", + ] + resources = ["*"] + } + } +} + +data "aws_iam_policy_document" "sts" { + count = var.allow_predefined_sts_actions ? 1 : 0 + + statement { + sid = "STS" + actions = [ + "sts:GetAccessKeyInfo", + "sts:GetCallerIdentity", + "sts:GetSessionToken", + ] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "logs_query" { + count = var.allow_cloudwatch_logs_query ? 1 : 0 + + statement { + sid = "AllowLogsQuery" + actions = [ + "logs:StartQuery", + "logs:StopQuery", + "logs:FilterLogEvents" + ] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "combined" { + source_policy_documents = concat( + [data.aws_iam_policy_document.allowed_services.json], + data.aws_iam_policy_document.console_services.*.json, + data.aws_iam_policy_document.sts.*.json, + data.aws_iam_policy_document.logs_query.*.json, + [var.additional_policy_json] + ) +} diff --git a/modules/aws-iam/modules/iam-read-only-policy/outputs.tf b/modules/aws-iam/modules/iam-read-only-policy/outputs.tf new file mode 100644 index 0000000..c18731d --- /dev/null +++ b/modules/aws-iam/modules/iam-read-only-policy/outputs.tf @@ -0,0 +1,34 @@ +output "policy_json" { + description = "Policy document as json. Useful if you need document but do not want to create IAM policy itself. For example for SSO Permission Set inline policies" + value = data.aws_iam_policy_document.combined.json +} + +output "id" { + description = "The policy's ID" + value = try(aws_iam_policy.policy[0].id, "") +} + +output "arn" { + description = "The ARN assigned by AWS to this policy" + value = try(aws_iam_policy.policy[0].arn, "") +} + +output "description" { + description = "The description of the policy" + value = try(aws_iam_policy.policy[0].description, "") +} + +output "name" { + description = "The name of the policy" + value = try(aws_iam_policy.policy[0].name, "") +} + +output "path" { + description = "The path of the policy in IAM" + value = try(aws_iam_policy.policy[0].path, "") +} + +output "policy" { + description = "The policy document" + value = try(aws_iam_policy.policy[0].policy, "") +} diff --git a/modules/aws-iam/modules/iam-read-only-policy/variables.tf b/modules/aws-iam/modules/iam-read-only-policy/variables.tf new file mode 100644 index 0000000..822b4bd --- /dev/null +++ b/modules/aws-iam/modules/iam-read-only-policy/variables.tf @@ -0,0 +1,64 @@ +variable "create_policy" { + description = "Whether to create the IAM policy" + type = bool + default = true +} + +variable "name" { + description = "The name of the policy" + type = string + default = "" +} + +variable "path" { + description = "The path of the policy in IAM" + type = string + default = "/" +} + +variable "description" { + description = "The description of the policy" + type = string + default = "IAM Policy" +} + +variable "allowed_services" { + description = "List of services to allow Get/List/Describe/View options. Service name should be the same as corresponding service IAM prefix. See what it is for each service here https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html" + type = list(string) +} + +variable "additional_policy_json" { + description = "JSON policy document if you want to add custom actions" + type = string + default = "{}" +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} + +variable "allow_cloudwatch_logs_query" { + description = "Allows StartQuery/StopQuery/FilterLogEvents CloudWatch actions" + type = bool + default = true +} + +variable "allow_predefined_sts_actions" { + description = "Allows GetCallerIdentity/GetSessionToken/GetAccessKeyInfo sts actions" + type = bool + default = true +} + +variable "allow_web_console_services" { + description = "Allows List/Get/Describe/View actions for services used when browsing AWS console (e.g. resource-groups, tag, health services)" + type = bool + default = true +} + +variable "web_console_services" { + description = "List of web console services to allow" + type = list(string) + default = ["resource-groups", "tag", "health", "ce"] +} diff --git a/modules/aws-iam/modules/iam-read-only-policy/versions.tf b/modules/aws-iam/modules/iam-read-only-policy/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-read-only-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/README.md b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/README.md new file mode 100644 index 0000000..6e9d502 --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/README.md @@ -0,0 +1,231 @@ +# IAM Role for Service Accounts in EKS + +Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s with optional policies for commonly used controllers/custom resources within EKS. The optional policies supported include: +- [Cert-Manager](https://cert-manager.io/docs/configuration/acme/dns01/route53/#set-up-an-iam-role) +- [Cluster Autoscaler](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md) +- [EBS CSI Driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/example-iam-policy.json) +- [EFS CSI Driver](https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/docs/iam-policy-example.json) +- [External DNS](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy) +- [External Secrets](https://github.com/external-secrets/kubernetes-external-secrets#add-a-secret) +- [FSx for Lustre CSI Driver](https://github.com/kubernetes-sigs/aws-fsx-csi-driver/blob/master/docs/README.md) +- [Karpenter](https://github.com/aws/karpenter/blob/main/website/content/en/preview/getting-started/cloudformation.yaml) +- [Load Balancer Controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/install/iam_policy.json) + - [Load Balancer Controller Target Group Binding Only](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/installation/#iam-permission-subset-for-those-who-use-targetgroupbinding-only-and-dont-plan-to-use-the-aws-load-balancer-controller-to-manage-security-group-rules) +- [App Mesh Controller](https://github.com/aws/aws-app-mesh-controller-for-k8s/blob/master/config/iam/controller-iam-policy.json) + - [App Mesh Envoy Proxy](https://raw.githubusercontent.com/aws/aws-app-mesh-controller-for-k8s/master/config/iam/envoy-iam-policy.json) +- [Managed Service for Prometheus](https://docs.aws.amazon.com/prometheus/latest/userguide/set-up-irsa.html) +- [Node Termination Handler](https://github.com/aws/aws-node-termination-handler#5-create-an-iam-role-for-the-pods) +- [Velero](https://github.com/vmware-tanzu/velero-plugin-for-aws#option-1-set-permissions-with-an-iam-user) +- [VPC CNI](https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html) + +This module is intended to be used with AWS EKS. For details of how a `ServiceAccount` in EKS can assume an IAM role, see the [EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + +This module supports multiple `ServiceAccount`s across multiple clusters and/or namespaces. This allows for a single IAM role to be used when an application may span multiple clusters (e.g. for DR) or multiple namespaces (e.g. for canary deployments). For example, to create an IAM role named `my-app` that can be assumed from the `ServiceAccount` named `my-app-staging` in the namespace `default` and `canary` in a cluster in `us-east-1`; and also the `ServiceAccount` name `my-app-staging` in the namespace `default` in a cluster in `ap-southeast-1`, the configuration would be: + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + role_name = "my-app" + + oidc_providers = { + one = { + provider_arn = "arn:aws:iam::111111111111:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + namespace_service_accounts = ["default:my-app-staging", "canary:my-app-staging"] + } + two = { + provider_arn = "arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" + namespace_service_accounts = ["default:my-app-staging"] + } + } +} +``` + +This module can be used in conjunction with the [`terraform-aws-eks`](https://github.com/kloia/platform-modules/tree/main/terraform-aws-eks/examples) module to easily integrate with it: + +```hcl +module "vpc_cni_irsa_role" { + source = "terraform-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + + role_name = "vpc-cni" + + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } +} + +module "karpenter_irsa_role" { + source = "terraform-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + + role_name = "karpenter_controller" + attach_karpenter_controller_policy = true + + karpenter_controller_cluster_id = module.eks.cluster_id + karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] + + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } +} + +module "eks" { + source = "terraform-modules/eks/aws" + version = "~> 18.6" + + cluster_name = "my-cluster" + cluster_version = "1.21" + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + eks_managed_node_groups = { + default = {} + } +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.amazon_managed_service_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.appmesh_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.appmesh_envoy_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.cert_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.efs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.external_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.fsx_lustre_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.load_balancer_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.load_balancer_controller_targetgroup_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.velero](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.amazon_managed_service_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.appmesh_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.appmesh_envoy_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.cert_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.efs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.external_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.fsx_lustre_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.load_balancer_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.load_balancer_controller_targetgroup_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.velero](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.amazon_managed_service_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.appmesh_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.appmesh_envoy_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cert_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.efs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.external_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.fsx_lustre_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.load_balancer_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.load_balancer_controller_targetgroup_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.velero](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [amazon\_managed\_service\_prometheus\_workspace\_arns](#input\_amazon\_managed\_service\_prometheus\_workspace\_arns) | List of AMP Workspace ARNs to read and write metrics | `list(string)` |
[
"*"
]
| no | +| [assume\_role\_condition\_test](#input\_assume\_role\_condition\_test) | Name of the [IAM condition operator](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html) to evaluate when assuming the role | `string` | `"StringEquals"` | no | +| [attach\_amazon\_managed\_service\_prometheus\_policy](#input\_attach\_amazon\_managed\_service\_prometheus\_policy) | Determines whether to attach the Amazon Managed Service for Prometheus IAM policy to the role | `bool` | `false` | no | +| [attach\_appmesh\_controller\_policy](#input\_attach\_appmesh\_controller\_policy) | Determines whether to attach the Appmesh Controller policy to the role | `bool` | `false` | no | +| [attach\_appmesh\_envoy\_proxy\_policy](#input\_attach\_appmesh\_envoy\_proxy\_policy) | Determines whether to attach the Appmesh envoy proxy policy to the role | `bool` | `false` | no | +| [attach\_cert\_manager\_policy](#input\_attach\_cert\_manager\_policy) | Determines whether to attach the Cert Manager IAM policy to the role | `bool` | `false` | no | +| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Determines whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | +| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Determines whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | +| [attach\_efs\_csi\_policy](#input\_attach\_efs\_csi\_policy) | Determines whether to attach the EFS CSI IAM policy to the role | `bool` | `false` | no | +| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Determines whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | +| [attach\_external\_secrets\_policy](#input\_attach\_external\_secrets\_policy) | Determines whether to attach the External Secrets policy to the role | `bool` | `false` | no | +| [attach\_fsx\_lustre\_csi\_policy](#input\_attach\_fsx\_lustre\_csi\_policy) | Determines whether to attach the FSx for Lustre CSI Driver IAM policy to the role | `bool` | `false` | no | +| [attach\_karpenter\_controller\_policy](#input\_attach\_karpenter\_controller\_policy) | Determines whether to attach the Karpenter Controller policy to the role | `bool` | `false` | no | +| [attach\_load\_balancer\_controller\_policy](#input\_attach\_load\_balancer\_controller\_policy) | Determines whether to attach the Load Balancer Controller policy to the role | `bool` | `false` | no | +| [attach\_load\_balancer\_controller\_targetgroup\_binding\_only\_policy](#input\_attach\_load\_balancer\_controller\_targetgroup\_binding\_only\_policy) | Determines whether to attach the Load Balancer Controller policy for the TargetGroupBinding only | `bool` | `false` | no | +| [attach\_node\_termination\_handler\_policy](#input\_attach\_node\_termination\_handler\_policy) | Determines whether to attach the Node Termination Handler policy to the role | `bool` | `false` | no | +| [attach\_velero\_policy](#input\_attach\_velero\_policy) | Determines whether to attach the Velero IAM policy to the role | `bool` | `false` | no | +| [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | +| [cert\_manager\_hosted\_zone\_arns](#input\_cert\_manager\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow Cert manager to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | +| [cluster\_autoscaler\_cluster\_ids](#input\_cluster\_autoscaler\_cluster\_ids) | List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy | `list(string)` | `[]` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | +| [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | +| [external\_dns\_hosted\_zone\_arns](#input\_external\_dns\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow External DNS to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | +| [external\_secrets\_secrets\_manager\_arns](#input\_external\_secrets\_secrets\_manager\_arns) | List of Secrets Manager ARNs that contain secrets to mount using External Secrets | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [external\_secrets\_ssm\_parameter\_arns](#input\_external\_secrets\_ssm\_parameter\_arns) | List of Systems Manager Parameter ARNs that contain secrets to mount using External Secrets | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [fsx\_lustre\_csi\_service\_role\_arns](#input\_fsx\_lustre\_csi\_service\_role\_arns) | Service role ARNs to allow FSx for Lustre CSI create and manage FSX for Lustre service linked roles | `list(string)` |
[
"arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*"
]
| no | +| [karpenter\_controller\_cluster\_id](#input\_karpenter\_controller\_cluster\_id) | Cluster ID where the Karpenter controller is provisioned/managing | `string` | `"*"` | no | +| [karpenter\_controller\_node\_iam\_role\_arns](#input\_karpenter\_controller\_node\_iam\_role\_arns) | List of node IAM role ARNs Karpenter can use to launch nodes | `list(string)` |
[
"*"
]
| no | +| [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | +| [karpenter\_subnet\_account\_id](#input\_karpenter\_subnet\_account\_id) | Account ID of where the subnets Karpenter will utilize resides. Used when subnets are shared from another account | `string` | `""` | no | +| [karpenter\_tag\_key](#input\_karpenter\_tag\_key) | Tag key (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner | `string` | `"karpenter.sh/discovery"` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | +| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provider map should contain the `provider`, `provider_arn`, and `namespace_service_accounts` | `any` | `{}` | no | +| [policy\_name\_prefix](#input\_policy\_name\_prefix) | IAM policy name prefix | `string` | `"AmazonEKS_"` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | no | +| [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no | +| [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `null` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `null` | no | +| [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `map(string)` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no | +| [velero\_s3\_bucket\_arns](#input\_velero\_s3\_bucket\_arns) | List of S3 Bucket ARNs that Velero needs access to in order to backup and restore cluster resources | `list(string)` |
[
"*"
]
| no | +| [vpc\_cni\_enable\_ipv4](#input\_vpc\_cni\_enable\_ipv4) | Determines whether to enable IPv4 permissions for VPC CNI policy | `bool` | `false` | no | +| [vpc\_cni\_enable\_ipv6](#input\_vpc\_cni\_enable\_ipv6) | Determines whether to enable IPv6 permissions for VPC CNI policy | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/main.tf b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/main.tf new file mode 100644 index 0000000..5b99694 --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/main.tf @@ -0,0 +1,55 @@ +data "aws_iam_policy_document" "this" { + count = var.create_role ? 1 : 0 + + dynamic "statement" { + for_each = var.oidc_providers + + content { + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = [statement.value.provider_arn] + } + + condition { + test = var.assume_role_condition_test + variable = "${replace(statement.value.provider_arn, "/^(.*provider/)/", "")}:sub" + values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"] + } + + # https://aws.amazon.com/premiumsupport/knowledge-center/eks-troubleshoot-oidc-and-irsa/?nc1=h_ls + condition { + test = var.assume_role_condition_test + variable = "${replace(statement.value.provider_arn, "/^(.*provider/)/", "")}:aud" + values = ["sts.amazonaws.com"] + } + + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + name = var.role_name + name_prefix = var.role_name_prefix + path = var.role_path + description = var.role_description + + assume_role_policy = data.aws_iam_policy_document.this[0].json + # assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":\"arn:aws:iam::111111111111:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/xxxxxxxx\"},\"Action\":\"sts:AssumeRole\",\"Condition\":{}}]}" + max_session_duration = var.max_session_duration + permissions_boundary = var.role_permissions_boundary_arn + force_detach_policies = var.force_detach_policies + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in var.role_policy_arns : k => v if var.create_role } + + role = aws_iam_role.this[0].name + policy_arn = each.value +} diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/outputs.tf b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/outputs.tf new file mode 100644 index 0000000..a680531 --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/policies.tf b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/policies.tf new file mode 100644 index 0000000..7821a9a --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/policies.tf @@ -0,0 +1,1275 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition + dns_suffix = data.aws_partition.current.dns_suffix +} + +################################################################################ +# Cert Manager Policy +################################################################################ + +# https://cert-manager.io/docs/configuration/acme/dns01/route53/#set-up-an-iam-role +data "aws_iam_policy_document" "cert_manager" { + count = var.create_role && var.attach_cert_manager_policy ? 1 : 0 + + statement { + actions = ["route53:GetChange"] + resources = ["arn:${local.partition}:route53:::change/*"] + } + + statement { + actions = [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ] + + resources = var.cert_manager_hosted_zone_arns + } + + statement { + actions = ["route53:ListHostedZonesByName"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "cert_manager" { + count = var.create_role && var.attach_cert_manager_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Cert_Manager_Policy-" + path = var.role_path + description = "Cert Manager policy to allow management of Route53 hosted zone records" + policy = data.aws_iam_policy_document.cert_manager[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "cert_manager" { + count = var.create_role && var.attach_cert_manager_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.cert_manager[0].arn +} + +################################################################################ +# Cluster Autoscaler Policy +################################################################################ + +# https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md +data "aws_iam_policy_document" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + statement { + actions = [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeScalingActivities", + "autoscaling:DescribeTags", + "ec2:DescribeLaunchTemplateVersions", + "ec2:DescribeInstanceTypes", + "eks:DescribeNodegroup", + ] + + resources = ["*"] + } + + dynamic "statement" { + for_each = toset(var.cluster_autoscaler_cluster_ids) + content { + actions = [ + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "autoscaling:ResourceTag/kubernetes.io/cluster/${statement.value}" + values = ["owned"] + } + } + } +} + +resource "aws_iam_policy" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Cluster_Autoscaler_Policy-" + path = var.role_path + description = "Cluster autoscaler policy to allow examination and modification of EC2 Auto Scaling Groups" + policy = data.aws_iam_policy_document.cluster_autoscaler[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.cluster_autoscaler[0].arn +} + +################################################################################ +# EBS CSI Policy +################################################################################ + +# https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/example-iam-policy.json +data "aws_iam_policy_document" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + statement { + actions = [ + "ec2:CreateSnapshot", + "ec2:AttachVolume", + "ec2:DetachVolume", + "ec2:ModifyVolume", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeSnapshots", + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:DescribeVolumesModifications", + ] + + resources = ["*"] + } + + statement { + actions = ["ec2:CreateTags"] + + resources = [ + "arn:${local.partition}:ec2:*:*:volume/*", + "arn:${local.partition}:ec2:*:*:snapshot/*", + ] + + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + values = [ + "CreateVolume", + "CreateSnapshot", + ] + } + } + + statement { + actions = ["ec2:DeleteTags"] + + resources = [ + "arn:${local.partition}:ec2:*:*:volume/*", + "arn:${local.partition}:ec2:*:*:snapshot/*", + ] + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/ebs.csi.aws.com/cluster" + values = [ + true + ] + } + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = [true] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + actions = ["ec2:DeleteSnapshot"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeSnapshotName" + values = ["*"] + } + } + + statement { + actions = ["ec2:DeleteSnapshot"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = [true] + } + } + + dynamic "statement" { + for_each = length(var.ebs_csi_kms_cmk_ids) > 0 ? [1] : [] + content { + actions = [ + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant", + ] + + resources = var.ebs_csi_kms_cmk_ids + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = [true] + } + } + } + + dynamic "statement" { + for_each = length(var.ebs_csi_kms_cmk_ids) > 0 ? [1] : [] + content { + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + + resources = var.ebs_csi_kms_cmk_ids + } + } +} + +resource "aws_iam_policy" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}EBS_CSI_Policy-" + path = var.role_path + description = "Provides permissions to manage EBS volumes via the container storage interface driver" + policy = data.aws_iam_policy_document.ebs_csi[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.ebs_csi[0].arn +} + +################################################################################ +# EFS CSI Driver Policy +################################################################################ + +# https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/docs/iam-policy-example.json +data "aws_iam_policy_document" "efs_csi" { + count = var.create_role && var.attach_efs_csi_policy ? 1 : 0 + + statement { + actions = [ + "ec2:DescribeAvailabilityZones", + "elasticfilesystem:DescribeAccessPoints", + "elasticfilesystem:DescribeFileSystems", + "elasticfilesystem:DescribeMountTargets", + ] + + resources = ["*"] + } + + statement { + actions = ["elasticfilesystem:CreateAccessPoint"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/efs.csi.aws.com/cluster" + values = ["true"] + } + } + + statement { + actions = ["elasticfilesystem:DeleteAccessPoint"] + resources = ["*"] + + condition { + test = "StringEquals" + variable = "aws:ResourceTag/efs.csi.aws.com/cluster" + values = ["true"] + } + } +} + +resource "aws_iam_policy" "efs_csi" { + count = var.create_role && var.attach_efs_csi_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}EFS_CSI_Policy-" + path = var.role_path + description = "Provides permissions to manage EFS volumes via the container storage interface driver" + policy = data.aws_iam_policy_document.efs_csi[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "efs_csi" { + count = var.create_role && var.attach_efs_csi_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.efs_csi[0].arn +} + +################################################################################ +# External DNS Policy +################################################################################ + +# https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy +data "aws_iam_policy_document" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + statement { + actions = ["route53:ChangeResourceRecordSets"] + resources = var.external_dns_hosted_zone_arns + } + + statement { + actions = [ + "route53:ListHostedZones", + "route53:ListResourceRecordSets", + ] + + resources = ["*"] + } +} + +resource "aws_iam_policy" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}External_DNS_Policy-" + path = var.role_path + description = "External DNS policy to allow management of Route53 hosted zone records" + policy = data.aws_iam_policy_document.external_dns[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.external_dns[0].arn +} + +################################################################################ +# External Secrets Policy +################################################################################ + +# https://github.com/external-secrets/kubernetes-external-secrets#add-a-secret +data "aws_iam_policy_document" "external_secrets" { + count = var.create_role && var.attach_external_secrets_policy ? 1 : 0 + + statement { + actions = ["ssm:GetParameter"] + resources = var.external_secrets_ssm_parameter_arns + } + + statement { + actions = [ + "secretsmanager:GetResourcePolicy", + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", + "secretsmanager:ListSecretVersionIds", + ] + resources = var.external_secrets_secrets_manager_arns + } +} + +resource "aws_iam_policy" "external_secrets" { + count = var.create_role && var.attach_external_secrets_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}External_Secrets_Policy-" + path = var.role_path + description = "Provides permissions to for External Secrets to retrieve secrets from AWS SSM and AWS Secrets Manager" + policy = data.aws_iam_policy_document.external_secrets[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "external_secrets" { + count = var.create_role && var.attach_external_secrets_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.external_secrets[0].arn +} + +################################################################################ +# FSx for Lustre CSI Driver Policy +################################################################################ + +# https://github.com/kubernetes-sigs/aws-fsx-csi-driver/blob/master/docs/README.md +data "aws_iam_policy_document" "fsx_lustre_csi" { + count = var.create_role && var.attach_fsx_lustre_csi_policy ? 1 : 0 + + statement { + actions = [ + "iam:CreateServiceLinkedRole", + "iam:AttachRolePolicy", + "iam:PutRolePolicy" + ] + resources = var.fsx_lustre_csi_service_role_arns + } + + statement { + actions = ["iam:CreateServiceLinkedRole"] + resources = ["*"] + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["fsx.${local.dns_suffix}"] + } + } + + statement { + actions = [ + "s3:ListBucket", + "fsx:CreateFileSystem", + "fsx:DeleteFileSystem", + "fsx:DescribeFileSystems", + "fsx:TagResource", + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "fsx_lustre_csi" { + count = var.create_role && var.attach_fsx_lustre_csi_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}FSx_Lustre_CSI_Policy-" + path = var.role_path + description = "Provides permissions to manage FSx Lustre volumes via the container storage interface driver" + policy = data.aws_iam_policy_document.fsx_lustre_csi[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "fsx_lustre_csi" { + count = var.create_role && var.attach_fsx_lustre_csi_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.fsx_lustre_csi[0].arn +} + +################################################################################ +# Karpenter Controller Policy +################################################################################ + +# curl -fsSL https://karpenter.sh/v0.6.1/getting-started/cloudformation.yaml +data "aws_iam_policy_document" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + statement { + actions = [ + "ec2:CreateLaunchTemplate", + "ec2:CreateFleet", + "ec2:CreateTags", + "ec2:DescribeLaunchTemplates", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSpotPriceHistory", + "pricing:GetProducts", + ] + + resources = ["*"] + } + + statement { + actions = [ + "ec2:TerminateInstances", + "ec2:DeleteLaunchTemplate", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/${var.karpenter_tag_key}" + values = [var.karpenter_controller_cluster_id] + } + } + + statement { + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:*:${local.account_id}:launch-template/*", + "arn:${local.partition}:ec2:*:${local.account_id}:security-group/*", + ] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/${var.karpenter_tag_key}" + values = [var.karpenter_controller_cluster_id] + } + } + + statement { + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:*::image/*", + "arn:${local.partition}:ec2:*:${local.account_id}:instance/*", + "arn:${local.partition}:ec2:*:${local.account_id}:spot-instances-request/*", + "arn:${local.partition}:ec2:*:${local.account_id}:volume/*", + "arn:${local.partition}:ec2:*:${local.account_id}:network-interface/*", + "arn:${local.partition}:ec2:*:${coalesce(var.karpenter_subnet_account_id, local.account_id)}:subnet/*", + ] + } + + statement { + actions = ["ssm:GetParameter"] + resources = var.karpenter_controller_ssm_parameter_arns + } + + statement { + actions = ["iam:PassRole"] + resources = var.karpenter_controller_node_iam_role_arns + } +} + +resource "aws_iam_policy" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Karpenter_Controller_Policy-" + path = var.role_path + description = "Provides permissions to handle node termination events via the Node Termination Handler" + policy = data.aws_iam_policy_document.karpenter_controller[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.karpenter_controller[0].arn +} + +################################################################################ +# AWS Load Balancer Controller Policy +################################################################################ + +# https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/install/iam_policy.json +data "aws_iam_policy_document" "load_balancer_controller" { + count = var.create_role && var.attach_load_balancer_controller_policy ? 1 : 0 + + statement { + actions = ["iam:CreateServiceLinkedRole"] + resources = ["*"] + + condition { + test = "StringEquals" + variable = "iam:AWSServiceName" + values = ["elasticloadbalancing.${local.dns_suffix}"] + } + } + + statement { + actions = [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:AddTags", + ] + resources = ["*"] + } + + statement { + actions = [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", + "wafv2:GetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:AssociateWebACL", + "wafv2:DisassociateWebACL", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection", + ] + resources = ["*"] + } + + statement { + actions = [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:CreateSecurityGroup", + ] + resources = ["*"] + } + + statement { + actions = ["ec2:CreateTags"] + resources = ["arn:${local.partition}:ec2:*:*:security-group/*"] + + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + values = ["CreateSecurityGroup"] + } + + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "ec2:CreateTags", + "ec2:DeleteTags", + ] + resources = ["arn:${local.partition}:ec2:*:*:security-group/*"] + + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["true"] + } + + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup", + ] + resources = ["*"] + + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup", + ] + resources = ["*"] + + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule", + ] + resources = ["*"] + } + + statement { + actions = [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags", + ] + resources = [ + "arn:${local.partition}:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:${local.partition}:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:${local.partition}:elasticloadbalancing:*:*:loadbalancer/app/*/*", + ] + + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["true"] + } + + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags", + ] + resources = [ + "arn:${local.partition}:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:${local.partition}:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:${local.partition}:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:${local.partition}:elasticloadbalancing:*:*:listener-rule/app/*/*/*", + ] + } + + statement { + actions = [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup", + ] + resources = ["*"] + + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + actions = [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets", + ] + resources = ["arn:${local.partition}:elasticloadbalancing:*:*:targetgroup/*/*"] + } + + statement { + actions = [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule", + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "load_balancer_controller" { + count = var.create_role && var.attach_load_balancer_controller_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}AWS_Load_Balancer_Controller-" + path = var.role_path + description = "Provides permissions for AWS Load Balancer Controller addon" + policy = data.aws_iam_policy_document.load_balancer_controller[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "load_balancer_controller" { + count = var.create_role && var.attach_load_balancer_controller_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.load_balancer_controller[0].arn +} + +################################################################################ +# AWS Load Balancer Controller TargetGroup Binding Only Policy +################################################################################ + +# https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/targetgroupbinding/targetgroupbinding/#reference +# https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/installation/#setup-iam-manually +data "aws_iam_policy_document" "load_balancer_controller_targetgroup_only" { + count = var.create_role && var.attach_load_balancer_controller_targetgroup_binding_only_policy ? 1 : 0 + + statement { + actions = [ + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeVpcs", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ] + + resources = ["*"] + } +} + +resource "aws_iam_policy" "load_balancer_controller_targetgroup_only" { + count = var.create_role && var.attach_load_balancer_controller_targetgroup_binding_only_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}AWS_Load_Balancer_Controller_TargetGroup_Only-" + path = var.role_path + description = "Provides permissions for AWS Load Balancer Controller addon in TargetGroup binding only scenario" + policy = data.aws_iam_policy_document.load_balancer_controller_targetgroup_only[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "load_balancer_controller_targetgroup_only" { + count = var.create_role && var.attach_load_balancer_controller_targetgroup_binding_only_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.load_balancer_controller_targetgroup_only[0].arn +} + +################################################################################ +# Appmesh Controller +################################################################################ +# https://github.com/aws/eks-charts/tree/master/stable/appmesh-controller#prerequisites +# https://raw.githubusercontent.com/aws/aws-app-mesh-controller-for-k8s/master/config/iam/controller-iam-policy.json +data "aws_iam_policy_document" "appmesh_controller" { + count = var.create_role && var.attach_appmesh_controller_policy ? 1 : 0 + + statement { + actions = [ + "appmesh:ListVirtualRouters", + "appmesh:ListVirtualServices", + "appmesh:ListRoutes", + "appmesh:ListGatewayRoutes", + "appmesh:ListMeshes", + "appmesh:ListVirtualNodes", + "appmesh:ListVirtualGateways", + "appmesh:DescribeMesh", + "appmesh:DescribeVirtualRouter", + "appmesh:DescribeRoute", + "appmesh:DescribeVirtualNode", + "appmesh:DescribeVirtualGateway", + "appmesh:DescribeGatewayRoute", + "appmesh:DescribeVirtualService", + "appmesh:CreateMesh", + "appmesh:CreateVirtualRouter", + "appmesh:CreateVirtualGateway", + "appmesh:CreateVirtualService", + "appmesh:CreateGatewayRoute", + "appmesh:CreateRoute", + "appmesh:CreateVirtualNode", + "appmesh:UpdateMesh", + "appmesh:UpdateRoute", + "appmesh:UpdateVirtualGateway", + "appmesh:UpdateVirtualRouter", + "appmesh:UpdateGatewayRoute", + "appmesh:UpdateVirtualService", + "appmesh:UpdateVirtualNode", + "appmesh:DeleteMesh", + "appmesh:DeleteRoute", + "appmesh:DeleteVirtualRouter", + "appmesh:DeleteGatewayRoute", + "appmesh:DeleteVirtualService", + "appmesh:DeleteVirtualNode", + "appmesh:DeleteVirtualGateway" + ] + resources = ["*"] + } + + statement { + actions = [ + "iam:CreateServiceLinkedRole" + ] + resources = ["arn:${local.partition}:iam::*:role/aws-service-role/appmesh.${local.dns_suffix}/AWSServiceRoleForAppMesh"] + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["appmesh.${local.dns_suffix}"] + } + } + + statement { + actions = [ + "acm:ListCertificates", + "acm:DescribeCertificate", + "acm-pca:DescribeCertificateAuthority", + "acm-pca:ListCertificateAuthorities" + ] + resources = ["*"] + } + + statement { + actions = [ + "servicediscovery:CreateService", + "servicediscovery:DeleteService", + "servicediscovery:GetService", + "servicediscovery:GetInstance", + "servicediscovery:RegisterInstance", + "servicediscovery:DeregisterInstance", + "servicediscovery:ListInstances", + "servicediscovery:ListNamespaces", + "servicediscovery:ListServices", + "servicediscovery:GetInstancesHealthStatus", + "servicediscovery:UpdateInstanceCustomHealthStatus", + "servicediscovery:GetOperation", + "route53:GetHealthCheck", + "route53:CreateHealthCheck", + "route53:UpdateHealthCheck", + "route53:ChangeResourceRecordSets", + "route53:DeleteHealthCheck" + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "appmesh_controller" { + count = var.create_role && var.attach_appmesh_controller_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Appmesh_Controller-" + path = var.role_path + description = "Provides permissions to for appmesh controller" + policy = data.aws_iam_policy_document.appmesh_controller[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "appmesh_controller" { + count = var.create_role && var.attach_appmesh_controller_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.appmesh_controller[0].arn +} + +################################################################################ +# Appmesh envoy proxy +################################################################################ +# https://github.com/aws/aws-app-mesh-controller-for-k8s/blob/f4a551399c4a4428d31692d0e6d944c2b78f2753/config/helm/appmesh-controller/README.md#with-irsa +# https://raw.githubusercontent.com/aws/aws-app-mesh-controller-for-k8s/master/config/iam/envoy-iam-policy.json +data "aws_iam_policy_document" "appmesh_envoy_proxy" { + count = var.create_role && var.attach_appmesh_envoy_proxy_policy ? 1 : 0 + + statement { + actions = [ + "appmesh:StreamAggregatedResources" + ] + resources = ["*"] + } + + statement { + actions = [ + "acm:ExportCertificate", + "acm-pca:GetCertificateAuthorityCertificate" + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "appmesh_envoy_proxy" { + count = var.create_role && var.attach_appmesh_envoy_proxy_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Appmesh_Envoy_Proxy-" + path = var.role_path + description = "Provides permissions to for appmesh envoy proxy" + policy = data.aws_iam_policy_document.appmesh_envoy_proxy[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "appmesh_envoy_proxy" { + count = var.create_role && var.attach_appmesh_envoy_proxy_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.appmesh_envoy_proxy[0].arn +} + +################################################################################ +# Amazon Managed Service for Prometheus Policy +################################################################################ + +# https://docs.aws.amazon.com/prometheus/latest/userguide/set-up-irsa.html +data "aws_iam_policy_document" "amazon_managed_service_prometheus" { + count = var.create_role && var.attach_amazon_managed_service_prometheus_policy ? 1 : 0 + + statement { + actions = [ + "aps:RemoteWrite", + "aps:QueryMetrics", + "aps:GetSeries", + "aps:GetLabels", + "aps:GetMetricMetadata", + ] + + resources = var.amazon_managed_service_prometheus_workspace_arns + } +} + +resource "aws_iam_policy" "amazon_managed_service_prometheus" { + count = var.create_role && var.attach_amazon_managed_service_prometheus_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Managed_Service_Prometheus_Policy-" + path = var.role_path + description = "Provides permissions to for Amazon Managed Service for Prometheus" + policy = data.aws_iam_policy_document.amazon_managed_service_prometheus[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "amazon_managed_service_prometheus" { + count = var.create_role && var.attach_amazon_managed_service_prometheus_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.amazon_managed_service_prometheus[0].arn +} + +################################################################################ +# Node Termination Handler Policy +################################################################################ + +# https://github.com/aws/aws-node-termination-handler#5-create-an-iam-role-for-the-pods +data "aws_iam_policy_document" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + statement { + actions = [ + "autoscaling:CompleteLifecycleAction", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeTags", + "ec2:DescribeInstances", + ] + + resources = ["*"] + } + + statement { + actions = [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + ] + + resources = var.node_termination_handler_sqs_queue_arns + } +} + +resource "aws_iam_policy" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Node_Termination_Handler_Policy-" + path = var.role_path + description = "Provides permissions to handle node termination events via the Node Termination Handler" + policy = data.aws_iam_policy_document.node_termination_handler[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.node_termination_handler[0].arn +} + +################################################################################ +# Velero Policy +################################################################################ + +# https://github.com/vmware-tanzu/velero-plugin-for-aws#set-permissions-for-velero +data "aws_iam_policy_document" "velero" { + count = var.create_role && var.attach_velero_policy ? 1 : 0 + + statement { + sid = "Ec2ReadWrite" + actions = [ + "ec2:DescribeVolumes", + "ec2:DescribeSnapshots", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:CreateSnapshot", + "ec2:DeleteSnapshot", + ] + resources = ["*"] + } + + statement { + sid = "S3ReadWrite" + actions = [ + "s3:GetObject", + "s3:DeleteObject", + "s3:PutObject", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + ] + resources = [for bucket in var.velero_s3_bucket_arns : "${bucket}/*"] + } + + statement { + sid = "S3List" + actions = [ + "s3:ListBucket", + ] + resources = var.velero_s3_bucket_arns + } +} + +resource "aws_iam_policy" "velero" { + count = var.create_role && var.attach_velero_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}Velero_Policy-" + path = var.role_path + description = "Provides Velero permissions to backup and restore cluster resources" + policy = data.aws_iam_policy_document.velero[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "velero" { + count = var.create_role && var.attach_velero_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.velero[0].arn +} + +################################################################################ +# VPC CNI Policy +################################################################################ + +data "aws_iam_policy_document" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + # arn:${local.partition}:iam::aws:policy/AmazonEKS_CNI_Policy + dynamic "statement" { + for_each = var.vpc_cni_enable_ipv4 ? [1] : [] + content { + sid = "IPV4" + actions = [ + "ec2:AssignPrivateIpAddresses", + "ec2:AttachNetworkInterface", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypes", + "ec2:DetachNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:UnassignPrivateIpAddresses", + ] + resources = ["*"] + } + } + + # https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy + dynamic "statement" { + for_each = var.vpc_cni_enable_ipv6 ? [1] : [] + content { + sid = "IPV6" + actions = [ + "ec2:AssignIpv6Addresses", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypes", + ] + resources = ["*"] + } + } + + statement { + sid = "CreateTags" + actions = ["ec2:CreateTags"] + resources = ["arn:${local.partition}:ec2:*:*:network-interface/*"] + } +} + +resource "aws_iam_policy" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + name_prefix = "${var.policy_name_prefix}CNI_Policy-" + path = var.role_path + description = "Provides the Amazon VPC CNI Plugin (amazon-vpc-cni-k8s) the permissions it requires to modify the IPv4/IPv6 address configuration on your EKS worker nodes" + policy = data.aws_iam_policy_document.vpc_cni[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.vpc_cni[0].arn +} diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/variables.tf b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/variables.tf new file mode 100644 index 0000000..3d9504f --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/variables.tf @@ -0,0 +1,297 @@ +variable "create_role" { + description = "Whether to create a role" + type = bool + default = true +} + +variable "role_name" { + description = "Name of IAM role" + type = string + default = null +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = null +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = null +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = null +} + +variable "role_name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "policy_name_prefix" { + description = "IAM policy name prefix" + type = string + default = "AmazonEKS_" +} + +variable "role_policy_arns" { + description = "ARNs of any policies to attach to the IAM role" + type = map(string) + default = {} +} + +variable "oidc_providers" { + description = "Map of OIDC providers where each provider map should contain the `provider`, `provider_arn`, and `namespace_service_accounts`" + type = any + default = {} +} + +variable "tags" { + description = "A map of tags to add the the IAM role" + type = map(any) + default = {} +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = true +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +variable "assume_role_condition_test" { + description = "Name of the [IAM condition operator](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html) to evaluate when assuming the role" + type = string + default = "StringEquals" +} + +################################################################################ +# Policies +################################################################################ + +# Cert Manager +variable "attach_cert_manager_policy" { + description = "Determines whether to attach the Cert Manager IAM policy to the role" + type = bool + default = false +} + +variable "cert_manager_hosted_zone_arns" { + description = "Route53 hosted zone ARNs to allow Cert manager to manage records" + type = list(string) + default = ["arn:aws:route53:::hostedzone/*"] +} + +# Cluster autoscaler +variable "attach_cluster_autoscaler_policy" { + description = "Determines whether to attach the Cluster Autoscaler IAM policy to the role" + type = bool + default = false +} + +variable "cluster_autoscaler_cluster_ids" { + description = "List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy" + type = list(string) + default = [] +} + +# EBS CSI +variable "attach_ebs_csi_policy" { + description = "Determines whether to attach the EBS CSI IAM policy to the role" + type = bool + default = false +} + +variable "ebs_csi_kms_cmk_ids" { + description = "KMS CMK IDs to allow EBS CSI to manage encrypted volumes" + type = list(string) + default = [] +} + +# EFS CSI +variable "attach_efs_csi_policy" { + description = "Determines whether to attach the EFS CSI IAM policy to the role" + type = bool + default = false +} + +# External DNS +variable "attach_external_dns_policy" { + description = "Determines whether to attach the External DNS IAM policy to the role" + type = bool + default = false +} + +variable "external_dns_hosted_zone_arns" { + description = "Route53 hosted zone ARNs to allow External DNS to manage records" + type = list(string) + default = ["arn:aws:route53:::hostedzone/*"] +} + +# External Secrets +variable "attach_external_secrets_policy" { + description = "Determines whether to attach the External Secrets policy to the role" + type = bool + default = false +} + +variable "external_secrets_ssm_parameter_arns" { + description = "List of Systems Manager Parameter ARNs that contain secrets to mount using External Secrets" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "external_secrets_secrets_manager_arns" { + description = "List of Secrets Manager ARNs that contain secrets to mount using External Secrets" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +# FSx Lustre CSI +variable "attach_fsx_lustre_csi_policy" { + description = "Determines whether to attach the FSx for Lustre CSI Driver IAM policy to the role" + type = bool + default = false +} + +variable "fsx_lustre_csi_service_role_arns" { + description = "Service role ARNs to allow FSx for Lustre CSI create and manage FSX for Lustre service linked roles" + type = list(string) + default = ["arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*"] +} + +# Karpenter controller +variable "attach_karpenter_controller_policy" { + description = "Determines whether to attach the Karpenter Controller policy to the role" + type = bool + default = false +} + +variable "karpenter_controller_cluster_id" { + description = "Cluster ID where the Karpenter controller is provisioned/managing" + type = string + default = "*" +} + +variable "karpenter_tag_key" { + description = "Tag key (`{key = value}`) applied to resources launched by Karpenter through the Karpenter provisioner" + type = string + default = "karpenter.sh/discovery" +} + +variable "karpenter_controller_ssm_parameter_arns" { + description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" + type = list(string) + # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 + default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] +} + +variable "karpenter_controller_node_iam_role_arns" { + description = "List of node IAM role ARNs Karpenter can use to launch nodes" + type = list(string) + default = ["*"] +} + +variable "karpenter_subnet_account_id" { + description = "Account ID of where the subnets Karpenter will utilize resides. Used when subnets are shared from another account" + type = string + default = "" +} + +# AWS Load Balancer Controller +variable "attach_load_balancer_controller_policy" { + description = "Determines whether to attach the Load Balancer Controller policy to the role" + type = bool + default = false +} + +# https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/targetgroupbinding/targetgroupbinding/#reference +# https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/installation/#setup-iam-manually +variable "attach_load_balancer_controller_targetgroup_binding_only_policy" { + description = "Determines whether to attach the Load Balancer Controller policy for the TargetGroupBinding only" + type = bool + default = false +} + +# AWS Appmesh Controller +variable "attach_appmesh_controller_policy" { + description = "Determines whether to attach the Appmesh Controller policy to the role" + type = bool + default = false +} + +# AWS Appmesh envoy proxy +variable "attach_appmesh_envoy_proxy_policy" { + description = "Determines whether to attach the Appmesh envoy proxy policy to the role" + type = bool + default = false +} + +# Amazon Managed Service for Prometheus +variable "attach_amazon_managed_service_prometheus_policy" { + description = "Determines whether to attach the Amazon Managed Service for Prometheus IAM policy to the role" + type = bool + default = false +} + +variable "amazon_managed_service_prometheus_workspace_arns" { + description = "List of AMP Workspace ARNs to read and write metrics" + type = list(string) + default = ["*"] +} + +# Velero +variable "attach_velero_policy" { + description = "Determines whether to attach the Velero IAM policy to the role" + type = bool + default = false +} + +variable "velero_s3_bucket_arns" { + description = "List of S3 Bucket ARNs that Velero needs access to in order to backup and restore cluster resources" + type = list(string) + default = ["*"] +} + +# VPC CNI +variable "attach_vpc_cni_policy" { + description = "Determines whether to attach the VPC CNI IAM policy to the role" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv4" { + description = "Determines whether to enable IPv4 permissions for VPC CNI policy" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv6" { + description = "Determines whether to enable IPv6 permissions for VPC CNI policy" + type = bool + default = false +} + +# Node termination handler +variable "attach_node_termination_handler_policy" { + description = "Determines whether to attach the Node Termination Handler policy to the role" + type = bool + default = false +} + +variable "node_termination_handler_sqs_queue_arns" { + description = "List of SQS ARNs that contain node termination events" + type = list(string) + default = ["*"] +} diff --git a/modules/aws-iam/modules/iam-role-for-service-accounts-eks/versions.tf b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-iam/modules/iam-role-for-service-accounts-eks/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-iam/modules/iam-user/README.md b/modules/aws-iam/modules/iam-user/README.md new file mode 100644 index 0000000..15925ff --- /dev/null +++ b/modules/aws-iam/modules/iam-user/README.md @@ -0,0 +1,94 @@ +# iam-user + +Creates IAM user, IAM login profile, IAM access key and uploads IAM SSH user public key. All of these are optional resources. + +## Notes for keybase users + +**If possible, always use PGP encryption to prevent Terraform from keeping unencrypted password and access secret key in state file.** + +### Keybase pre-requisits + +When `pgp_key` is specified as `keybase:username`, make sure that that user has already uploaded public key to keybase.io. For example, user with username `test` has done it properly and you can [verify it here](https://keybase.io/test/pgp_keys.asc). + +### How to decrypt user's encrypted password and secret key + +This module outputs commands and PGP messages which can be decrypted either using [keybase.io web-site](https://keybase.io/decrypt) or using command line to get user's password and user's secret key: +- `keybase_password_decrypt_command` +- `keybase_secret_key_decrypt_command` +- `keybase_ses_smtp_password_v4_decrypt_command` +- `keybase_password_pgp_message` +- `keybase_secret_key_pgp_message` +- `keybase_ses_smtp_password_v4_pgp_message` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_access_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource | +| [aws_iam_access_key.this_no_pgp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource | +| [aws_iam_user.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource | +| [aws_iam_user_login_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_login_profile) | resource | +| [aws_iam_user_ssh_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_ssh_key) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create\_iam\_access\_key](#input\_create\_iam\_access\_key) | Whether to create IAM access key | `bool` | `true` | no | +| [create\_iam\_user\_login\_profile](#input\_create\_iam\_user\_login\_profile) | Whether to create IAM user login profile | `bool` | `true` | no | +| [create\_user](#input\_create\_user) | Whether to create the IAM user | `bool` | `true` | no | +| [force\_destroy](#input\_force\_destroy) | When destroying this user, destroy even if it has non-Terraform-managed IAM access keys, login profile or MFA devices. Without force\_destroy a user with non-Terraform-managed access keys and login profile will fail to be destroyed. | `bool` | `false` | no | +| [name](#input\_name) | Desired name for the IAM user | `string` | n/a | yes | +| [password\_length](#input\_password\_length) | The length of the generated password | `number` | `20` | no | +| [password\_reset\_required](#input\_password\_reset\_required) | Whether the user should be forced to reset the generated password on first login. | `bool` | `true` | no | +| [path](#input\_path) | Desired path for the IAM user | `string` | `"/"` | no | +| [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the user. | `string` | `""` | no | +| [pgp\_key](#input\_pgp\_key) | Either a base-64 encoded PGP public key, or a keybase username in the form `keybase:username`. Used to encrypt password and access key. | `string` | `""` | no | +| [ssh\_key\_encoding](#input\_ssh\_key\_encoding) | Specifies the public key encoding format to use in the response. To retrieve the public key in ssh-rsa format, use SSH. To retrieve the public key in PEM format, use PEM | `string` | `"SSH"` | no | +| [ssh\_public\_key](#input\_ssh\_public\_key) | The SSH public key. The public key must be encoded in ssh-rsa format or PEM format | `string` | `""` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | +| [upload\_iam\_user\_ssh\_key](#input\_upload\_iam\_user\_ssh\_key) | Whether to upload a public ssh key to the IAM user | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_access\_key\_encrypted\_secret](#output\_iam\_access\_key\_encrypted\_secret) | The encrypted secret, base64 encoded | +| [iam\_access\_key\_encrypted\_ses\_smtp\_password\_v4](#output\_iam\_access\_key\_encrypted\_ses\_smtp\_password\_v4) | The encrypted secret access key converted into an SES SMTP password by applying AWS's Sigv4 conversion algorithm | +| [iam\_access\_key\_id](#output\_iam\_access\_key\_id) | The access key ID | +| [iam\_access\_key\_key\_fingerprint](#output\_iam\_access\_key\_key\_fingerprint) | The fingerprint of the PGP key used to encrypt the secret | +| [iam\_access\_key\_secret](#output\_iam\_access\_key\_secret) | The access key secret | +| [iam\_access\_key\_ses\_smtp\_password\_v4](#output\_iam\_access\_key\_ses\_smtp\_password\_v4) | The secret access key converted into an SES SMTP password by applying AWS's Sigv4 conversion algorithm | +| [iam\_access\_key\_status](#output\_iam\_access\_key\_status) | Active or Inactive. Keys are initially active, but can be made inactive by other means. | +| [iam\_user\_arn](#output\_iam\_user\_arn) | The ARN assigned by AWS for this user | +| [iam\_user\_login\_profile\_encrypted\_password](#output\_iam\_user\_login\_profile\_encrypted\_password) | The encrypted password, base64 encoded | +| [iam\_user\_login\_profile\_key\_fingerprint](#output\_iam\_user\_login\_profile\_key\_fingerprint) | The fingerprint of the PGP key used to encrypt the password | +| [iam\_user\_login\_profile\_password](#output\_iam\_user\_login\_profile\_password) | The user password | +| [iam\_user\_name](#output\_iam\_user\_name) | The user's name | +| [iam\_user\_ssh\_key\_fingerprint](#output\_iam\_user\_ssh\_key\_fingerprint) | The MD5 message digest of the SSH public key | +| [iam\_user\_ssh\_key\_ssh\_public\_key\_id](#output\_iam\_user\_ssh\_key\_ssh\_public\_key\_id) | The unique identifier for the SSH public key | +| [iam\_user\_unique\_id](#output\_iam\_user\_unique\_id) | The unique ID assigned by AWS | +| [keybase\_password\_decrypt\_command](#output\_keybase\_password\_decrypt\_command) | Decrypt user password command | +| [keybase\_password\_pgp\_message](#output\_keybase\_password\_pgp\_message) | Encrypted password | +| [keybase\_secret\_key\_decrypt\_command](#output\_keybase\_secret\_key\_decrypt\_command) | Decrypt access secret key command | +| [keybase\_secret\_key\_pgp\_message](#output\_keybase\_secret\_key\_pgp\_message) | Encrypted access secret key | +| [keybase\_ses\_smtp\_password\_v4\_decrypt\_command](#output\_keybase\_ses\_smtp\_password\_v4\_decrypt\_command) | Decrypt SES SMTP password command | +| [keybase\_ses\_smtp\_password\_v4\_pgp\_message](#output\_keybase\_ses\_smtp\_password\_v4\_pgp\_message) | Encrypted SES SMTP password | +| [pgp\_key](#output\_pgp\_key) | PGP key used to encrypt sensitive data for this user (if empty - secrets are not encrypted) | diff --git a/modules/aws-iam/modules/iam-user/main.tf b/modules/aws-iam/modules/iam-user/main.tf new file mode 100644 index 0000000..792de76 --- /dev/null +++ b/modules/aws-iam/modules/iam-user/main.tf @@ -0,0 +1,45 @@ +resource "aws_iam_user" "this" { + count = var.create_user ? 1 : 0 + + name = var.name + path = var.path + force_destroy = var.force_destroy + permissions_boundary = var.permissions_boundary + + tags = var.tags +} + +resource "aws_iam_user_login_profile" "this" { + count = var.create_user && var.create_iam_user_login_profile ? 1 : 0 + + user = aws_iam_user.this[0].name + pgp_key = var.pgp_key + password_length = var.password_length + password_reset_required = var.password_reset_required + + # TODO: Remove once https://github.com/hashicorp/terraform-provider-aws/issues/23567 is resolved + lifecycle { + ignore_changes = [password_reset_required] + } +} + +resource "aws_iam_access_key" "this" { + count = var.create_user && var.create_iam_access_key && var.pgp_key != "" ? 1 : 0 + + user = aws_iam_user.this[0].name + pgp_key = var.pgp_key +} + +resource "aws_iam_access_key" "this_no_pgp" { + count = var.create_user && var.create_iam_access_key && var.pgp_key == "" ? 1 : 0 + + user = aws_iam_user.this[0].name +} + +resource "aws_iam_user_ssh_key" "this" { + count = var.create_user && var.upload_iam_user_ssh_key ? 1 : 0 + + username = aws_iam_user.this[0].name + encoding = var.ssh_key_encoding + public_key = var.ssh_public_key +} diff --git a/modules/aws-iam/modules/iam-user/outputs.tf b/modules/aws-iam/modules/iam-user/outputs.tf new file mode 100644 index 0000000..98dadef --- /dev/null +++ b/modules/aws-iam/modules/iam-user/outputs.tf @@ -0,0 +1,151 @@ +locals { + has_encrypted_password = length(compact(aws_iam_user_login_profile.this[*].encrypted_password)) > 0 + has_encrypted_secret = length(compact(aws_iam_access_key.this[*].encrypted_secret)) > 0 + has_encrypted_ses_smtp_password_v4 = length(compact(aws_iam_access_key.this[*].encrypted_ses_smtp_password_v4)) > 0 +} + +output "iam_user_name" { + description = "The user's name" + value = try(aws_iam_user.this[0].name, "") +} + +output "iam_user_arn" { + description = "The ARN assigned by AWS for this user" + value = try(aws_iam_user.this[0].arn, "") +} + +output "iam_user_unique_id" { + description = "The unique ID assigned by AWS" + value = try(aws_iam_user.this[0].unique_id, "") +} + +output "iam_user_login_profile_key_fingerprint" { + description = "The fingerprint of the PGP key used to encrypt the password" + value = try(aws_iam_user_login_profile.this[0].key_fingerprint, "") +} + +output "iam_user_login_profile_encrypted_password" { + description = "The encrypted password, base64 encoded" + value = try(aws_iam_user_login_profile.this[0].encrypted_password, "") +} + +output "iam_user_login_profile_password" { + description = "The user password" + value = lookup(try(aws_iam_user_login_profile.this[0], {}), "password", sensitive("")) + sensitive = true +} + +output "iam_access_key_id" { + description = "The access key ID" + value = try(aws_iam_access_key.this[0].id, aws_iam_access_key.this_no_pgp[0].id, "") +} + +output "iam_access_key_secret" { + description = "The access key secret" + value = try(aws_iam_access_key.this_no_pgp[0].secret, "") + sensitive = true +} + +output "iam_access_key_key_fingerprint" { + description = "The fingerprint of the PGP key used to encrypt the secret" + value = try(aws_iam_access_key.this[0].key_fingerprint, "") +} + +output "iam_access_key_encrypted_secret" { + description = "The encrypted secret, base64 encoded" + value = try(aws_iam_access_key.this[0].encrypted_secret, "") +} + +output "iam_access_key_ses_smtp_password_v4" { + description = "The secret access key converted into an SES SMTP password by applying AWS's Sigv4 conversion algorithm" + value = try(aws_iam_access_key.this_no_pgp[0].ses_smtp_password_v4, "") + sensitive = true +} + +output "iam_access_key_encrypted_ses_smtp_password_v4" { + description = "The encrypted secret access key converted into an SES SMTP password by applying AWS's Sigv4 conversion algorithm" + value = try(aws_iam_access_key.this[0].encrypted_ses_smtp_password_v4, "") +} + +output "iam_access_key_status" { + description = "Active or Inactive. Keys are initially active, but can be made inactive by other means." + value = try(aws_iam_access_key.this[0].status, aws_iam_access_key.this_no_pgp[0].status, "") +} + +output "pgp_key" { + description = "PGP key used to encrypt sensitive data for this user (if empty - secrets are not encrypted)" + value = var.pgp_key +} + +output "keybase_password_decrypt_command" { + description = "Decrypt user password command" + value = !local.has_encrypted_password ? null : < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.21 | +| [tls](#requirement\_tls) | >= 3.4 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.21 | +| [tls](#provider\_tls) | >= 3.4 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_key_pair.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | +| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_private\_key](#input\_create\_private\_key) | Determines whether a private key will be created | `bool` | `false` | no | +| [key\_name](#input\_key\_name) | The name for the key pair. Conflicts with `key_name_prefix` | `string` | `null` | no | +| [key\_name\_prefix](#input\_key\_name\_prefix) | Creates a unique name beginning with the specified prefix. Conflicts with `key_name` | `string` | `null` | no | +| [private\_key\_algorithm](#input\_private\_key\_algorithm) | Name of the algorithm to use when generating the private key. Currently-supported values are `RSA` and `ED25519` | `string` | `"RSA"` | no | +| [private\_key\_rsa\_bits](#input\_private\_key\_rsa\_bits) | When algorithm is `RSA`, the size of the generated RSA key, in bits (default: `4096`) | `number` | `4096` | no | +| [public\_key](#input\_public\_key) | The public key material | `string` | `""` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [key\_pair\_arn](#output\_key\_pair\_arn) | The key pair ARN | +| [key\_pair\_fingerprint](#output\_key\_pair\_fingerprint) | The MD5 public key fingerprint as specified in section 4 of RFC 4716 | +| [key\_pair\_id](#output\_key\_pair\_id) | The key pair ID | +| [key\_pair\_name](#output\_key\_pair\_name) | The key pair name | +| [private\_key\_id](#output\_private\_key\_id) | Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource | +| [private\_key\_openssh](#output\_private\_key\_openssh) | Private key data in OpenSSH PEM (RFC 4716) format | +| [private\_key\_pem](#output\_private\_key\_pem) | Private key data in PEM (RFC 1421) format | +| [public\_key\_fingerprint\_md5](#output\_public\_key\_fingerprint\_md5) | The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [public\_key\_fingerprint\_sha256](#output\_public\_key\_fingerprint\_sha256) | The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [public\_key\_openssh](#output\_public\_key\_openssh) | The public key data in "Authorized Keys" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys | +| [public\_key\_pem](#output\_public\_key\_pem) | Public key data in PEM (RFC 1421) format | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-key-pair/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-key-pair/tree/master/LICENSE) for full details. diff --git a/modules/aws-key-pair/examples/README.md b/modules/aws-key-pair/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/modules/aws-key-pair/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/modules/aws-key-pair/examples/complete/README.md b/modules/aws-key-pair/examples/complete/README.md new file mode 100644 index 0000000..c25ef9b --- /dev/null +++ b/modules/aws-key-pair/examples/complete/README.md @@ -0,0 +1,79 @@ +# AWS EC2 key pair + +Configuration in this directory creates EC2 key pair + +- One key pair with a module created private key (private/public key pair) +- One key pair with external provided public key material + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.21 | +| [tls](#requirement\_tls) | >= 3.4 | + +## Providers + +| Name | Version | +|------|---------| +| [tls](#provider\_tls) | >= 3.4 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [key\_pair](#module\_key\_pair) | ../../ | n/a | +| [key\_pair\_disabled](#module\_key\_pair\_disabled) | ../../ | n/a | +| [key\_pair\_external](#module\_key\_pair\_external) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [external\_key\_pair\_arn](#output\_external\_key\_pair\_arn) | The key pair ARN | +| [external\_key\_pair\_fingerprint](#output\_external\_key\_pair\_fingerprint) | The MD5 public key fingerprint as specified in section 4 of RFC 4716 | +| [external\_key\_pair\_id](#output\_external\_key\_pair\_id) | The key pair ID | +| [external\_key\_pair\_name](#output\_external\_key\_pair\_name) | The key pair name | +| [external\_private\_key\_id](#output\_external\_private\_key\_id) | Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource | +| [external\_private\_key\_openssh](#output\_external\_private\_key\_openssh) | Private key data in OpenSSH PEM (RFC 4716) format | +| [external\_private\_key\_pem](#output\_external\_private\_key\_pem) | Private key data in PEM (RFC 1421) format | +| [external\_public\_key\_fingerprint\_md5](#output\_external\_public\_key\_fingerprint\_md5) | The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [external\_public\_key\_fingerprint\_sha256](#output\_external\_public\_key\_fingerprint\_sha256) | The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [external\_public\_key\_openssh](#output\_external\_public\_key\_openssh) | The public key data in "Authorized Keys" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys | +| [external\_public\_key\_pem](#output\_external\_public\_key\_pem) | Public key data in PEM (RFC 1421) format | +| [key\_pair\_arn](#output\_key\_pair\_arn) | The key pair ARN | +| [key\_pair\_fingerprint](#output\_key\_pair\_fingerprint) | The MD5 public key fingerprint as specified in section 4 of RFC 4716 | +| [key\_pair\_id](#output\_key\_pair\_id) | The key pair ID | +| [key\_pair\_name](#output\_key\_pair\_name) | The key pair name | +| [private\_key\_id](#output\_private\_key\_id) | Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource | +| [private\_key\_openssh](#output\_private\_key\_openssh) | Private key data in OpenSSH PEM (RFC 4716) format | +| [private\_key\_pem](#output\_private\_key\_pem) | Private key data in PEM (RFC 1421) format | +| [public\_key\_fingerprint\_md5](#output\_public\_key\_fingerprint\_md5) | The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [public\_key\_fingerprint\_sha256](#output\_public\_key\_fingerprint\_sha256) | The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations | +| [public\_key\_openssh](#output\_public\_key\_openssh) | The public key data in "Authorized Keys" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys | +| [public\_key\_pem](#output\_public\_key\_pem) | Public key data in PEM (RFC 1421) format | + diff --git a/modules/aws-key-pair/examples/complete/main.tf b/modules/aws-key-pair/examples/complete/main.tf new file mode 100644 index 0000000..4bf4041 --- /dev/null +++ b/modules/aws-key-pair/examples/complete/main.tf @@ -0,0 +1,50 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-${replace(basename(path.cwd), "_", "-")}" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-key-pair" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# Key Pair Module +################################################################################ + +module "key_pair" { + source = "../../" + + key_name = local.name + create_private_key = true + + tags = local.tags +} + +module "key_pair_external" { + source = "../../" + + key_name = "${local.name}-external" + public_key = trimspace(tls_private_key.this.public_key_openssh) + + tags = local.tags +} + +module "key_pair_disabled" { + source = "../../" + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +resource "tls_private_key" "this" { + algorithm = "RSA" +} diff --git a/modules/aws-key-pair/examples/complete/outputs.tf b/modules/aws-key-pair/examples/complete/outputs.tf new file mode 100644 index 0000000..ad47184 --- /dev/null +++ b/modules/aws-key-pair/examples/complete/outputs.tf @@ -0,0 +1,121 @@ +################################################################################ +# Key Pair +################################################################################ + +output "key_pair_id" { + description = "The key pair ID" + value = module.key_pair.key_pair_id +} + +output "key_pair_arn" { + description = "The key pair ARN" + value = module.key_pair.key_pair_arn +} + +output "key_pair_name" { + description = "The key pair name" + value = module.key_pair.key_pair_name +} + +output "key_pair_fingerprint" { + description = "The MD5 public key fingerprint as specified in section 4 of RFC 4716" + value = module.key_pair.key_pair_fingerprint +} + +output "private_key_id" { + description = "Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource" + value = module.key_pair.private_key_id +} + +output "private_key_openssh" { + description = "Private key data in OpenSSH PEM (RFC 4716) format" + value = module.key_pair.private_key_openssh + sensitive = true +} + +output "private_key_pem" { + description = "Private key data in PEM (RFC 1421) format" + value = module.key_pair.private_key_pem + sensitive = true +} + +output "public_key_fingerprint_md5" { + description = "The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = module.key_pair.public_key_fingerprint_md5 +} + +output "public_key_fingerprint_sha256" { + description = "The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = module.key_pair.public_key_fingerprint_sha256 +} + +output "public_key_openssh" { + description = "The public key data in \"Authorized Keys\" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys" + value = module.key_pair.public_key_openssh +} + +output "public_key_pem" { + description = "Public key data in PEM (RFC 1421) format" + value = module.key_pair.public_key_pem +} + +################################################################################ +# Key Pair - External +################################################################################ + +output "external_key_pair_id" { + description = "The key pair ID" + value = module.key_pair_external.key_pair_id +} + +output "external_key_pair_arn" { + description = "The key pair ARN" + value = module.key_pair_external.key_pair_arn +} + +output "external_key_pair_name" { + description = "The key pair name" + value = module.key_pair_external.key_pair_name +} + +output "external_key_pair_fingerprint" { + description = "The MD5 public key fingerprint as specified in section 4 of RFC 4716" + value = module.key_pair_external.key_pair_fingerprint +} + +output "external_private_key_id" { + description = "Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource" + value = module.key_pair_external.private_key_id +} + +output "external_private_key_openssh" { + description = "Private key data in OpenSSH PEM (RFC 4716) format" + value = module.key_pair_external.private_key_openssh + sensitive = true +} + +output "external_private_key_pem" { + description = "Private key data in PEM (RFC 1421) format" + value = module.key_pair_external.private_key_pem + sensitive = true +} + +output "external_public_key_fingerprint_md5" { + description = "The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = module.key_pair_external.public_key_fingerprint_md5 +} + +output "external_public_key_fingerprint_sha256" { + description = "The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = module.key_pair_external.public_key_fingerprint_sha256 +} + +output "external_public_key_openssh" { + description = "The public key data in \"Authorized Keys\" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys" + value = module.key_pair_external.public_key_openssh +} + +output "external_public_key_pem" { + description = "Public key data in PEM (RFC 1421) format" + value = module.key_pair_external.public_key_pem +} diff --git a/modules/aws-key-pair/examples/complete/variables.tf b/modules/aws-key-pair/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-key-pair/examples/complete/versions.tf b/modules/aws-key-pair/examples/complete/versions.tf new file mode 100644 index 0000000..c2da4bc --- /dev/null +++ b/modules/aws-key-pair/examples/complete/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.21" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.4" + } + } +} diff --git a/modules/aws-key-pair/main.tf b/modules/aws-key-pair/main.tf new file mode 100644 index 0000000..014f1a1 --- /dev/null +++ b/modules/aws-key-pair/main.tf @@ -0,0 +1,24 @@ +################################################################################ +# Key Pair +################################################################################ + +resource "aws_key_pair" "this" { + count = var.create ? 1 : 0 + + key_name = var.key_name + key_name_prefix = var.key_name_prefix + public_key = var.create_private_key ? trimspace(tls_private_key.this[0].public_key_openssh) : var.public_key + + tags = var.tags +} + +################################################################################ +# Private Key +################################################################################ + +resource "tls_private_key" "this" { + count = var.create && var.create_private_key ? 1 : 0 + + algorithm = var.private_key_algorithm + rsa_bits = var.private_key_rsa_bits +} diff --git a/modules/aws-key-pair/outputs.tf b/modules/aws-key-pair/outputs.tf new file mode 100644 index 0000000..fdbc360 --- /dev/null +++ b/modules/aws-key-pair/outputs.tf @@ -0,0 +1,64 @@ +################################################################################ +# Key Pair +################################################################################ + +output "key_pair_id" { + description = "The key pair ID" + value = try(aws_key_pair.this[0].key_pair_id, "") +} + +output "key_pair_arn" { + description = "The key pair ARN" + value = try(aws_key_pair.this[0].arn, "") +} + +output "key_pair_name" { + description = "The key pair name" + value = try(aws_key_pair.this[0].key_name, "") +} + +output "key_pair_fingerprint" { + description = "The MD5 public key fingerprint as specified in section 4 of RFC 4716" + value = try(aws_key_pair.this[0].fingerprint, "") +} + +################################################################################ +# Private Key +################################################################################ + +output "private_key_id" { + description = "Unique identifier for this resource: hexadecimal representation of the SHA1 checksum of the resource" + value = try(tls_private_key.this[0].id, "") +} + +output "private_key_openssh" { + description = "Private key data in OpenSSH PEM (RFC 4716) format" + value = try(trimspace(tls_private_key.this[0].private_key_openssh), "") + sensitive = true +} + +output "private_key_pem" { + description = "Private key data in PEM (RFC 1421) format" + value = try(trimspace(tls_private_key.this[0].private_key_pem), "") + sensitive = true +} + +output "public_key_fingerprint_md5" { + description = "The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = try(tls_private_key.this[0].public_key_fingerprint_md5, "") +} + +output "public_key_fingerprint_sha256" { + description = "The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:....` Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the ECDSA P224 limitations" + value = try(tls_private_key.this[0].public_key_fingerprint_sha256, "") +} + +output "public_key_openssh" { + description = "The public key data in \"Authorized Keys\" format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys" + value = try(trimspace(tls_private_key.this[0].public_key_openssh), "") +} + +output "public_key_pem" { + description = "Public key data in PEM (RFC 1421) format" + value = try(trimspace(tls_private_key.this[0].public_key_pem), "") +} diff --git a/modules/aws-key-pair/variables.tf b/modules/aws-key-pair/variables.tf new file mode 100644 index 0000000..501b3a5 --- /dev/null +++ b/modules/aws-key-pair/variables.tf @@ -0,0 +1,55 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Key Pair +################################################################################ + +variable "key_name" { + description = "The name for the key pair. Conflicts with `key_name_prefix`" + type = string + default = null +} + +variable "key_name_prefix" { + description = "Creates a unique name beginning with the specified prefix. Conflicts with `key_name`" + type = string + default = null +} + +variable "public_key" { + description = "The public key material" + type = string + default = "" +} + +################################################################################ +# Private Key +################################################################################ + +variable "create_private_key" { + description = "Determines whether a private key will be created" + type = bool + default = false +} + +variable "private_key_algorithm" { + description = "Name of the algorithm to use when generating the private key. Currently-supported values are `RSA` and `ED25519`" + type = string + default = "RSA" +} + +variable "private_key_rsa_bits" { + description = "When algorithm is `RSA`, the size of the generated RSA key, in bits (default: `4096`)" + type = number + default = 4096 +} diff --git a/modules/aws-key-pair/versions.tf b/modules/aws-key-pair/versions.tf new file mode 100644 index 0000000..c2da4bc --- /dev/null +++ b/modules/aws-key-pair/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.21" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.4" + } + } +} diff --git a/modules/aws-kms/README.md b/modules/aws-kms/README.md new file mode 100644 index 0000000..bdd147e --- /dev/null +++ b/modules/aws-kms/README.md @@ -0,0 +1,217 @@ +# AWS KMS Terraform module + +Terraform module which creates AWS KMS resources. + + + +## Usage + + +### Service + +Reference usage for EC2 AutoScaling service linked role to launch encrypted EBS volumes: + +```hcl +module "kms" { + source = "platform-modules/kms/aws" + + description = "EC2 AutoScaling key usage" + key_usage = "ENCRYPT_DECRYPT" + + # Policy + key_administrators = ["arn:aws:iam::012345678901:role/admin"] + key_users = ["arn:aws:iam::012345678901:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"] + key_service_users = ["arn:aws:iam::012345678901:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"] + + # Aliases + aliases = ["mycompany/ebs"] + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +### External Key + +Reference usage for external CMK (externally provided encryption material): + +```hcl +module "kms" { + source = "platform/kms/aws" + + description = "External key example" + key_material_base64 = "Wblj06fduthWggmsT0cLVoIMOkeLbc2kVfMud77i/JY=" + valid_to = "2085-04-12T23:20:50.52Z" + + # Policy + key_owners = ["arn:aws:iam::012345678901:role/owner"] + key_administrators = ["arn:aws:iam::012345678901:role/admin"] + key_users = ["arn:aws:iam::012345678901:role/user"] + key_service_users = ["arn:aws:iam::012345678901:role/ec2-role"] + + # Aliases + aliases = ["mycompany/external"] + aliases_use_name_prefix = true + + # Grants + grants = { + lambda = { + grantee_principal = "arn:aws:iam::012345678901:role/lambda-function" + operations = ["Encrypt", "Decrypt", "GenerateDataKey"] + constraints = { + encryption_context_equals = { + Department = "Finance" + } + } + } + } + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + +### Reference + +Reference usage showing available configurations. + +```hcl +module "kms" { + source = "platform/kms/aws" + + description = "Complete key example showing various configurations available" + deletion_window_in_days = 7 + enable_key_rotation = true + is_enabled = true + key_usage = "ENCRYPT_DECRYPT" + multi_region = false + + # Policy + enable_default_policy = true + key_owners = ["arn:aws:iam::012345678901:role/owner"] + key_administrators = ["arn:aws:iam::012345678901:role/admin"] + key_users = ["arn:aws:iam::012345678901:role/user"] + key_service_users = ["arn:aws:iam::012345678901:role/ec2-role"] + key_symmetric_encryption_users = ["arn:aws:iam::012345678901:role/symmetric-user"] + key_hmac_users = ["arn:aws:iam::012345678901:role/hmac-user"] + key_asymmetric_public_encryption_users = ["arn:aws:iam::012345678901:role/asymmetric-public-user"] + key_asymmetric_sign_verify_users = ["arn:aws:iam::012345678901:role/sign-verify-user"] + + # Aliases + aliases = ["one", "foo/bar"] # accepts static strings only + computed_aliases = { + ex = { + # Sometimes you want to pass in an upstream attribute as the name and + # that conflicts with using `for_each over a `toset()` since the value is not + # known until after applying. Instead, we can use `computed_aliases` to work + # around this limitation + # Reference: https://github.com/hashicorp/terraform/issues/30937 + name = aws_iam_role.lambda.name + } + } + aliases_use_name_prefix = true + + # Grants + grants = { + lambda = { + grantee_principal = "arn:aws:iam::012345678901:role/lambda-function" + operations = ["Encrypt", "Decrypt", "GenerateDataKey"] + constraints = { + encryption_context_equals = { + Department = "Finance" + } + } + } + } + + tags = { + Terraform = "true" + Environment = "dev" + } +} +``` + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.72 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.72 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | +| [aws_kms_external_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_external_key) | resource | +| [aws_kms_grant.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_grant) | resource | +| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aliases](#input\_aliases) | A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values | `list(string)` | `[]` | no | +| [aliases\_use\_name\_prefix](#input\_aliases\_use\_name\_prefix) | Determines whether the alias name is used as a prefix | `bool` | `false` | no | +| [bypass\_policy\_lockout\_safety\_check](#input\_bypass\_policy\_lockout\_safety\_check) | A flag to indicate whether to bypass the key policy lockout safety check. Setting this value to true increases the risk that the KMS key becomes unmanageable | `bool` | `null` | no | +| [computed\_aliases](#input\_computed\_aliases) | A map of aliases to create. Values provided via the `name` key of the map can be computed from upstream resources | `any` | `{}` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_external](#input\_create\_external) | Determines whether an external CMK (externally provided material) will be created or a standard CMK (AWS provided material) | `bool` | `false` | no | +| [customer\_master\_key\_spec](#input\_customer\_master\_key\_spec) | Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `HMAC_256`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`. Defaults to `SYMMETRIC_DEFAULT` | `string` | `null` | no | +| [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30` | `number` | `null` | no | +| [description](#input\_description) | The description of the key as viewed in AWS console | `string` | `null` | no | +| [enable\_default\_policy](#input\_enable\_default\_policy) | Specifies whether to enable the default key policy. Defaults to `true` | `bool` | `true` | no | +| [enable\_key\_rotation](#input\_enable\_key\_rotation) | Specifies whether key rotation is enabled. Defaults to `true` | `bool` | `true` | no | +| [grants](#input\_grants) | A map of grant definitions to create | `any` | `{}` | no | +| [is\_enabled](#input\_is\_enabled) | Specifies whether the key is enabled. Defaults to `true` | `bool` | `null` | no | +| [key\_administrators](#input\_key\_administrators) | A list of IAM ARNs for [key administrators](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators) | `list(string)` | `[]` | no | +| [key\_asymmetric\_public\_encryption\_users](#input\_key\_asymmetric\_public\_encryption\_users) | A list of IAM ARNs for [key asymmetric public encryption users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto) | `list(string)` | `[]` | no | +| [key\_asymmetric\_sign\_verify\_users](#input\_key\_asymmetric\_sign\_verify\_users) | A list of IAM ARNs for [key asymmetric sign and verify users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto) | `list(string)` | `[]` | no | +| [key\_hmac\_users](#input\_key\_hmac\_users) | A list of IAM ARNs for [key HMAC users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto) | `list(string)` | `[]` | no | +| [key\_material\_base64](#input\_key\_material\_base64) | Base64 encoded 256-bit symmetric encryption key material to import. The CMK is permanently associated with this key material. External key only | `string` | `null` | no | +| [key\_owners](#input\_key\_owners) | A list of IAM ARNs for those who will have full key permissions (`kms:*`) | `list(string)` | `[]` | no | +| [key\_service\_users](#input\_key\_service\_users) | A list of IAM ARNs for [key service users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration) | `list(string)` | `[]` | no | +| [key\_symmetric\_encryption\_users](#input\_key\_symmetric\_encryption\_users) | A list of IAM ARNs for [key symmetric encryption users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto) | `list(string)` | `[]` | no | +| [key\_usage](#input\_key\_usage) | Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`. Defaults to `ENCRYPT_DECRYPT` | `string` | `null` | no | +| [key\_users](#input\_key\_users) | A list of IAM ARNs for [key users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users) | `list(string)` | `[]` | no | +| [multi\_region](#input\_multi\_region) | Indicates whether the KMS key is a multi-Region (`true`) or regional (`false`) key. Defaults to `false` | `bool` | `false` | no | +| [override\_policy\_documents](#input\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [policy](#input\_policy) | A valid policy JSON document. Although this is a key policy, not an IAM policy, an `aws_iam_policy_document`, in the form that designates a principal, can be used | `string` | `null` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [valid\_to](#input\_valid\_to) | Time at which the imported key material expires. When the key material expires, AWS KMS deletes the key material and the CMK becomes unusable. If not specified, key material does not expire | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [aliases](#output\_aliases) | A map of aliases created and their attributes | +| [external\_key\_expiration\_model](#output\_external\_key\_expiration\_model) | Whether the key material expires. Empty when pending key material import, otherwise `KEY_MATERIAL_EXPIRES` or `KEY_MATERIAL_DOES_NOT_EXPIRE` | +| [external\_key\_state](#output\_external\_key\_state) | The state of the CMK | +| [external\_key\_usage](#output\_external\_key\_usage) | The cryptographic operations for which you can use the CMK | +| [grants](#output\_grants) | A map of grants created and their attributes | +| [key\_arn](#output\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [key\_id](#output\_key\_id) | The globally unique identifier for the key | +| [key\_policy](#output\_key\_policy) | The IAM resource policy set on the key | + + + diff --git a/modules/aws-kms/main.tf b/modules/aws-kms/main.tf new file mode 100644 index 0000000..3972a4e --- /dev/null +++ b/modules/aws-kms/main.tf @@ -0,0 +1,512 @@ +data "aws_partition" "current" { + count = var.create ? 1 : 0 +} +data "aws_caller_identity" "current" { + count = var.create ? 1 : 0 +} + +locals { + account_id = try(data.aws_caller_identity.current[0].account_id, "") + partition = try(data.aws_partition.current[0].partition, "") + dns_suffix = try(data.aws_partition.current[0].dns_suffix, "") +} + +################################################################################ +# Key +################################################################################ + +resource "aws_kms_key" "this" { + count = var.create && !var.create_external && !var.create_replica && !var.create_replica_external ? 1 : 0 + + bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check + customer_master_key_spec = var.customer_master_key_spec + custom_key_store_id = var.custom_key_store_id + deletion_window_in_days = var.deletion_window_in_days + description = var.description + enable_key_rotation = var.enable_key_rotation + is_enabled = var.is_enabled + key_usage = var.key_usage + multi_region = var.multi_region + policy = coalesce(var.policy, data.aws_iam_policy_document.this[0].json) + + tags = var.tags +} + +################################################################################ +# Public Key +################################################################################ + +data "aws_kms_public_key" "by_id" { + count = var.create && !var.create_external && var.create_public_key ? 1 : 0 + key_id = aws_kms_key.this[0].id +} + +################################################################################ +# External Key +################################################################################ + +resource "aws_kms_external_key" "this" { + count = var.create && var.create_external && !var.create_replica && !var.create_replica_external ? 1 : 0 + + bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check + deletion_window_in_days = var.deletion_window_in_days + description = var.description + enabled = var.is_enabled + key_material_base64 = var.key_material_base64 + multi_region = var.multi_region + policy = coalesce(var.policy, data.aws_iam_policy_document.this[0].json) + valid_to = var.valid_to + + tags = var.tags +} + +################################################################################ +# Replica Key +################################################################################ + +resource "aws_kms_replica_key" "this" { + count = var.create && var.create_replica && !var.create_external && !var.create_replica_external ? 1 : 0 + + bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check + deletion_window_in_days = var.deletion_window_in_days + description = var.description + primary_key_arn = var.primary_key_arn + enabled = var.is_enabled + policy = coalesce(var.policy, data.aws_iam_policy_document.this[0].json) + + tags = var.tags +} + +################################################################################ +# Replica External Key +################################################################################ + +resource "aws_kms_replica_external_key" "this" { + count = var.create && !var.create_replica && !var.create_external && var.create_replica_external ? 1 : 0 + + bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check + deletion_window_in_days = var.deletion_window_in_days + description = var.description + enabled = var.is_enabled + key_material_base64 = var.key_material_base64 + policy = coalesce(var.policy, data.aws_iam_policy_document.this[0].json) + primary_key_arn = var.primary_external_key_arn + valid_to = var.valid_to + + tags = var.tags +} + +################################################################################ +# Policy +################################################################################ + +data "aws_iam_policy_document" "this" { + count = var.create ? 1 : 0 + + source_policy_documents = var.source_policy_documents + override_policy_documents = var.override_policy_documents + + # Default policy - account wide access to all key operations + dynamic "statement" { + for_each = var.enable_default_policy ? [1] : [] + + content { + sid = "Default" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = ["arn:${local.partition}:iam::${local.account_id}:root"] + } + } + } + + # Key owner - all key operations + dynamic "statement" { + for_each = length(var.key_owners) > 0 ? [1] : [] + + content { + sid = "KeyOwner" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_owners + } + } + } + + # Key administrators - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators + dynamic "statement" { + for_each = length(var.key_administrators) > 0 ? [1] : [] + + content { + sid = "KeyAdministration" + actions = [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:TagResource", + "kms:UntagResource", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:ReplicateKey", + "kms:ImportKeyMaterial" + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_administrators + } + } + } + + # Key users - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users + dynamic "statement" { + for_each = length(var.key_users) > 0 ? [1] : [] + + content { + sid = "KeyUsage" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_users + } + } + } + + # Key service users - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration + dynamic "statement" { + for_each = length(var.key_service_users) > 0 ? [1] : [] + + content { + sid = "KeyServiceUsage" + actions = [ + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_service_users + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = [true] + } + } + } + + # Key service roles for autoscaling - https://docs.aws.amazon.com/autoscaling/ec2/userguide/key-policy-requirements-EBS-encryption.html#policy-example-cmk-access + dynamic "statement" { + for_each = length(var.key_service_roles_for_autoscaling) > 0 ? [1] : [] + + content { + sid = "KeyServiceRolesASG" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_service_roles_for_autoscaling + } + } + } + + dynamic "statement" { + for_each = length(var.key_service_roles_for_autoscaling) > 0 ? [1] : [] + + content { + sid = "KeyServiceRolesASGPersistentVol" + actions = [ + "kms:CreateGrant" + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_service_roles_for_autoscaling + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = [true] + } + } + } + + # Key cryptographic operations - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto + dynamic "statement" { + for_each = length(var.key_symmetric_encryption_users) > 0 ? [1] : [] + + content { + sid = "KeySymmetricEncryption" + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_symmetric_encryption_users + } + } + } + + dynamic "statement" { + for_each = length(var.key_hmac_users) > 0 ? [1] : [] + + content { + sid = "KeyHMAC" + actions = [ + "kms:DescribeKey", + "kms:GenerateMac", + "kms:VerifyMac", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_hmac_users + } + } + } + + dynamic "statement" { + for_each = length(var.key_asymmetric_public_encryption_users) > 0 ? [1] : [] + + content { + sid = "KeyAsymmetricPublicEncryption" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:DescribeKey", + "kms:GetPublicKey", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_asymmetric_public_encryption_users + } + } + } + + dynamic "statement" { + for_each = length(var.key_asymmetric_sign_verify_users) > 0 ? [1] : [] + + content { + sid = "KeyAsymmetricSignVerify" + actions = [ + "kms:DescribeKey", + "kms:GetPublicKey", + "kms:Sign", + "kms:Verify", + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = var.key_asymmetric_sign_verify_users + } + } + } + + # https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-managing-permissions.html#KMS-key-policy-for-DNSSEC + dynamic "statement" { + for_each = var.enable_route53_dnssec ? [1] : [] + + content { + sid = "Route53DnssecService" + actions = [ + "kms:DescribeKey", + "kms:GetPublicKey", + "kms:Sign", + ] + resources = ["*"] + + principals { + type = "Service" + identifiers = ["dnssec-route53.${local.dns_suffix}"] + } + } + } + + dynamic "statement" { + for_each = length(var.key_services) > 0 ? [1] : [] + content { + sid = "KeyService" + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*", + ] + resources = ["*"] + principals { + type = "Service" + identifiers = var.key_services + } + } + } + + # https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-managing-permissions.html#KMS-key-policy-for-DNSSEC + dynamic "statement" { + for_each = var.enable_route53_dnssec ? [1] : [] + + content { + sid = "Route53DnssecGrant" + actions = ["kms:CreateGrant"] + resources = ["*"] + + principals { + type = "Service" + identifiers = ["dnssec-route53.${local.dns_suffix}"] + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = ["true"] + } + + dynamic "condition" { + for_each = var.route53_dnssec_sources + + content { + test = "StringEquals" + variable = "aws:SourceAccount" + values = try(condition.value.account_ids, [local.account_id]) + } + } + + dynamic "condition" { + for_each = var.route53_dnssec_sources + + content { + test = "ArnLike" + variable = "aws:SourceArn" + values = [try(condition.value.hosted_zone_arn, "arn:${local.partition}:route53:::hostedzone/*")] + } + } + } + } + + dynamic "statement" { + for_each = var.key_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +################################################################################ +# Alias +################################################################################ + +locals { + aliases = { for k, v in toset(var.aliases) : k => { name = v } } +} + +resource "aws_kms_alias" "this" { + for_each = { for k, v in merge(local.aliases, var.computed_aliases) : k => v if var.create } + + name = var.aliases_use_name_prefix ? null : "alias/${each.value.name}" + name_prefix = var.aliases_use_name_prefix ? "alias/${each.value.name}-" : null + target_key_id = try(aws_kms_key.this[0].key_id, aws_kms_external_key.this[0].id, aws_kms_replica_key.this[0].key_id, aws_kms_replica_external_key.this[0].key_id) +} + +################################################################################ +# Grant +################################################################################ + +resource "aws_kms_grant" "this" { + for_each = { for k, v in var.grants : k => v if var.create } + + name = try(each.value.name, each.key) + key_id = try(aws_kms_key.this[0].key_id, aws_kms_external_key.this[0].id, aws_kms_replica_key.this[0].key_id, aws_kms_replica_external_key.this[0].key_id) + grantee_principal = each.value.grantee_principal + operations = each.value.operations + + dynamic "constraints" { + for_each = length(lookup(each.value, "constraints", {})) == 0 ? [] : [each.value.constraints] + + content { + encryption_context_equals = try(constraints.value.encryption_context_equals, null) + encryption_context_subset = try(constraints.value.encryption_context_subset, null) + } + } + + retiring_principal = try(each.value.retiring_principal, null) + grant_creation_tokens = try(each.value.grant_creation_tokens, null) + retire_on_delete = try(each.value.retire_on_delete, null) +} diff --git a/modules/aws-kms/outputs.tf b/modules/aws-kms/outputs.tf new file mode 100644 index 0000000..c4bda08 --- /dev/null +++ b/modules/aws-kms/outputs.tf @@ -0,0 +1,60 @@ +################################################################################ +# Key +################################################################################ + +output "key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = try(aws_kms_key.this[0].arn, aws_kms_external_key.this[0].arn, aws_kms_replica_key.this[0].arn, aws_kms_replica_external_key.this[0].arn, null) +} + +output "key_id" { + description = "The globally unique identifier for the key" + value = try(aws_kms_key.this[0].key_id, aws_kms_external_key.this[0].id, aws_kms_replica_key.this[0].key_id, aws_kms_replica_external_key.this[0].key_id, null) +} + +output "key_policy" { + description = "The IAM resource policy set on the key" + value = try(aws_kms_key.this[0].policy, aws_kms_external_key.this[0].policy, aws_kms_replica_key.this[0].policy, aws_kms_replica_external_key.this[0].policy, null) +} + +output "external_key_expiration_model" { + description = "Whether the key material expires. Empty when pending key material import, otherwise `KEY_MATERIAL_EXPIRES` or `KEY_MATERIAL_DOES_NOT_EXPIRE`" + value = try(aws_kms_external_key.this[0].expiration_model, aws_kms_replica_external_key.this[0].expiration_model, null) +} + +output "external_key_state" { + description = "The state of the CMK" + value = try(aws_kms_external_key.this[0].key_state, aws_kms_replica_external_key.this[0].key_state, null) +} + +output "external_key_usage" { + description = "The cryptographic operations for which you can use the CMK" + value = try(aws_kms_external_key.this[0].key_usage, aws_kms_replica_external_key.this[0].key_usage, null) +} + +################################################################################ +# Alias +################################################################################ + +output "aliases" { + description = "A map of aliases created and their attributes" + value = aws_kms_alias.this +} + +################################################################################ +# Grant +################################################################################ + +output "grants" { + description = "A map of grants created and their attributes" + value = aws_kms_grant.this +} + +################################################################################ +# Public Key +################################################################################ + +output "public_key" { + value = try(data.aws_kms_public_key.by_id, null) + sensitive = true +} diff --git a/modules/aws-kms/variables.tf b/modules/aws-kms/variables.tf new file mode 100644 index 0000000..cda9b1a --- /dev/null +++ b/modules/aws-kms/variables.tf @@ -0,0 +1,259 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Key +################################################################################ + +variable "create_external" { + description = "Determines whether an external CMK (externally provided material) will be created or a standard CMK (AWS provided material)" + type = bool + default = false +} + +variable "bypass_policy_lockout_safety_check" { + description = "A flag to indicate whether to bypass the key policy lockout safety check. Setting this value to true increases the risk that the KMS key becomes unmanageable" + type = bool + default = null +} + +variable "customer_master_key_spec" { + description = "Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `HMAC_256`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`. Defaults to `SYMMETRIC_DEFAULT`" + type = string + default = null +} + +variable "custom_key_store_id" { + description = "ID of the KMS Custom Key Store where the key will be stored instead of KMS (eg CloudHSM)." + type = string + default = null +} + +variable "deletion_window_in_days" { + description = "The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30`" + type = number + default = null +} + +variable "description" { + description = "The description of the key as viewed in AWS console" + type = string + default = null +} + +variable "enable_key_rotation" { + description = "Specifies whether key rotation is enabled. Defaults to `true`" + type = bool + default = true +} + +variable "is_enabled" { + description = "Specifies whether the key is enabled. Defaults to `true`" + type = bool + default = null +} + +variable "key_material_base64" { + description = "Base64 encoded 256-bit symmetric encryption key material to import. The CMK is permanently associated with this key material. External key only" + type = string + default = null +} + +variable "key_usage" { + description = "Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`. Defaults to `ENCRYPT_DECRYPT`" + type = string + default = null +} + +variable "multi_region" { + description = "Indicates whether the KMS key is a multi-Region (`true`) or regional (`false`) key. Defaults to `false`" + type = bool + default = false +} + +variable "policy" { + description = "A valid policy JSON document. Although this is a key policy, not an IAM policy, an `aws_iam_policy_document`, in the form that designates a principal, can be used" + type = string + default = null +} + +variable "valid_to" { + description = "Time at which the imported key material expires. When the key material expires, AWS KMS deletes the key material and the CMK becomes unusable. If not specified, key material does not expire" + type = string + default = null +} + +variable "enable_default_policy" { + description = "Specifies whether to enable the default key policy. Defaults to `true`" + type = bool + default = true +} + +variable "key_owners" { + description = "A list of IAM ARNs for those who will have full key permissions (`kms:*`)" + type = list(string) + default = [] +} + +variable "key_administrators" { + description = "A list of IAM ARNs for [key administrators](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators)" + type = list(string) + default = [] +} + +variable "key_users" { + description = "A list of IAM ARNs for [key users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users)" + type = list(string) + default = [] +} + +variable "key_service_users" { + description = "A list of IAM ARNs for [key service users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration)" + type = list(string) + default = [] +} + +variable "key_service_roles_for_autoscaling" { + description = "A list of IAM ARNs for [AWSServiceRoleForAutoScaling roles](https://docs.aws.amazon.com/autoscaling/ec2/userguide/key-policy-requirements-EBS-encryption.html#policy-example-cmk-access)" + type = list(string) + default = [] +} + +variable "key_symmetric_encryption_users" { + description = "A list of IAM ARNs for [key symmetric encryption users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto)" + type = list(string) + default = [] +} + +variable "key_hmac_users" { + description = "A list of IAM ARNs for [key HMAC users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto)" + type = list(string) + default = [] +} + +variable "key_asymmetric_public_encryption_users" { + description = "A list of IAM ARNs for [key asymmetric public encryption users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto)" + type = list(string) + default = [] +} + +variable "key_asymmetric_sign_verify_users" { + description = "A list of IAM ARNs for [key asymmetric sign and verify users](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto)" + type = list(string) + default = [] +} + +variable "key_services" { + description = "A list of IAM ARNs for service accounts" + type = list(string) + default = [] +} + +variable "key_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +variable "source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "enable_route53_dnssec" { + description = "Determines whether the KMS policy used for Route53 DNSSEC signing is enabled" + type = bool + default = false +} + +variable "route53_dnssec_sources" { + description = "A list of maps containing `account_ids` and Route53 `hosted_zone_arn` that will be allowed to sign DNSSEC records" + type = list(any) + default = [] +} + +################################################################################ +# Replica Key +################################################################################ + +variable "create_replica" { + description = "Determines whether a replica standard CMK will be created (AWS provided material)" + type = bool + default = false +} + +variable "primary_key_arn" { + description = "The primary key arn of a multi-region replica key" + type = string + default = null +} + +################################################################################ +# Replica External Key +################################################################################ + +variable "create_replica_external" { + description = "Determines whether a replica external CMK will be created (externally provided material)" + type = bool + default = false +} + +variable "primary_external_key_arn" { + description = "The primary external key arn of a multi-region replica external key" + type = string + default = null +} + +################################################################################ +# Alias +################################################################################ + +variable "aliases" { + description = "A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values" + type = list(string) + default = [] +} + +variable "computed_aliases" { + description = "A map of aliases to create. Values provided via the `name` key of the map can be computed from upstream resources" + type = any + default = {} +} + +variable "aliases_use_name_prefix" { + description = "Determines whether the alias name is used as a prefix" + type = bool + default = false +} + +################################################################################ +# Grant +################################################################################ + +variable "grants" { + description = "A map of grant definitions to create" + type = any + default = {} +} + +variable "create_public_key" { + description = "Determines whether output public key of kms" + type = bool + default = false +} diff --git a/modules/aws-kms/versions.tf b/modules/aws-kms/versions.tf new file mode 100644 index 0000000..f70bc5e --- /dev/null +++ b/modules/aws-kms/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0, < 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.33" + } + } +} diff --git a/modules/aws-lambda/README.md b/modules/aws-lambda/README.md new file mode 100644 index 0000000..e0a1c22 --- /dev/null +++ b/modules/aws-lambda/README.md @@ -0,0 +1,810 @@ +# AWS Lambda Terraform module + +Terraform module, which creates almost all supported AWS Lambda resources as well as taking care of building and packaging of required Lambda dependencies for functions and layers. + +This Terraform module is the part of [serverless.tf framework](https://github.com/antonbabenko/serverless.tf), which aims to simplify all operations when working with the serverless in Terraform: + +1. Build and install dependencies - [read more](#build). Requires Python 3.6 or newer. +2. Create, store, and use deployment packages - [read more](#package). +3. Create, update, and publish AWS Lambda Function and Lambda Layer - [see usage](#usage). +4. Create static and dynamic aliases for AWS Lambda Function - [see usage](#usage), see [modules/alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/alias). +5. Do complex deployments (eg, rolling, canary, rollbacks, triggers) - [read more](#deployment), see [modules/deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/deploy). + +## Features + +- Build dependencies for your Lambda Function and Layer. +- Support builds locally and in Docker (with or without SSH agent support for private builds). +- Create deployment package or deploy existing (previously built package) from local, from S3, from URL, or from AWS ECR repository. +- Store deployment packages locally or in the S3 bucket. +- Support almost all features of Lambda resources (function, layer, alias, etc.) +- Lambda@Edge +- Conditional creation for many types of resources. +- Control execution of nearly any step in the process - build, package, store package, deploy, update. +- Control nearly all aspects of Lambda resources (provisioned concurrency, VPC, EFS, dead-letter notification, tracing, async events, event source mapping, IAM role, IAM policies, and more). +- Support integration with other `serverless.tf` modules like [HTTP API Gateway](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2) (see [examples there](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2/tree/master/examples/complete-http)). + +## Usage + +### Lambda Function (store package locally) + +```hcl +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda1" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + source_path = "../src/lambda-function1" + + tags = { + Name = "my-lambda1" + } +} +``` + +### Lambda Function and Lambda Layer (store packages on S3) + +```hcl +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "lambda-with-layer" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + publish = true + + source_path = "../src/lambda-function1" + + store_on_s3 = true + s3_bucket = "my-bucket-id-with-lambda-builds" + + layers = [ + module.lambda_layer_s3.lambda_layer_arn, + ] + + environment_variables = { + Serverless = "Terraform" + } + + tags = { + Module = "lambda-with-layer" + } +} + +module "lambda_layer_s3" { + source = "terraform-aws-modules/lambda/aws" + + create_layer = true + + layer_name = "lambda-layer-s3" + description = "My amazing lambda layer (deployed from S3)" + compatible_runtimes = ["python3.8"] + + source_path = "../src/lambda-layer" + + store_on_s3 = true + s3_bucket = "my-bucket-id-with-lambda-builds" +} +``` + +### Lambda Functions with existing package (prebuilt) stored locally + +```hcl +module "lambda_function_existing_package_local" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + create_package = false + local_existing_package = "../existing_package.zip" +} +``` + +### Lambda Function or Lambda Layer with the deployable artifact maintained separately from the infrastructure + +If you want to manage function code and infrastructure resources (such as IAM permissions, policies, events, etc) in separate flows (e.g., different repositories, teams, CI/CD pipelines). + +Disable source code tracking to turn off deployments (and rollbacks) using the module by setting `ignore_source_code_hash = true` and deploy a _dummy function_. + +When the infrastructure and the dummy function is deployed, you can use external tool to update the source code of the function (eg, using [AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html)) and keep using this module via Terraform to manage the infrastructure. + +Be aware that changes in `local_existing_package` value may trigger deployment via Terraform. + +```hcl +module "lambda_function_externally_managed_package" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-externally-managed-package" + description = "My lambda function code is deployed separately" + handler = "index.lambda_handler" + runtime = "python3.8" + + create_package = false + local_existing_package = "./lambda_functions/code.zip" + + ignore_source_code_hash = true +} +``` + +### Lambda Function with existing package (prebuilt) stored in S3 bucket + +Note that this module does not copy prebuilt packages into S3 bucket. This module can only store packages it builds locally and in S3 bucket. + +```hcl +locals { + my_function_source = "../path/to/package.zip" +} + +resource "aws_s3_bucket" "builds" { + bucket = "my-builds" + acl = "private" +} + +resource "aws_s3_object" "my_function" { + bucket = aws_s3_bucket.builds.id + key = "${filemd5(local.my_function_source)}.zip" + source = local.my_function_source +} + +module "lambda_function_existing_package_s3" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + create_package = false + s3_existing_package = { + bucket = aws_s3_bucket.builds.id + key = aws_s3_object.my_function.id + } +} +``` + +### Lambda Functions from Container Image stored on AWS ECR + +```hcl +module "lambda_function_container_image" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + + create_package = false + + image_uri = "132367819851.dkr.ecr.eu-west-1.amazonaws.com/complete-cow:1.0" + package_type = "Image" +} +``` + +### Lambda Layers (store packages locally and on S3) + +```hcl +module "lambda_layer_local" { + source = "terraform-aws-modules/lambda/aws" + + create_layer = true + + layer_name = "my-layer-local" + description = "My amazing lambda layer (deployed from local)" + compatible_runtimes = ["python3.8"] + + source_path = "../fixtures/python3.8-app1" +} + +module "lambda_layer_s3" { + source = "terraform-aws-modules/lambda/aws" + + create_layer = true + + layer_name = "my-layer-s3" + description = "My amazing lambda layer (deployed from S3)" + compatible_runtimes = ["python3.8"] + + source_path = "../fixtures/python3.8-app1" + + store_on_s3 = true + s3_bucket = "my-bucket-id-with-lambda-builds" +} +``` + +### Lambda@Edge + +Make sure, you deploy Lambda@Edge functions into US East (N. Virginia) region (`us-east-1`). See [Requirements and Restrictions on Lambda Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers). + +```hcl +module "lambda_at_edge" { + source = "terraform-aws-modules/lambda/aws" + + lambda_at_edge = true + + function_name = "my-lambda-at-edge" + description = "My awesome lambda@edge function" + handler = "index.lambda_handler" + runtime = "python3.8" + + source_path = "../fixtures/python3.8-app1" + + tags = { + Module = "lambda-at-edge" + } +} +``` + +### Lambda Function in VPC + +```hcl +module "lambda_function_in_vpc" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-in-vpc" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + source_path = "../fixtures/python3.8-app1" + + vpc_subnet_ids = module.vpc.intra_subnets + vpc_security_group_ids = [module.vpc.default_security_group_id] + attach_network_policy = true +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + name = "my-vpc" + cidr = "10.10.0.0/16" + + # Specify at least one of: intra_subnets, private_subnets, or public_subnets + azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] + intra_subnets = ["10.10.101.0/24", "10.10.102.0/24", "10.10.103.0/24"] +} +``` + +## Additional IAM policies for Lambda Functions + +There are 6 supported ways to attach IAM policies to IAM role used by Lambda Function: + +1. `policy_json` - JSON string or heredoc, when `attach_policy_json = true`. +1. `policy_jsons` - List of JSON strings or heredoc, when `attach_policy_jsons = true` and `number_of_policy_jsons > 0`. +1. `policy` - ARN of existing IAM policy, when `attach_policy = true`. +1. `policies` - List of ARNs of existing IAM policies, when `attach_policies = true` and `number_of_policies > 0`. +1. `policy_statements` - Map of maps to define IAM statements which will be generated as IAM policy. Requires `attach_policy_statements = true`. See `examples/complete` for more information. +1. `assume_role_policy_statements` - Map of maps to define IAM statements which will be generated as IAM policy for assuming Lambda Function role (trust relationship). See `examples/complete` for more information. + +## Lambda Permissions for allowed triggers + +Lambda Permissions should be specified to allow certain resources to invoke Lambda Function. + +```hcl +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + + # ...omitted for brevity + + allowed_triggers = { + APIGatewayAny = { + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:012345678901:aqnku8akd0/*/*/*" + }, + APIGatewayDevPost = { + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:012345678901:aqnku8akd0/dev/POST/*" + }, + OneRule = { + principal = "events.amazonaws.com" + source_arn = "arn:aws:events:eu-west-1:012345678901:rule/RunDaily" + } + } +} +``` + +## Conditional creation + +Sometimes you need to have a way to create resources conditionally but Terraform does not allow usage of `count` inside `module` block, so the solution is to specify `create` arguments. + +```hcl +module "lambda" { + source = "terraform-aws-modules/lambda/aws" + + create = false # to disable all resources + + create_package = false # to control build package process + create_function = false # to control creation of the Lambda Function and related resources + create_layer = false # to control creation of the Lambda Layer and related resources + create_role = false # to control creation of the IAM role and policies required for Lambda Function + + attach_cloudwatch_logs_policy = false + attach_dead_letter_policy = false + attach_network_policy = false + attach_tracing_policy = false + attach_async_event_policy = false + + # ... omitted +} +``` + +## How does building and packaging work? + +This is one of the most complicated part done by the module and normally you don't have to know internals. + +`package.py` is Python script which does it. Make sure, Python 3.6 or newer is installed. The main functions of the script are to generate a filename of zip-archive based on the content of the files, verify if zip-archive has been already created, and create zip-archive only when it is necessary (during `apply`, not `plan`). + +Hash of zip-archive created with the same content of the files is always identical which prevents unnecessary force-updates of the Lambda resources unless content modifies. If you need to have different filenames for the same content you can specify extra string argument `hash_extra`. + +When calling this module multiple times in one execution to create packages with the same `source_path`, zip-archives will be corrupted due to concurrent writes into the same file. There are two solutions - set different values for `hash_extra` to create different archives, or create package once outside (using this module) and then pass `local_existing_package` argument to create other Lambda resources. + +## Debug + +Building and packaging has been historically hard to debug (especially with Terraform), so we made an effort to make it easier for user to see debug info. There are 3 different debug levels: `DEBUG` - to see only what is happening during planning phase and how a zip file content filtering in case of applied patterns, `DEBUG2` - to see more logging output, `DEBUG3` - to see all logging values, `DUMP_ENV` - to see all logging values and env variables (be careful sharing your env variables as they may contain secrets!). + +User can specify debug level like this: + +``` +export TF_LAMBDA_PACKAGE_LOG_LEVEL=DEBUG2 +terraform apply +``` + +User can enable comments in heredoc strings in `patterns` which can be helpful in some situations. To do this set this environment variable: + +``` +export TF_LAMBDA_PACKAGE_PATTERN_COMMENTS=true +terraform apply +``` + +## Build Dependencies + +You can specify `source_path` in a variety of ways to achieve desired flexibility when building deployment packages locally or in Docker. You can use absolute or relative paths. If you have placed terraform files in subdirectories, note that relative paths are specified from the directory where `terraform plan` is run and not the location of your terraform file. + +Note that, when building locally, files are not copying anywhere from the source directories when making packages, we use fast Python regular expressions to find matching files and directories, which makes packaging very fast and easy to understand. + +### Simple build from single directory + +When `source_path` is set to a string, the content of that path will be used to create deployment package as-is: + +`source_path = "src/function1"` + +### Static build from multiple source directories + +When `source_path` is set to a list of directories the content of each will be taken and one archive will be created. + +### Combine various options for extreme flexibility + +This is the most complete way of creating a deployment package from multiple sources with multiple dependencies. This example is showing some of the available options (see [examples/build-package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) for more): + +```hcl +source_path = [ + "src/main-source", + "src/another-source/index.py", + { + path = "src/function1-dep", + patterns = [ + "!.*/.*\\.txt", # Skip all txt files recursively + ] + }, { + path = "src/python3.8-app1", + pip_requirements = true, + pip_tmp_dir = "/tmp/dir/location" + prefix_in_zip = "foo/bar1", + }, { + path = "src/python3.8-app2", + pip_requirements = "requirements-large.txt", + patterns = [ + "!vendor/colorful-0.5.4.dist-info/RECORD", + "!vendor/colorful-.+.dist-info/.*", + "!vendor/colorful/__pycache__/?.*", + ] + }, { + path = "src/nodejs14.x-app1", + npm_requirements = true, + npm_tmp_dir = "/tmp/dir/location" + prefix_in_zip = "foo/bar1", + }, { + path = "src/python3.8-app3", + commands = [ + "npm install", + ":zip" + ], + patterns = [ + "!.*/.*\\.txt", # Skip all txt files recursively + "node_modules/.+", # Include all node_modules + ], + }, { + path = "src/python3.8-app3", + commands = ["go build"], + patterns = < Deployment package - Create or use existing + +By default, this module creates deployment package and uses it to create or update Lambda Function or Lambda Layer. + +Sometimes, you may want to separate build of deployment package (eg, to compile and install dependencies) from the deployment of a package into two separate steps. + +When creating archive locally outside of this module you need to set `create_package = false` and then argument `local_existing_package = "existing_package.zip"`. Alternatively, you may prefer to keep your deployment packages into S3 bucket and provide a reference to them like this: + +```hcl + create_package = false + s3_existing_package = { + bucket = "my-bucket-with-lambda-builds" + key = "existing_package.zip" + } +``` + +### Using deployment package from remote URL + +This can be implemented in two steps: download file locally using CURL, and pass path to deployment package as `local_existing_package` argument. + +```hcl +locals { + package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip" + downloaded = "downloaded_package_${md5(local.package_url)}.zip" +} + +resource "null_resource" "download_package" { + triggers = { + downloaded = local.downloaded + } + + provisioner "local-exec" { + command = "curl -L -o ${local.downloaded} ${local.package_url}" + } +} + +data "null_data_source" "downloaded_package" { + inputs = { + id = null_resource.download_package.id + filename = local.downloaded + } +} + +module "lambda_function_existing_package_from_remote_url" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.8" + + create_package = false + local_existing_package = data.null_data_source.downloaded_package.outputs["filename"] +} +``` + +## How to deploy and manage Lambda Functions? + +### Simple deployments + +Typically, Lambda Function resource updates when source code changes. If `publish = true` is specified a new [Lambda Function version](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html) will also be created. + +Published Lambda Function can be invoked using either by version number or using `$LATEST`. This is the simplest way of deployment which does not required any additional tool or service. + +### Controlled deployments (rolling, canary, rollbacks) + +In order to do controlled deployments (rolling, canary, rollbacks) of Lambda Functions we need to use [Lambda Function aliases](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html). + +In simple terms, Lambda alias is like a pointer to either one version of Lambda Function (when deployment complete), or to two weighted versions of Lambda Function (during rolling or canary deployment). + +One Lambda Function can be used in multiple aliases. Using aliases gives large control of which version deployed when having multiple environments. + +There is [alias module](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/alias), which simplifies working with alias (create, manage configurations, updates, etc). See [examples/alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) for various use-cases how aliases can be configured and used. + +There is [deploy module](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/deploy), which creates required resources to do deployments using AWS CodeDeploy. It also creates the deployment, and wait for completion. See [examples/deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) for complete end-to-end build/update/deploy process. + +## Terraform CI/CD + +Terraform Cloud, Terraform Enterprise, and many other SaaS for running Terraform do not have Python pre-installed on the workers. You will need to provide an [alternative Docker image](https://www.terraform.io/docs/enterprise/install/installer.html#alternative-terraform-worker-image) with Python installed to be able to use this module there. + +## FAQ + +Q1: Why deployment package not recreating every time I change something? Or why deployment package is being recreated every time but content has not been changed? + +> Answer: There can be several reasons related to concurrent executions, or to content hash. Sometimes, changes has happened inside of dependency which is not used in calculating content hash. Or multiple packages are creating at the same time from the same sources. You can force it by setting value of `hash_extra` to distinct values. + +Q2: How to force recreate deployment package? + +> Answer: Delete an existing zip-archive from `builds` directory, or make a change in your source code. If there is no zip-archive for the current content hash, it will be recreated during `terraform apply`. + +Q3: `null_resource.archive[0] must be replaced` + +> Answer: This probably mean that zip-archive has been deployed, but is currently absent locally, and it has to be recreated locally. When you run into this issue during CI/CD process (where workspace is clean) or from multiple workspaces, you can set environment variable `TF_RECREATE_MISSING_LAMBDA_PACKAGE=false` or pass `recreate_missing_package = false` as a parameter to the module and run `terraform apply`. + +Q4: What does this error mean - `"We currently do not support adding policies for $LATEST."` ? + +> Answer: When the Lambda function is created with `publish = true` the new version is automatically increased and a qualified identifier (version number) becomes available and will be used when setting Lambda permissions. +> +> When `publish = false` (default), only unqualified identifier (`$LATEST`) is available which leads to the error. +> +> The solution is to either disable the creation of Lambda permissions for the current version by setting `create_current_version_allowed_triggers = false`, or to enable publish of Lambda function (`publish = true`). + +## Notes + +1. Creation of Lambda Functions and Lambda Layers is very similar and both support the same features (building from source path, using existing package, storing package locally or on S3) +2. Check out this [Awesome list of AWS Lambda Layers](https://github.com/mthenw/awesome-layers) + +## Examples + +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/complete) - Create Lambda resources in various combinations with all supported features. +- [Container Image](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/container-image) - Create a Docker image with a platform specified in the Dockerfile (using [docker provider](https://registry.terraform.io/providers/kreuzwerker/docker)), push it to AWS ECR, and create Lambda function from it. +- [Build and Package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) - Build and create deployment packages in various ways. +- [Alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) - Create static and dynamic aliases in various ways. +- [Deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) - Complete end-to-end build/update/deploy process using AWS CodeDeploy. +- [Async Invocations](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/async) - Create Lambda Function with async event configuration (with SQS, SNS, and EventBridge integration). +- [With VPC](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-vpc) - Create Lambda Function with VPC. +- [With EFS](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-efs) - Create Lambda Function with Elastic File System attached (Terraform 0.13+ is recommended). +- [Multiple regions](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/multiple-regions) - Create the same Lambda Function in multiple regions with non-conflicting IAM roles and policies. +- [Event Source Mapping](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/event-source-mapping) - Create Lambda Function with event source mapping configuration (SQS, DynamoDB, Amazon MQ, and Kinesis). +- [Triggers](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/triggers) - Create Lambda Function with some triggers (eg, Cloudwatch Events, EventBridge). + +# Examples by the users of this module + +- [1Mill/serverless-tf-examples](https://github.com/1Mill/serverless-tf-examples/tree/main/src) + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.9 | +| [external](#requirement\_external) | >= 1.0 | +| [local](#requirement\_local) | >= 1.0 | +| [null](#requirement\_null) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.9 | +| [external](#provider\_external) | >= 1.0 | +| [local](#provider\_local) | >= 1.0 | +| [null](#provider\_null) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_iam_policy.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.additional_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.additional_jsons](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.additional_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.additional_jsons](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.additional_many](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.additional_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_lambda_event_source_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | +| [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | +| [aws_lambda_function_event_invoke_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_event_invoke_config) | resource | +| [aws_lambda_function_url.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_url) | resource | +| [aws_lambda_layer_version.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_layer_version) | resource | +| [aws_lambda_permission.current_version_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_permission.unqualified_alias_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_provisioned_concurrency_config.current_version](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_provisioned_concurrency_config) | resource | +| [aws_s3_object.lambda_package](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | +| [local_file.archive_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | +| [null_resource.archive](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_arn.log_group_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudwatch_log_group) | data source | +| [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | +| [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | +| [aws_iam_policy_document.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [external_external.archive_prepare](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | +| [architectures](#input\_architectures) | Instruction set architecture for your Lambda function. Valid values are ["x86\_64"] and ["arm64"]. | `list(string)` | `null` | no | +| [artifacts\_dir](#input\_artifacts\_dir) | Directory name where artifacts should be stored | `string` | `"builds"` | no | +| [assume\_role\_policy\_statements](#input\_assume\_role\_policy\_statements) | Map of dynamic policy statements for assuming Lambda Function role (trust relationship) | `any` | `{}` | no | +| [attach\_async\_event\_policy](#input\_attach\_async\_event\_policy) | Controls whether async event policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_cloudwatch\_logs\_policy](#input\_attach\_cloudwatch\_logs\_policy) | Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function | `bool` | `true` | no | +| [attach\_dead\_letter\_policy](#input\_attach\_dead\_letter\_policy) | Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_network\_policy](#input\_attach\_network\_policy) | Controls whether VPC/network policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policies](#input\_attach\_policies) | Controls whether list of policies should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy](#input\_attach\_policy) | Controls whether policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_json](#input\_attach\_policy\_json) | Controls whether policy\_json should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_jsons](#input\_attach\_policy\_jsons) | Controls whether policy\_jsons should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_statements](#input\_attach\_policy\_statements) | Controls whether policy\_statements should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_tracing\_policy](#input\_attach\_tracing\_policy) | Controls whether X-Ray tracing policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [authorization\_type](#input\_authorization\_type) | The type of authentication that the Lambda Function URL uses. Set to 'AWS\_IAM' to restrict access to authenticated IAM users only. Set to 'NONE' to bypass IAM authentication and create a public endpoint. | `string` | `"NONE"` | no | +| [build\_in\_docker](#input\_build\_in\_docker) | Whether to build dependencies in Docker | `bool` | `false` | no | +| [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | +| [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `null` | no | +| [cloudwatch\_logs\_tags](#input\_cloudwatch\_logs\_tags) | A map of tags to assign to the resource. | `map(string)` | `{}` | no | +| [compatible\_architectures](#input\_compatible\_architectures) | A list of Architectures Lambda layer is compatible with. Currently x86\_64 and arm64 can be specified. | `list(string)` | `null` | no | +| [compatible\_runtimes](#input\_compatible\_runtimes) | A list of Runtimes this layer is compatible with. Up to 5 runtimes can be specified. | `list(string)` | `[]` | no | +| [cors](#input\_cors) | CORS settings to be used by the Lambda Function URL | `any` | `{}` | no | +| [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | +| [create\_async\_event\_config](#input\_create\_async\_event\_config) | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | +| [create\_current\_version\_allowed\_triggers](#input\_create\_current\_version\_allowed\_triggers) | Whether to allow triggers on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_current\_version\_async\_event\_config](#input\_create\_current\_version\_async\_event\_config) | Whether to allow async event configuration on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_function](#input\_create\_function) | Controls whether Lambda Function resource should be created | `bool` | `true` | no | +| [create\_lambda\_function\_url](#input\_create\_lambda\_function\_url) | Controls whether the Lambda Function URL resource should be created | `bool` | `false` | no | +| [create\_layer](#input\_create\_layer) | Controls whether Lambda Layer resource should be created | `bool` | `false` | no | +| [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `true` | no | +| [create\_role](#input\_create\_role) | Controls whether IAM role for Lambda Function should be created | `bool` | `true` | no | +| [create\_unqualified\_alias\_allowed\_triggers](#input\_create\_unqualified\_alias\_allowed\_triggers) | Whether to allow triggers on unqualified alias pointing to $LATEST version | `bool` | `true` | no | +| [create\_unqualified\_alias\_async\_event\_config](#input\_create\_unqualified\_alias\_async\_event\_config) | Whether to allow async event configuration on unqualified alias pointing to $LATEST version | `bool` | `true` | no | +| [create\_unqualified\_alias\_lambda\_function\_url](#input\_create\_unqualified\_alias\_lambda\_function\_url) | Whether to use unqualified alias pointing to $LATEST version in Lambda Function URL | `bool` | `true` | no | +| [dead\_letter\_target\_arn](#input\_dead\_letter\_target\_arn) | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | +| [description](#input\_description) | Description of your Lambda Function (or Layer) | `string` | `""` | no | +| [destination\_on\_failure](#input\_destination\_on\_failure) | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | +| [destination\_on\_success](#input\_destination\_on\_success) | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | +| [docker\_build\_root](#input\_docker\_build\_root) | Root dir where to build in Docker | `string` | `""` | no | +| [docker\_file](#input\_docker\_file) | Path to a Dockerfile when building in Docker | `string` | `""` | no | +| [docker\_image](#input\_docker\_image) | Docker image to use for the build | `string` | `""` | no | +| [docker\_pip\_cache](#input\_docker\_pip\_cache) | Whether to mount a shared pip cache folder into docker environment or not | `any` | `null` | no | +| [docker\_with\_ssh\_agent](#input\_docker\_with\_ssh\_agent) | Whether to pass SSH\_AUTH\_SOCK into docker environment or not | `bool` | `false` | no | +| [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | +| [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no | +| [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no | +| [file\_system\_arn](#input\_file\_system\_arn) | The Amazon Resource Name (ARN) of the Amazon EFS Access Point that provides access to the file system. | `string` | `null` | no | +| [file\_system\_local\_mount\_path](#input\_file\_system\_local\_mount\_path) | The path where the function can access the file system, starting with /mnt/. | `string` | `null` | no | +| [function\_name](#input\_function\_name) | A unique name for your Lambda Function | `string` | `""` | no | +| [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `""` | no | +| [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | +| [ignore\_source\_code\_hash](#input\_ignore\_source\_code\_hash) | Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately. | `bool` | `false` | no | +| [image\_config\_command](#input\_image\_config\_command) | The CMD for the docker image | `list(string)` | `[]` | no | +| [image\_config\_entry\_point](#input\_image\_config\_entry\_point) | The ENTRYPOINT for the docker image | `list(string)` | `[]` | no | +| [image\_config\_working\_directory](#input\_image\_config\_working\_directory) | The working directory for the docker image | `string` | `null` | no | +| [image\_uri](#input\_image\_uri) | The ECR image URI containing the function's deployment package. | `string` | `null` | no | +| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no | +| [lambda\_at\_edge](#input\_lambda\_at\_edge) | Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function | `bool` | `false` | no | +| [lambda\_role](#input\_lambda\_role) | IAM role ARN attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See Lambda Permission Model for more details. | `string` | `""` | no | +| [layer\_name](#input\_layer\_name) | Name of Lambda Layer to create | `string` | `""` | no | +| [layer\_skip\_destroy](#input\_layer\_skip\_destroy) | Whether to retain the old version of a previously deployed Lambda Layer. | `bool` | `false` | no | +| [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `null` | no | +| [license\_info](#input\_license\_info) | License info for your Lambda Layer. Eg, MIT or full url of a license. | `string` | `""` | no | +| [local\_existing\_package](#input\_local\_existing\_package) | The absolute path to an existing zip-file to use | `string` | `null` | no | +| [maximum\_event\_age\_in\_seconds](#input\_maximum\_event\_age\_in\_seconds) | Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600. | `number` | `null` | no | +| [maximum\_retry\_attempts](#input\_maximum\_retry\_attempts) | Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2. | `number` | `null` | no | +| [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. Valid value between 128 MB to 10,240 MB (10 GB), in 64 MB increments. | `number` | `128` | no | +| [number\_of\_policies](#input\_number\_of\_policies) | Number of policies to attach to IAM role for Lambda Function | `number` | `0` | no | +| [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role for Lambda Function | `number` | `0` | no | +| [package\_type](#input\_package\_type) | The Lambda deployment package type. Valid options: Zip or Image | `string` | `"Zip"` | no | +| [policies](#input\_policies) | List of policy statements ARN to attach to Lambda Function role | `list(string)` | `[]` | no | +| [policy](#input\_policy) | An additional policy document ARN to attach to the Lambda Function role | `string` | `null` | no | +| [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to the Lambda Function role | `string` | `null` | no | +| [policy\_jsons](#input\_policy\_jsons) | List of additional policy documents as JSON to attach to Lambda Function role | `list(string)` | `[]` | no | +| [policy\_path](#input\_policy\_path) | Path of policies to that should be added to IAM role for Lambda Function | `string` | `null` | no | +| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to Lambda Function role | `any` | `{}` | no | +| [provisioned\_concurrent\_executions](#input\_provisioned\_concurrent\_executions) | Amount of capacity to allocate. Set to 1 or greater to enable, or set to 0 to disable provisioned concurrency. | `number` | `-1` | no | +| [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no | +| [recreate\_missing\_package](#input\_recreate\_missing\_package) | Whether to recreate missing Lambda package if it is missing locally or not | `bool` | `true` | no | +| [reserved\_concurrent\_executions](#input\_reserved\_concurrent\_executions) | The amount of reserved concurrent executions for this Lambda Function. A value of 0 disables Lambda Function from being triggered and -1 removes any concurrency limitations. Defaults to Unreserved Concurrency Limits -1. | `number` | `-1` | no | +| [role\_description](#input\_role\_description) | Description of IAM role to use for Lambda Function | `string` | `null` | no | +| [role\_force\_detach\_policies](#input\_role\_force\_detach\_policies) | Specifies to force detaching any policies the IAM role has before destroying it. | `bool` | `true` | no | +| [role\_name](#input\_role\_name) | Name of IAM role to use for Lambda Function | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role to use for Lambda Function | `string` | `null` | no | +| [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by Lambda Function | `string` | `null` | no | +| [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | +| [runtime](#input\_runtime) | Lambda Function runtime | `string` | `""` | no | +| [s3\_acl](#input\_s3\_acl) | The canned ACL to apply. Valid values are private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, and bucket-owner-full-control. Defaults to private. | `string` | `"private"` | no | +| [s3\_bucket](#input\_s3\_bucket) | S3 bucket to store artifacts | `string` | `null` | no | +| [s3\_existing\_package](#input\_s3\_existing\_package) | The S3 bucket object with keys bucket, key, version pointing to an existing zip-file to use | `map(string)` | `null` | no | +| [s3\_object\_storage\_class](#input\_s3\_object\_storage\_class) | Specifies the desired Storage Class for the artifact uploaded to S3. Can be either STANDARD, REDUCED\_REDUNDANCY, ONEZONE\_IA, INTELLIGENT\_TIERING, or STANDARD\_IA. | `string` | `"ONEZONE_IA"` | no | +| [s3\_object\_tags](#input\_s3\_object\_tags) | A map of tags to assign to S3 bucket object. | `map(string)` | `{}` | no | +| [s3\_object\_tags\_only](#input\_s3\_object\_tags\_only) | Set to true to not merge tags with s3\_object\_tags. Useful to avoid breaching S3 Object 10 tag limit. | `bool` | `false` | no | +| [s3\_prefix](#input\_s3\_prefix) | Directory name where artifacts should be stored in the S3 bucket. If unset, the path from `artifacts_dir` is used | `string` | `null` | no | +| [s3\_server\_side\_encryption](#input\_s3\_server\_side\_encryption) | Specifies server-side encryption of the object in S3. Valid values are "AES256" and "aws:kms". | `string` | `null` | no | +| [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code | `any` | `null` | no | +| [store\_on\_s3](#input\_store\_on\_s3) | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | +| [tags](#input\_tags) | A map of tags to assign to resources. | `map(string)` | `{}` | no | +| [timeout](#input\_timeout) | The amount of time your Lambda Function has to run in seconds. | `number` | `3` | no | +| [tracing\_mode](#input\_tracing\_mode) | Tracing mode of the Lambda Function. Valid value can be either PassThrough or Active. | `string` | `null` | no | +| [trusted\_entities](#input\_trusted\_entities) | List of additional trusted entities for assuming Lambda Function role (trust relationship) | `any` | `[]` | no | +| [use\_existing\_cloudwatch\_log\_group](#input\_use\_existing\_cloudwatch\_log\_group) | Whether to use an existing CloudWatch log group or create new | `bool` | `false` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | +| [vpc\_subnet\_ids](#input\_vpc\_subnet\_ids) | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The ARN of the Cloudwatch Log Group | +| [lambda\_cloudwatch\_log\_group\_name](#output\_lambda\_cloudwatch\_log\_group\_name) | The name of the Cloudwatch Log Group | +| [lambda\_event\_source\_mapping\_function\_arn](#output\_lambda\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | +| [lambda\_event\_source\_mapping\_state](#output\_lambda\_event\_source\_mapping\_state) | The state of the event source mapping | +| [lambda\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | +| [lambda\_event\_source\_mapping\_uuid](#output\_lambda\_event\_source\_mapping\_uuid) | The UUID of the created event source mapping | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | +| [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | +| [lambda\_function\_kms\_key\_arn](#output\_lambda\_function\_kms\_key\_arn) | The ARN for the KMS encryption key of Lambda Function | +| [lambda\_function\_last\_modified](#output\_lambda\_function\_last\_modified) | The date Lambda Function resource was last modified | +| [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | +| [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | +| [lambda\_function\_source\_code\_hash](#output\_lambda\_function\_source\_code\_hash) | Base64-encoded representation of raw SHA-256 sum of the zip file | +| [lambda\_function\_source\_code\_size](#output\_lambda\_function\_source\_code\_size) | The size in bytes of the function .zip file | +| [lambda\_function\_url](#output\_lambda\_function\_url) | The URL of the Lambda Function URL | +| [lambda\_function\_url\_id](#output\_lambda\_function\_url\_id) | The Lambda Function URL generated id | +| [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [lambda\_layer\_version](#output\_lambda\_layer\_version) | The Lambda Layer version | +| [lambda\_role\_arn](#output\_lambda\_role\_arn) | The ARN of the IAM role created for the Lambda Function | +| [lambda\_role\_unique\_id](#output\_lambda\_role\_unique\_id) | The unique id of the IAM role created for the Lambda Function | +| [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | +| [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | + + + + diff --git a/modules/aws-lambda/iam.tf b/modules/aws-lambda/iam.tf new file mode 100644 index 0000000..371e0cc --- /dev/null +++ b/modules/aws-lambda/iam.tf @@ -0,0 +1,409 @@ +locals { + create_role = local.create && var.create_function && !var.create_layer && var.create_role + + # Lambda@Edge uses the Cloudwatch region closest to the location where the function is executed + # The region part of the LogGroup ARN is then replaced with a wildcard (*) so Lambda@Edge is able to log in every region + log_group_arn_regional = try(data.aws_cloudwatch_log_group.lambda[0].arn, aws_cloudwatch_log_group.lambda[0].arn, "") + log_group_name = try(data.aws_cloudwatch_log_group.lambda[0].name, aws_cloudwatch_log_group.lambda[0].name, "") + log_group_arn = var.lambda_at_edge ? format("arn:%s:%s:%s:%s:%s", data.aws_arn.log_group_arn[0].partition, data.aws_arn.log_group_arn[0].service, "*", data.aws_arn.log_group_arn[0].account, data.aws_arn.log_group_arn[0].resource) : local.log_group_arn_regional + + # Defaulting to "*" (an invalid character for an IAM Role name) will cause an error when + # attempting to plan if the role_name and function_name are not set. This is a workaround + # for #83 that will allow one to import resources without receiving an error from coalesce. + # @see https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/83 + role_name = local.create_role ? coalesce(var.role_name, var.function_name, "*") : null + + # IAM Role trusted entities is a list of any (allow strings (services) and maps (type+identifiers)) + trusted_entities_services = distinct(compact(concat( + slice(["lambda.amazonaws.com", "edgelambda.amazonaws.com"], 0, var.lambda_at_edge ? 2 : 1), + [for service in var.trusted_entities : try(tostring(service), "")] + ))) + + trusted_entities_principals = [ + for principal in var.trusted_entities : { + type = principal.type + identifiers = tolist(principal.identifiers) + } + if !can(tostring(principal)) + ] +} + +resource "random_string" "log_s3_name" { + length = 8 + numeric = true + special = false +} + +########### +# IAM role +########### + +data "aws_iam_role" "lambda" { + count = !local.create_role ? 1 : 0 + name = var.role_name +} + +data "aws_iam_policy_document" "assume_role" { + count = local.create_role ? 1 : 0 + + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = local.trusted_entities_services + } + + dynamic "principals" { + for_each = local.trusted_entities_principals + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + } + + dynamic "statement" { + for_each = var.assume_role_policy_statements + + content { + sid = try(statement.value.sid, replace(statement.key, "/[^0-9A-Za-z]*/", "")) + effect = try(statement.value.effect, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.condition, []) + content { + test = condition.value.test + variable = condition.value.variable + values = condition.value.values + } + } + } + } +} + +resource "aws_iam_role" "lambda" { + count = local.create_role ? 1 : 0 + + name = local.role_name + description = var.role_description + path = var.role_path + force_detach_policies = var.role_force_detach_policies + permissions_boundary = var.role_permissions_boundary + assume_role_policy = data.aws_iam_policy_document.assume_role[0].json + + tags = merge(var.tags, var.role_tags) +} + +################## +# Cloudwatch Logs +################## + +data "aws_arn" "log_group_arn" { + count = var.lambda_at_edge ? 1 : 0 + + arn = local.log_group_arn_regional +} + +data "aws_iam_policy_document" "logs" { + count = var.attach_cloudwatch_logs_policy ? 1 : 0 + + statement { + effect = "Allow" + + actions = compact([ + !var.use_existing_cloudwatch_log_group ? "logs:CreateLogGroup" : "", + "logs:CreateLogStream", + "logs:PutLogEvents" + ]) + + resources = flatten([for _, v in ["%v:*", "%v:*:*"] : format(v, local.log_group_arn)]) + } +} + +resource "aws_iam_policy" "logs" { + count = var.attach_cloudwatch_logs_policy ? 1 : 0 + + name = local.create_role ? "${local.role_name}-logs-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-logs-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy_document.logs[0].json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "logs" { + count = var.attach_cloudwatch_logs_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.logs[0].arn +} + +##################### +# Dead Letter Config +##################### + +data "aws_iam_policy_document" "dead_letter" { + count = var.attach_dead_letter_policy ? 1 : 0 + + statement { + effect = "Allow" + + actions = [ + "sns:Publish", + "sqs:SendMessage", + ] + + resources = [ + var.dead_letter_target_arn, + ] + } +} + +resource "aws_iam_policy" "dead_letter" { + count = var.attach_dead_letter_policy ? 1 : 0 + + name = local.create_role ? "${local.role_name}-dl-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-dl-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy_document.dead_letter[0].json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "dead_letter" { + count = var.attach_dead_letter_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.dead_letter[0].arn +} + +###### +# VPC +###### + +# Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function +data "aws_iam_policy" "vpc" { + count = var.attach_network_policy ? 1 : 0 + + arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSLambdaENIManagementAccess" +} + +resource "aws_iam_policy" "vpc" { + count = var.attach_network_policy ? 1 : 0 + + name = local.create_role ? "${local.role_name}-vpc-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-vpc-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy.vpc[0].policy + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "vpc" { + count = var.attach_network_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.vpc[0].arn +} + +##################### +# Tracing with X-Ray +##################### + +# Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function +data "aws_iam_policy" "tracing" { + count = var.attach_tracing_policy ? 1 : 0 + + arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AWSXRayDaemonWriteAccess" +} + +resource "aws_iam_policy" "tracing" { + count = var.attach_tracing_policy ? 1 : 0 + + name = local.create_role ? "${local.role_name}-tracing-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-tracing-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy.tracing[0].policy + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "tracing" { + count = var.attach_tracing_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.tracing[0].arn +} + +############################### +# Failure/Success Async Events +############################### + +data "aws_iam_policy_document" "async" { + count = var.attach_async_event_policy ? 1 : 0 + + statement { + effect = "Allow" + + actions = [ + "sns:Publish", + "sqs:SendMessage", + "events:PutEvents", + "lambda:InvokeFunction", + ] + + resources = compact(distinct([var.destination_on_failure, var.destination_on_success])) + } +} + +resource "aws_iam_policy" "async" { + count = var.attach_async_event_policy ? 1 : 0 + + name = local.create_role ? "${local.role_name}-async-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-async-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy_document.async[0].json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "async" { + count = var.attach_async_event_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.async[0].arn +} + +########################### +# Additional policy (JSON) +########################### + +resource "aws_iam_policy" "additional_json" { + count = var.attach_policy_json ? 1 : 0 + + name = local.role_name + path = var.policy_path + policy = var.policy_json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "additional_json" { + count = var.attach_policy_json ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.additional_json[0].arn +} + +##################################### +# Additional policies (list of JSON) +##################################### + +resource "aws_iam_policy" "additional_jsons" { + count = var.attach_policy_jsons ? var.number_of_policy_jsons : 0 + + name = local.create_role ? "${local.role_name}-${count.index}-${random_string.log_s3_name.result}" : "${data.aws_iam_role.lambda[0].name}-${count.index}-${random_string.log_s3_name.result}" + path = var.policy_path + policy = var.policy_jsons[count.index] + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "additional_jsons" { + count = var.attach_policy_jsons ? var.number_of_policy_jsons : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.additional_jsons[count.index].arn +} + +########################### +# ARN of additional policy +########################### + +resource "aws_iam_role_policy_attachment" "additional_one" { + count = var.attach_policy ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = var.policy +} + +###################################### +# List of ARNs of additional policies +###################################### + +resource "aws_iam_role_policy_attachment" "additional_many" { + count = var.attach_policies ? var.number_of_policies : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = var.policies[count.index] +} + +############################### +# Additional policy statements +############################### + +data "aws_iam_policy_document" "additional_inline" { + count = var.attach_policy_statements ? 1 : 0 + + dynamic "statement" { + for_each = var.policy_statements + + content { + sid = try(statement.value.sid, replace(statement.key, "/[^0-9A-Za-z]*/", "")) + effect = try(statement.value.effect, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.condition, []) + content { + test = condition.value.test + variable = condition.value.variable + values = condition.value.values + } + } + } + } +} + +resource "aws_iam_policy" "additional_inline" { + count = var.attach_policy_statements ? 1 : 0 + + name = "${local.role_name}-inline-${random_string.log_s3_name.result}" + path = var.policy_path + policy = data.aws_iam_policy_document.additional_inline[0].json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "additional_inline" { + count = var.attach_policy_statements ? 1 : 0 + + role = local.create_role ? aws_iam_role.lambda[0].name : data.aws_iam_role.lambda[0].name + policy_arn = aws_iam_policy.additional_inline[0].arn +} diff --git a/modules/aws-lambda/main.tf b/modules/aws-lambda/main.tf new file mode 100644 index 0000000..8e2d8db --- /dev/null +++ b/modules/aws-lambda/main.tf @@ -0,0 +1,338 @@ +data "aws_partition" "current" {} + +data "aws_subnets" "private_subnets_with_lambda_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } +} + +locals { + create = var.create + + archive_filename = try(data.external.archive_prepare[0].result.filename, null) + archive_filename_string = local.archive_filename != null ? local.archive_filename : "" + archive_was_missing = try(data.external.archive_prepare[0].result.was_missing, false) + + # Use a generated filename to determine when the source code has changed. + # filename - to get package from local + filename = var.local_existing_package != null ? var.local_existing_package : (var.store_on_s3 ? null : local.archive_filename) + was_missing = var.local_existing_package != null ? !fileexists(var.local_existing_package) : local.archive_was_missing + + # s3_* - to get package from S3 + s3_bucket = var.s3_existing_package != null ? try(var.s3_existing_package.bucket, null) : (var.store_on_s3 ? var.s3_bucket : null) + s3_key = var.s3_existing_package != null ? try(var.s3_existing_package.key, null) : (var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null) + s3_object_version = var.s3_existing_package != null ? try(var.s3_existing_package.version_id, null) : (var.store_on_s3 ? try(aws_s3_object.lambda_package[0].version_id, null) : null) + +} + +resource "aws_lambda_function" "this" { + count = local.create && var.create_function && !var.create_layer ? 1 : 0 + + function_name = var.function_name + description = var.description + role = var.create_role ? aws_iam_role.lambda[0].arn : data.aws_iam_role.lambda[0].arn + handler = var.package_type != "Zip" ? null : var.handler + memory_size = var.memory_size + reserved_concurrent_executions = var.reserved_concurrent_executions + runtime = var.package_type != "Zip" ? null : var.runtime + layers = var.layers + timeout = var.lambda_at_edge ? min(var.timeout, 30) : var.timeout + publish = var.lambda_at_edge ? true : var.publish + kms_key_arn = var.kms_key_arn + image_uri = var.image_uri + package_type = var.package_type + architectures = var.architectures + + /* ephemeral_storage is not supported in gov-cloud region, so it should be set to `null` */ + dynamic "ephemeral_storage" { + for_each = var.ephemeral_storage_size == null ? [] : [true] + + content { + size = var.ephemeral_storage_size + } + } + + filename = local.filename + source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null + + s3_bucket = local.s3_bucket + s3_key = local.s3_key + s3_object_version = local.s3_object_version + + dynamic "image_config" { + for_each = length(var.image_config_entry_point) > 0 || length(var.image_config_command) > 0 || var.image_config_working_directory != null ? [true] : [] + content { + entry_point = var.image_config_entry_point + command = var.image_config_command + working_directory = var.image_config_working_directory + } + } + + dynamic "environment" { + for_each = length(keys(var.environment_variables)) == 0 ? [] : [true] + content { + variables = var.environment_variables + } + } + + dynamic "dead_letter_config" { + for_each = var.dead_letter_target_arn == null ? [] : [true] + content { + target_arn = var.dead_letter_target_arn + } + } + + dynamic "tracing_config" { + for_each = var.tracing_mode == null ? [] : [true] + content { + mode = var.tracing_mode + } + } + + dynamic "vpc_config" { + for_each = try(data.aws_subnets.private_subnets_with_lambda_tag.ids, null) != null && var.vpc_security_group_ids != null ? [true] : [] + content { + security_group_ids = var.vpc_security_group_ids + subnet_ids = try(data.aws_subnets.private_subnets_with_lambda_tag.ids, null) + } + } + + dynamic "file_system_config" { + for_each = var.file_system_arn != null && var.file_system_local_mount_path != null ? [true] : [] + content { + local_mount_path = var.file_system_local_mount_path + arn = var.file_system_arn + } + } + + tags = var.tags + + depends_on = [ + null_resource.archive, + aws_s3_object.lambda_package, + + # Depending on the log group is necessary to allow Terraform to create the log group before AWS can. + # When a lambda function is invoked, AWS creates the log group automatically if it doesn't exist yet. + # Without the dependency, this can result in a race condition if the lambda function is invoked before + # Terraform can create the log group. + aws_cloudwatch_log_group.lambda, + + # Before the lambda is created the execution role with all its policies should be ready + aws_iam_role_policy_attachment.additional_inline, + aws_iam_role_policy_attachment.additional_json, + aws_iam_role_policy_attachment.additional_jsons, + aws_iam_role_policy_attachment.additional_many, + aws_iam_role_policy_attachment.additional_one, + aws_iam_role_policy_attachment.async, + aws_iam_role_policy_attachment.logs, + aws_iam_role_policy_attachment.dead_letter, + aws_iam_role_policy_attachment.vpc, + aws_iam_role_policy_attachment.tracing, + ] +} + +resource "aws_lambda_layer_version" "this" { + count = local.create && var.create_layer ? 1 : 0 + + layer_name = var.layer_name + description = var.description + license_info = var.license_info + + compatible_runtimes = length(var.compatible_runtimes) > 0 ? var.compatible_runtimes : [var.runtime] + compatible_architectures = var.compatible_architectures + skip_destroy = var.layer_skip_destroy + + filename = local.filename + source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null + + s3_bucket = local.s3_bucket + s3_key = local.s3_key + s3_object_version = local.s3_object_version + + depends_on = [null_resource.archive, aws_s3_object.lambda_package] +} + +resource "aws_s3_object" "lambda_package" { + count = local.create && var.store_on_s3 && var.create_package ? 1 : 0 + + bucket = var.s3_bucket + acl = var.s3_acl + key = local.s3_key + source = data.external.archive_prepare[0].result.filename + storage_class = var.s3_object_storage_class + + server_side_encryption = var.s3_server_side_encryption + + tags = var.s3_object_tags_only ? var.s3_object_tags : merge(var.tags, var.s3_object_tags) + + depends_on = [null_resource.archive] +} + +data "aws_cloudwatch_log_group" "lambda" { + count = local.create && var.create_function && !var.create_layer && var.use_existing_cloudwatch_log_group ? 1 : 0 + + name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" +} + +resource "aws_cloudwatch_log_group" "lambda" { + count = local.create && var.create_function && !var.create_layer && !var.use_existing_cloudwatch_log_group ? 1 : 0 + + name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" + retention_in_days = var.cloudwatch_logs_retention_in_days + kms_key_id = var.cloudwatch_logs_kms_key_id + + tags = merge(var.tags, var.cloudwatch_logs_tags) +} + +resource "aws_lambda_provisioned_concurrency_config" "current_version" { + count = local.create && var.create_function && !var.create_layer && var.provisioned_concurrent_executions > -1 ? 1 : 0 + + function_name = aws_lambda_function.this[0].function_name + qualifier = aws_lambda_function.this[0].version + + provisioned_concurrent_executions = var.provisioned_concurrent_executions +} + +locals { + qualifiers = zipmap(["current_version", "unqualified_alias"], [var.create_current_version_async_event_config ? true : null, var.create_unqualified_alias_async_event_config ? true : null]) +} + +resource "aws_lambda_function_event_invoke_config" "this" { + for_each = { for k, v in local.qualifiers : k => v if local.create && var.create_function && !var.create_layer && var.create_async_event_config } + + function_name = aws_lambda_function.this[0].function_name + qualifier = each.key == "current_version" ? aws_lambda_function.this[0].version : null + + maximum_event_age_in_seconds = var.maximum_event_age_in_seconds + maximum_retry_attempts = var.maximum_retry_attempts + + dynamic "destination_config" { + for_each = var.destination_on_failure != null || var.destination_on_success != null ? [true] : [] + content { + dynamic "on_failure" { + for_each = var.destination_on_failure != null ? [true] : [] + content { + destination = var.destination_on_failure + } + } + + dynamic "on_success" { + for_each = var.destination_on_success != null ? [true] : [] + content { + destination = var.destination_on_success + } + } + } + } +} + +resource "aws_lambda_permission" "current_version_triggers" { + for_each = { for k, v in var.allowed_triggers : k => v if local.create && var.create_function && !var.create_layer && var.create_current_version_allowed_triggers } + + function_name = aws_lambda_function.this[0].function_name + qualifier = aws_lambda_function.this[0].version + + statement_id = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) +} + +# Error: Error adding new Lambda Permission for lambda: InvalidParameterValueException: We currently do not support adding policies for $LATEST. +resource "aws_lambda_permission" "unqualified_alias_triggers" { + for_each = { for k, v in var.allowed_triggers : k => v if local.create && var.create_function && !var.create_layer && var.create_unqualified_alias_allowed_triggers } + + function_name = aws_lambda_function.this[0].function_name + + statement_id = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) +} + +resource "aws_lambda_event_source_mapping" "this" { + for_each = { for k, v in var.event_source_mapping : k => v if local.create && var.create_function && !var.create_layer && var.create_unqualified_alias_allowed_triggers } + + function_name = aws_lambda_function.this[0].arn + + event_source_arn = try(each.value.event_source_arn, null) + + batch_size = try(each.value.batch_size, null) + maximum_batching_window_in_seconds = try(each.value.maximum_batching_window_in_seconds, null) + enabled = try(each.value.enabled, true) + starting_position = try(each.value.starting_position, null) + starting_position_timestamp = try(each.value.starting_position_timestamp, null) + parallelization_factor = try(each.value.parallelization_factor, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, null) + maximum_record_age_in_seconds = try(each.value.maximum_record_age_in_seconds, null) + bisect_batch_on_function_error = try(each.value.bisect_batch_on_function_error, null) + topics = try(each.value.topics, null) + queues = try(each.value.queues, null) + function_response_types = try(each.value.function_response_types, null) + + dynamic "destination_config" { + for_each = try(each.value.destination_arn_on_failure, null) != null ? [true] : [] + content { + on_failure { + destination_arn = each.value["destination_arn_on_failure"] + } + } + } + + dynamic "self_managed_event_source" { + for_each = try(each.value.self_managed_event_source, []) + content { + endpoints = self_managed_event_source.value.endpoints + } + } + + dynamic "source_access_configuration" { + for_each = try(each.value.source_access_configuration, []) + content { + type = source_access_configuration.value["type"] + uri = source_access_configuration.value["uri"] + } + } + + dynamic "filter_criteria" { + for_each = try(each.value.filter_criteria, null) != null ? [true] : [] + + content { + filter { + pattern = try(each.value["filter_criteria"].pattern, null) + } + } + } +} + +resource "aws_lambda_function_url" "this" { + count = local.create && var.create_function && !var.create_layer && var.create_lambda_function_url ? 1 : 0 + + function_name = aws_lambda_function.this[0].function_name + + # Error: error creating Lambda Function URL: ValidationException + qualifier = var.create_unqualified_alias_lambda_function_url ? null : aws_lambda_function.this[0].version + authorization_type = var.authorization_type + + dynamic "cors" { + for_each = length(keys(var.cors)) == 0 ? [] : [var.cors] + + content { + allow_credentials = try(cors.value.allow_credentials, null) + allow_headers = try(cors.value.allow_headers, null) + allow_methods = try(cors.value.allow_methods, null) + allow_origins = try(cors.value.allow_origins, null) + expose_headers = try(cors.value.expose_headers, null) + max_age = try(cors.value.max_age, null) + } + } +} diff --git a/modules/aws-lambda/modules/alias/README.md b/modules/aws-lambda/modules/alias/README.md new file mode 100644 index 0000000..c74d07d --- /dev/null +++ b/modules/aws-lambda/modules/alias/README.md @@ -0,0 +1,191 @@ +# AWS Lambda Alias + +Terraform module, which creates Lambda alias as well as takes care of async event configuration and Lambda permissions for alias. + +Lambda Alias is required to do complex Lambda deployments, eg. using external tools like AWS CodeDeploy. + +This Terraform module is the part of [serverless.tf framework](https://github.com/antonbabenko/serverless.tf), which aims to simplify all operations when working with the serverless in Terraform. + + +## Usage + +### Lambda Function and statically configured alias with the version of Lambda Function + +```hcl +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda1" + handler = "index.lambda_handler" + runtime = "python3.8" + + source_path = "../src/lambda-function1" +} + +module "alias_no_refresh" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + refresh_alias = false + + name = "current-no-refresh" + + function_name = module.lambda_function.lambda_function_name + function_version = module.lambda_function.lambda_function_version + + allowed_triggers = { + AnotherAPIGatewayAny = { + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:012345678901:abcdedfgse/*/*/*" + } + } +} +``` + +### Lambda Alias (auto-refreshing when version changed externally) + +This is useful when doing complex deployments using external tool. + +```hcl +module "alias_refresh" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = "current-with-refresh" + function_name = module.lambda_function.lambda_function_name +} +``` + +### Lambda Alias (using existing alias) + +This is useful when extra configuration on existing Lambda alias is required. + +```hcl +module "alias_refresh" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = "current-with-refresh" + function_name = module.lambda_function.lambda_function_name +} + +module "alias_existing" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + use_existing_alias = true + + name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name + + allowed_triggers = { + AnotherAwesomeAPIGateway = { + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:012345678901:aqnku8akd0/*/*/*" + } + } +} +``` + + +## Conditional creation + +Sometimes you need to have a way to create resources conditionally but Terraform does not allow usage of `count` inside `module` block, so the solution is to specify `create` arguments. + +```hcl +module "lambda" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + create = false # to disable all resources + + create_async_event_config = false # to control async configs + create_version_async_event_config = false + create_qualified_alias_async_event_config = false + + create_version_allowed_triggers = false # to control allowed triggers on version + create_qualified_alias_allowed_triggers = false # to control allowed triggers on alias + + # ... omitted +} +``` + +## Examples + +* [Alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) - Create Lambda function and aliases in various combinations with all supported features. + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.35 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.35 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_lambda_alias.no_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_alias) | resource | +| [aws_lambda_alias.with_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_alias) | resource | +| [aws_lambda_event_source_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | +| [aws_lambda_function_event_invoke_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_event_invoke_config) | resource | +| [aws_lambda_permission.qualified_alias_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_permission.version_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_alias.existing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_alias) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | +| [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | +| [create\_async\_event\_config](#input\_create\_async\_event\_config) | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | +| [create\_qualified\_alias\_allowed\_triggers](#input\_create\_qualified\_alias\_allowed\_triggers) | Whether to allow triggers on qualified alias | `bool` | `true` | no | +| [create\_qualified\_alias\_async\_event\_config](#input\_create\_qualified\_alias\_async\_event\_config) | Whether to allow async event configuration on qualified alias | `bool` | `true` | no | +| [create\_version\_allowed\_triggers](#input\_create\_version\_allowed\_triggers) | Whether to allow triggers on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_version\_async\_event\_config](#input\_create\_version\_async\_event\_config) | Whether to allow async event configuration on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [description](#input\_description) | Description of the alias. | `string` | `""` | no | +| [destination\_on\_failure](#input\_destination\_on\_failure) | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | +| [destination\_on\_success](#input\_destination\_on\_success) | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | +| [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no | +| [function\_name](#input\_function\_name) | The function ARN of the Lambda function for which you want to create an alias. | `string` | `""` | no | +| [function\_version](#input\_function\_version) | Lambda function version for which you are creating the alias. Pattern: ($LATEST\|[0-9]+). | `string` | `""` | no | +| [maximum\_event\_age\_in\_seconds](#input\_maximum\_event\_age\_in\_seconds) | Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600. | `number` | `null` | no | +| [maximum\_retry\_attempts](#input\_maximum\_retry\_attempts) | Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2. | `number` | `null` | no | +| [name](#input\_name) | Name for the alias you are creating. | `string` | `""` | no | +| [refresh\_alias](#input\_refresh\_alias) | Whether to refresh function version used in the alias. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `true` | no | +| [routing\_additional\_version\_weights](#input\_routing\_additional\_version\_weights) | A map that defines the proportion of events that should be sent to different versions of a lambda function. | `map(number)` | `{}` | no | +| [use\_existing\_alias](#input\_use\_existing\_alias) | Whether to manage existing alias instead of creating a new one. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [lambda\_alias\_arn](#output\_lambda\_alias\_arn) | The ARN of the Lambda Function Alias | +| [lambda\_alias\_description](#output\_lambda\_alias\_description) | Description of alias | +| [lambda\_alias\_event\_source\_mapping\_function\_arn](#output\_lambda\_alias\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | +| [lambda\_alias\_event\_source\_mapping\_state](#output\_lambda\_alias\_event\_source\_mapping\_state) | The state of the event source mapping | +| [lambda\_alias\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_alias\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | +| [lambda\_alias\_event\_source\_mapping\_uuid](#output\_lambda\_alias\_event\_source\_mapping\_uuid) | The UUID of the created event source mapping | +| [lambda\_alias\_function\_version](#output\_lambda\_alias\_function\_version) | Lambda function version which the alias uses | +| [lambda\_alias\_invoke\_arn](#output\_lambda\_alias\_invoke\_arn) | The ARN to be used for invoking Lambda Function from API Gateway | +| [lambda\_alias\_name](#output\_lambda\_alias\_name) | The name of the Lambda Function Alias | + + +## Authors + +Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. + +Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. + + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/modules/aws-lambda/modules/alias/main.tf b/modules/aws-lambda/modules/alias/main.tf new file mode 100644 index 0000000..823a4f7 --- /dev/null +++ b/modules/aws-lambda/modules/alias/main.tf @@ -0,0 +1,166 @@ +locals { + alias_arn = try(data.aws_lambda_alias.existing[0].arn, aws_lambda_alias.no_refresh[0].arn, aws_lambda_alias.with_refresh[0].arn, "") + version = try(data.aws_lambda_alias.existing[0].function_version, aws_lambda_alias.with_refresh[0].function_version, aws_lambda_alias.no_refresh[0].function_version, "") + qualifiers = zipmap(["version", "qualified_alias"], [var.create_version_async_event_config ? true : null, var.create_qualified_alias_async_event_config ? true : null]) +} + +data "aws_lambda_alias" "existing" { + count = var.create && var.use_existing_alias ? 1 : 0 + + function_name = var.function_name + name = var.name +} + +resource "aws_lambda_alias" "no_refresh" { + count = var.create && !var.use_existing_alias && !var.refresh_alias ? 1 : 0 + + name = var.name + description = var.description + + function_name = var.function_name + function_version = var.function_version != "" ? var.function_version : "$LATEST" + + # $LATEST is not supported for an alias pointing to more than 1 version + dynamic "routing_config" { + for_each = length(keys(var.routing_additional_version_weights)) == 0 ? [] : [true] + content { + additional_version_weights = var.routing_additional_version_weights + } + } +} + +resource "aws_lambda_alias" "with_refresh" { + count = var.create && !var.use_existing_alias && var.refresh_alias ? 1 : 0 + + name = var.name + description = var.description + + function_name = var.function_name + function_version = var.function_version != "" ? var.function_version : "$LATEST" + + # $LATEST is not supported for an alias pointing to more than 1 version + dynamic "routing_config" { + for_each = length(keys(var.routing_additional_version_weights)) == 0 ? [] : [true] + content { + additional_version_weights = var.routing_additional_version_weights + } + } + + lifecycle { + ignore_changes = [function_version] + } +} + +resource "aws_lambda_function_event_invoke_config" "this" { + for_each = var.create && var.create_async_event_config ? local.qualifiers : {} + + function_name = var.function_name + qualifier = each.key == "version" ? local.version : var.name + + maximum_event_age_in_seconds = var.maximum_event_age_in_seconds + maximum_retry_attempts = var.maximum_retry_attempts + + dynamic "destination_config" { + for_each = var.destination_on_failure != null || var.destination_on_success != null ? [true] : [] + content { + dynamic "on_failure" { + for_each = var.destination_on_failure != null ? [true] : [] + content { + destination = var.destination_on_failure + } + } + + dynamic "on_success" { + for_each = var.destination_on_success != null ? [true] : [] + content { + destination = var.destination_on_success + } + } + } + } +} + +resource "aws_lambda_permission" "version_triggers" { + for_each = var.create && var.create_version_allowed_triggers ? var.allowed_triggers : {} + + function_name = var.function_name + + # Error: Error adding new Lambda Permission for ... InvalidParameterValueException: We currently do not support adding policies for $LATEST. + qualifier = local.version != "$LATEST" ? local.version : null + + statement_id = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) +} + +resource "aws_lambda_permission" "qualified_alias_triggers" { + for_each = var.create && var.create_qualified_alias_allowed_triggers ? var.allowed_triggers : {} + + function_name = var.function_name + qualifier = var.name + + statement_id = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) +} + +resource "aws_lambda_event_source_mapping" "this" { + for_each = { for k, v in var.event_source_mapping : k => v if var.create } + + function_name = local.alias_arn + + event_source_arn = try(each.value.event_source_arn, null) + + batch_size = try(each.value.batch_size, null) + maximum_batching_window_in_seconds = try(each.value.maximum_batching_window_in_seconds, null) + enabled = try(each.value.enabled, null) + starting_position = try(each.value.starting_position, null) + starting_position_timestamp = try(each.value.starting_position_timestamp, null) + parallelization_factor = try(each.value.parallelization_factor, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, null) + maximum_record_age_in_seconds = try(each.value.maximum_record_age_in_seconds, null) + bisect_batch_on_function_error = try(each.value.bisect_batch_on_function_error, null) + topics = try(each.value.topics, null) + queues = try(each.value.queues, null) + function_response_types = try(each.value.function_response_types, null) + + dynamic "destination_config" { + for_each = try(each.value.destination_arn_on_failure, null) != null ? [true] : [] + content { + on_failure { + destination_arn = each.value["destination_arn_on_failure"] + } + } + } + + dynamic "self_managed_event_source" { + for_each = try(each.value.self_managed_event_source, []) + content { + endpoints = self_managed_event_source.value.endpoints + } + } + + dynamic "source_access_configuration" { + for_each = try(each.value.source_access_configuration, []) + content { + type = source_access_configuration.value["type"] + uri = source_access_configuration.value["uri"] + } + } + + dynamic "filter_criteria" { + for_each = try(each.value.filter_criteria, null) != null ? [true] : [] + + content { + filter { + pattern = try(each.value["filter_criteria"].pattern, null) + } + } + } +} diff --git a/modules/aws-lambda/modules/alias/outputs.tf b/modules/aws-lambda/modules/alias/outputs.tf new file mode 100644 index 0000000..b1a2915 --- /dev/null +++ b/modules/aws-lambda/modules/alias/outputs.tf @@ -0,0 +1,45 @@ +# Lambda Alias +output "lambda_alias_name" { + description = "The name of the Lambda Function Alias" + value = try(data.aws_lambda_alias.existing[0].name, aws_lambda_alias.with_refresh[0].name, aws_lambda_alias.no_refresh[0].name, "") +} + +output "lambda_alias_arn" { + description = "The ARN of the Lambda Function Alias" + value = try(data.aws_lambda_alias.existing[0].arn, aws_lambda_alias.with_refresh[0].arn, aws_lambda_alias.no_refresh[0].arn, "") +} + +output "lambda_alias_invoke_arn" { + description = "The ARN to be used for invoking Lambda Function from API Gateway" + value = try(data.aws_lambda_alias.existing[0].invoke_arn, aws_lambda_alias.with_refresh[0].invoke_arn, aws_lambda_alias.no_refresh[0].invoke_arn, "") +} + +output "lambda_alias_description" { + description = "Description of alias" + value = try(data.aws_lambda_alias.existing[0].description, aws_lambda_alias.with_refresh[0].description, aws_lambda_alias.no_refresh[0].description, "") +} + +output "lambda_alias_function_version" { + description = "Lambda function version which the alias uses" + value = try(data.aws_lambda_alias.existing[0].function_version, aws_lambda_alias.with_refresh[0].function_version, aws_lambda_alias.no_refresh[0].function_version, "") +} + +output "lambda_alias_event_source_mapping_function_arn" { + description = "The the ARN of the Lambda function the event source mapping is sending events to" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.function_arn } +} + +output "lambda_alias_event_source_mapping_state" { + description = "The state of the event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state } +} + +output "lambda_alias_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state_transition_reason } +} + +output "lambda_alias_event_source_mapping_uuid" { + description = "The UUID of the created event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.uuid } +} diff --git a/modules/aws-lambda/modules/alias/variables.tf b/modules/aws-lambda/modules/alias/variables.tf new file mode 100644 index 0000000..732067f --- /dev/null +++ b/modules/aws-lambda/modules/alias/variables.tf @@ -0,0 +1,129 @@ +variable "create" { + description = "Controls whether resources should be created" + type = bool + default = true +} + +variable "use_existing_alias" { + description = "Whether to manage existing alias instead of creating a new one. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy)." + type = bool + default = false +} + +variable "refresh_alias" { + description = "Whether to refresh function version used in the alias. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy)." + type = bool + default = true +} + +variable "create_async_event_config" { + description = "Controls whether async event configuration for Lambda Function/Alias should be created" + type = bool + default = false +} + +variable "create_version_async_event_config" { + description = "Whether to allow async event configuration on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources)" + type = bool + default = true +} + +variable "create_qualified_alias_async_event_config" { + description = "Whether to allow async event configuration on qualified alias" + type = bool + default = true +} + +variable "create_version_allowed_triggers" { + description = "Whether to allow triggers on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources)" + type = bool + default = true +} + +variable "create_qualified_alias_allowed_triggers" { + description = "Whether to allow triggers on qualified alias" + type = bool + default = true +} + +######## +# Alias +######## + +variable "name" { + description = "Name for the alias you are creating." + type = string + default = "" +} + +variable "description" { + description = "Description of the alias." + type = string + default = "" +} + +variable "function_name" { + description = "The function ARN of the Lambda function for which you want to create an alias." + type = string + default = "" +} + +variable "function_version" { + description = "Lambda function version for which you are creating the alias. Pattern: ($LATEST|[0-9]+)." + type = string + default = "" +} + +variable "routing_additional_version_weights" { + description = "A map that defines the proportion of events that should be sent to different versions of a lambda function." + type = map(number) + default = {} +} + +############################ +# Lambda Async Event Config +############################ + +variable "maximum_event_age_in_seconds" { + description = "Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600." + type = number + default = null +} + +variable "maximum_retry_attempts" { + description = "Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2." + type = number + default = null +} + +variable "destination_on_failure" { + description = "Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations" + type = string + default = null +} + +variable "destination_on_success" { + description = "Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations" + type = string + default = null +} + +############################################ +# Lambda Permissions (for allowed triggers) +############################################ + +variable "allowed_triggers" { + description = "Map of allowed triggers to create Lambda permissions" + type = map(any) + default = {} +} + +############################################ +# Lambda Event Source Mapping +############################################ + +variable "event_source_mapping" { + description = "Map of event source mapping" + type = any + default = {} +} diff --git a/modules/aws-lambda/modules/alias/versions.tf b/modules/aws-lambda/modules/alias/versions.tf new file mode 100644 index 0000000..d56fc0e --- /dev/null +++ b/modules/aws-lambda/modules/alias/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.35" + } + } +} diff --git a/modules/aws-lambda/modules/deploy/README.md b/modules/aws-lambda/modules/deploy/README.md new file mode 100644 index 0000000..f25bb7f --- /dev/null +++ b/modules/aws-lambda/modules/deploy/README.md @@ -0,0 +1,205 @@ +# Lambda Function Deployment via AWS CodeDeploy + +Terraform module, which creates Lambda alias as well as AWS CodeDeploy resources required to deploy. + +This Terraform module is the part of [serverless.tf framework](https://github.com/antonbabenko/serverless.tf), which aims to simplify all operations when working with the serverless in Terraform. + +This module can create AWS CodeDeploy application and deployment group, if necessary. If you have several functions, you probably want to create those resources externally, and then set `use_existing_deployment_group = true`. + +During deployment this module does the following: +1. Create JSON object with required AppSpec configuration. Optionally, you can store deploy script for debug purposes by setting `save_deploy_script = true`. +1. Run [`aws deploy create-deployment` command](https://docs.aws.amazon.com/cli/latest/reference/deploy/create-deployment.html) if `create_deployment = true` and `run_deployment = true` was set. +1. After deployment is created, it can wait for the completion if `wait_deployment_completion = true`. Be aware, that Terraform will lock the execution and it can fail if it runs for a long period of time. Set this flag for fast deployments (eg, `deployment_config_name = "CodeDeployDefault.LambdaAllAtOnce"`). + + +## Usage + +### Complete example of Lambda Function deployment via AWS CodeDeploy + +```hcl +module "lambda_function" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda1" + handler = "index.lambda_handler" + runtime = "python3.8" + + source_path = "../src/lambda-function1" +} + +module "alias_refresh" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = "current-with-refresh" + function_name = module.lambda_function.lambda_function_name + + # Set function_version when creating alias to be able to deploy using it, + # because AWS CodeDeploy doesn't understand $LATEST as CurrentVersion. + function_version = module.lambda_function.lambda_function_version +} + +module "deploy" { + source = "terraform-aws-modules/lambda/aws//modules/deploy" + + alias_name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name + + target_version = module.lambda_function.lambda_function_version + + create_app = true + app_name = "my-awesome-app" + + create_deployment_group = true + deployment_group_name = "something" + + create_deployment = true + run_deployment = true + wait_deployment_completion = true + + triggers = { + start = { + events = ["DeploymentStart"] + name = "DeploymentStart" + target_arn = "arn:aws:sns:eu-west-1:012345678901:sns1" + } + success = { + events = ["DeploymentSuccess"] + name = "DeploymentSuccess" + target_arn = "arn:aws:sns:eu-west-1:012345678901:sns2" + } + } +} +``` + +## Conditional creation + +Sometimes you need to have a way to create resources conditionally but Terraform does not allow usage of `count` inside `module` block, so the solution is to specify `create` arguments. + +```hcl +module "lambda" { + source = "terraform-aws-modules/lambda/aws//modules/deploy" + + create = false # to disable all resources + + create_app = false + use_existing_app = false + create_deployment_group = false + use_existing_deployment_group = false + + # ... omitted +} +``` + +## Examples + +* [Deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) - Creates Lambda Function, Alias, and all resources required to create deployments using AWS CodeDeploy. + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.35 | +| [local](#requirement\_local) | >= 1.0 | +| [null](#requirement\_null) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.35 | +| [local](#provider\_local) | >= 1.0 | +| [null](#provider\_null) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_codedeploy_app.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codedeploy_app) | resource | +| [aws_codedeploy_deployment_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codedeploy_deployment_group) | resource | +| [aws_iam_policy.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [local_file.deploy_script](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | +| [null_resource.deploy](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_role.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source | +| [aws_lambda_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_alias) | data source | +| [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_function) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [after\_allow\_traffic\_hook\_arn](#input\_after\_allow\_traffic\_hook\_arn) | ARN of Lambda function to execute after allow traffic during deployment. This function should be named CodeDeployHook\_, to match the managed AWSCodeDeployForLambda policy, unless you're using a custom role | `string` | `""` | no | +| [alarm\_enabled](#input\_alarm\_enabled) | Indicates whether the alarm configuration is enabled. This option is useful when you want to temporarily deactivate alarm monitoring for a deployment group without having to add the same alarms again later. | `bool` | `false` | no | +| [alarm\_ignore\_poll\_alarm\_failure](#input\_alarm\_ignore\_poll\_alarm\_failure) | Indicates whether a deployment should continue if information about the current state of alarms cannot be retrieved from CloudWatch. | `bool` | `false` | no | +| [alarms](#input\_alarms) | A list of alarms configured for the deployment group. A maximum of 10 alarms can be added to a deployment group. | `list(string)` | `[]` | no | +| [alias\_name](#input\_alias\_name) | Name for the alias | `string` | `""` | no | +| [app\_name](#input\_app\_name) | Name of AWS CodeDeploy application | `string` | `""` | no | +| [attach\_hooks\_policy](#input\_attach\_hooks\_policy) | Whether to attach Invoke policy to CodeDeploy role when before allow traffic or after allow traffic hooks are defined. | `bool` | `true` | no | +| [attach\_triggers\_policy](#input\_attach\_triggers\_policy) | Whether to attach SNS policy to CodeDeploy role when triggers are defined | `bool` | `false` | no | +| [auto\_rollback\_enabled](#input\_auto\_rollback\_enabled) | Indicates whether a defined automatic rollback configuration is currently enabled for this Deployment Group. | `bool` | `true` | no | +| [auto\_rollback\_events](#input\_auto\_rollback\_events) | List of event types that trigger a rollback. Supported types are DEPLOYMENT\_FAILURE and DEPLOYMENT\_STOP\_ON\_ALARM. | `list(string)` |
[
"DEPLOYMENT_STOP_ON_ALARM"
]
| no | +| [aws\_cli\_command](#input\_aws\_cli\_command) | Command to run as AWS CLI. May include extra arguments like region and profile. | `string` | `"aws"` | no | +| [before\_allow\_traffic\_hook\_arn](#input\_before\_allow\_traffic\_hook\_arn) | ARN of Lambda function to execute before allow traffic during deployment. This function should be named CodeDeployHook\_, to match the managed AWSCodeDeployForLambda policy, unless you're using a custom role | `string` | `""` | no | +| [codedeploy\_principals](#input\_codedeploy\_principals) | List of CodeDeploy service principals to allow. The list can include global or regional endpoints. | `list(string)` |
[
"codedeploy.amazonaws.com"
]
| no | +| [codedeploy\_role\_name](#input\_codedeploy\_role\_name) | IAM role name to create or use by CodeDeploy | `string` | `""` | no | +| [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | +| [create\_app](#input\_create\_app) | Whether to create new AWS CodeDeploy app | `bool` | `false` | no | +| [create\_codedeploy\_role](#input\_create\_codedeploy\_role) | Whether to create new AWS CodeDeploy IAM role | `bool` | `true` | no | +| [create\_deployment](#input\_create\_deployment) | Create the AWS resources and script for CodeDeploy | `bool` | `false` | no | +| [create\_deployment\_group](#input\_create\_deployment\_group) | Whether to create new AWS CodeDeploy Deployment Group | `bool` | `false` | no | +| [current\_version](#input\_current\_version) | Current version of Lambda function version to deploy (can't be $LATEST) | `string` | `""` | no | +| [deployment\_config\_name](#input\_deployment\_config\_name) | Name of deployment config to use | `string` | `"CodeDeployDefault.LambdaAllAtOnce"` | no | +| [deployment\_group\_name](#input\_deployment\_group\_name) | Name of deployment group to use | `string` | `""` | no | +| [description](#input\_description) | Description to use for the deployment | `string` | `""` | no | +| [force\_deploy](#input\_force\_deploy) | Force deployment every time (even when nothing changes) | `bool` | `false` | no | +| [function\_name](#input\_function\_name) | The name of the Lambda function to deploy | `string` | `""` | no | +| [get\_deployment\_sleep\_timer](#input\_get\_deployment\_sleep\_timer) | Adds additional sleep time to get-deployment command to avoid the service throttling | `number` | `5` | no | +| [interpreter](#input\_interpreter) | List of interpreter arguments used to execute deploy script, first arg is path | `list(string)` |
[
"/bin/bash",
"-c"
]
| no | +| [run\_deployment](#input\_run\_deployment) | Run AWS CLI command to start the deployment | `bool` | `false` | no | +| [save\_deploy\_script](#input\_save\_deploy\_script) | Save deploy script locally | `bool` | `false` | no | +| [tags](#input\_tags) | A map of tags to assign to resources. | `map(string)` | `{}` | no | +| [target\_version](#input\_target\_version) | Target version of Lambda function version to deploy | `string` | `""` | no | +| [triggers](#input\_triggers) | Map of triggers which will be notified when event happens. Valid options for event types are DeploymentStart, DeploymentSuccess, DeploymentFailure, DeploymentStop, DeploymentRollback, DeploymentReady (Applies only to replacement instances in a blue/green deployment), InstanceStart, InstanceSuccess, InstanceFailure, InstanceReady. Note that not all are applicable for Lambda deployments. | `map(any)` | `{}` | no | +| [use\_existing\_app](#input\_use\_existing\_app) | Whether to use existing AWS CodeDeploy app | `bool` | `false` | no | +| [use\_existing\_deployment\_group](#input\_use\_existing\_deployment\_group) | Whether to use existing AWS CodeDeploy Deployment Group | `bool` | `false` | no | +| [wait\_deployment\_completion](#input\_wait\_deployment\_completion) | Wait until deployment completes. It can take a lot of time and your terraform process may lock execution for long time. | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [appspec](#output\_appspec) | Appspec data as HCL | +| [appspec\_content](#output\_appspec\_content) | Appspec data as valid JSON | +| [appspec\_sha256](#output\_appspec\_sha256) | SHA256 of Appspec JSON | +| [codedeploy\_app\_name](#output\_codedeploy\_app\_name) | Name of CodeDeploy application | +| [codedeploy\_deployment\_group\_id](#output\_codedeploy\_deployment\_group\_id) | CodeDeploy deployment group id | +| [codedeploy\_deployment\_group\_name](#output\_codedeploy\_deployment\_group\_name) | CodeDeploy deployment group name | +| [codedeploy\_iam\_role\_name](#output\_codedeploy\_iam\_role\_name) | Name of IAM role used by CodeDeploy | +| [deploy\_script](#output\_deploy\_script) | Path to a deployment script | +| [script](#output\_script) | Deployment script | + + +## Authors + +Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. + +Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. + + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/modules/aws-lambda/modules/deploy/main.tf b/modules/aws-lambda/modules/deploy/main.tf new file mode 100644 index 0000000..f82ca37 --- /dev/null +++ b/modules/aws-lambda/modules/deploy/main.tf @@ -0,0 +1,286 @@ +locals { + # AWS CodeDeploy can't deploy when CurrentVersion is "$LATEST" + qualifier = try(data.aws_lambda_function.this[0].qualifier, "") + current_version = local.qualifier == "$LATEST" ? 1 : local.qualifier + + app_name = try(aws_codedeploy_app.this[0].name, var.app_name) + deployment_group_name = try(aws_codedeploy_deployment_group.this[0].deployment_group_name, var.deployment_group_name) + + appspec = merge({ + version = "0.0" + Resources = [ + { + MyFunction = { + Type = "AWS::Lambda::Function" + Properties = { + Name = var.function_name + Alias = var.alias_name + CurrentVersion = var.current_version != "" ? var.current_version : local.current_version + TargetVersion : var.target_version + } + } + } + ] + }, var.before_allow_traffic_hook_arn != "" || var.after_allow_traffic_hook_arn != "" ? { + Hooks = [for k, v in zipmap(["BeforeAllowTraffic", "AfterAllowTraffic"], [ + var.before_allow_traffic_hook_arn != "" ? var.before_allow_traffic_hook_arn : null, + var.after_allow_traffic_hook_arn != "" ? var.after_allow_traffic_hook_arn : null + ]) : tomap({ (k) = v }) if v != null] + } : {}) + + appspec_content = replace(jsonencode(local.appspec), "\"", "\\\"") + appspec_sha256 = sha256(jsonencode(local.appspec)) + + script = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.22 | +| [docker](#requirement\_docker) | >= 2.12 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.22 | +| [docker](#provider\_docker) | >= 2.12 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ecr_lifecycle_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy) | resource | +| [aws_ecr_repository.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository) | resource | +| [docker_registry_image.this](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/registry_image) | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [build\_args](#input\_build\_args) | A map of Docker build arguments. | `map(string)` | `{}` | no | +| [create\_ecr\_repo](#input\_create\_ecr\_repo) | Controls whether ECR repository for Lambda image should be created | `bool` | `false` | no | +| [docker\_file\_path](#input\_docker\_file\_path) | Path to Dockerfile in source package | `string` | `"Dockerfile"` | no | +| [ecr\_address](#input\_ecr\_address) | Address of ECR repository for cross-account container image pulling (optional). Option `create_ecr_repo` must be `false` | `string` | `null` | no | +| [ecr\_force\_delete](#input\_ecr\_force\_delete) | If true, will delete the repository even if it contains images. | `bool` | `true` | no | +| [ecr\_repo](#input\_ecr\_repo) | Name of ECR repository to use or to create | `string` | `null` | no | +| [ecr\_repo\_lifecycle\_policy](#input\_ecr\_repo\_lifecycle\_policy) | A JSON formatted ECR lifecycle policy to automate the cleaning up of unused images. | `string` | `null` | no | +| [ecr\_repo\_tags](#input\_ecr\_repo\_tags) | A map of tags to assign to ECR repository | `map(string)` | `{}` | no | +| [image\_tag](#input\_image\_tag) | Image tag to use. If not specified current timestamp in format 'YYYYMMDDhhmmss' will be used. This can lead to unnecessary rebuilds. | `string` | `null` | no | +| [image\_tag\_mutability](#input\_image\_tag\_mutability) | The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE` | `string` | `"MUTABLE"` | no | +| [keep\_remotely](#input\_keep\_remotely) | Whether to keep Docker image in the remote registry on destroy operation. | `bool` | `false` | no | +| [scan\_on\_push](#input\_scan\_on\_push) | Indicates whether images are scanned after being pushed to the repository | `bool` | `false` | no | +| [source\_path](#input\_source\_path) | Path to folder containing application code | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [image\_uri](#output\_image\_uri) | The ECR image URI for deploying lambda | + + +## Authors + +Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. + +Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. + + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/modules/aws-lambda/modules/docker-build/main.tf b/modules/aws-lambda/modules/docker-build/main.tf new file mode 100644 index 0000000..9ff7415 --- /dev/null +++ b/modules/aws-lambda/modules/docker-build/main.tf @@ -0,0 +1,43 @@ +data "aws_region" "current" {} + +data "aws_caller_identity" "this" {} + +locals { + ecr_address = coalesce(var.ecr_address, format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.name)) + ecr_repo = var.create_ecr_repo ? aws_ecr_repository.this[0].id : var.ecr_repo + image_tag = coalesce(var.image_tag, formatdate("YYYYMMDDhhmmss", timestamp())) + ecr_image_name = format("%v/%v:%v", local.ecr_address, local.ecr_repo, local.image_tag) +} + +resource "docker_registry_image" "this" { + name = local.ecr_image_name + + build { + context = var.source_path + dockerfile = var.docker_file_path + build_args = var.build_args + } + + keep_remotely = var.keep_remotely +} + +resource "aws_ecr_repository" "this" { + count = var.create_ecr_repo ? 1 : 0 + + force_delete = var.ecr_force_delete + name = var.ecr_repo + image_tag_mutability = var.image_tag_mutability + + image_scanning_configuration { + scan_on_push = var.scan_on_push + } + + tags = var.ecr_repo_tags +} + +resource "aws_ecr_lifecycle_policy" "this" { + count = var.ecr_repo_lifecycle_policy != null ? 1 : 0 + + policy = var.ecr_repo_lifecycle_policy + repository = local.ecr_repo +} diff --git a/modules/aws-lambda/modules/docker-build/outputs.tf b/modules/aws-lambda/modules/docker-build/outputs.tf new file mode 100644 index 0000000..05c9063 --- /dev/null +++ b/modules/aws-lambda/modules/docker-build/outputs.tf @@ -0,0 +1,4 @@ +output "image_uri" { + description = "The ECR image URI for deploying lambda" + value = docker_registry_image.this.name +} diff --git a/modules/aws-lambda/modules/docker-build/variables.tf b/modules/aws-lambda/modules/docker-build/variables.tf new file mode 100644 index 0000000..4fadbff --- /dev/null +++ b/modules/aws-lambda/modules/docker-build/variables.tf @@ -0,0 +1,78 @@ +variable "create_ecr_repo" { + description = "Controls whether ECR repository for Lambda image should be created" + type = bool + default = false +} + +variable "ecr_address" { + description = "Address of ECR repository for cross-account container image pulling (optional). Option `create_ecr_repo` must be `false`" + type = string + default = null +} + +variable "ecr_repo" { + description = "Name of ECR repository to use or to create" + type = string + default = null +} + +variable "image_tag" { + description = "Image tag to use. If not specified current timestamp in format 'YYYYMMDDhhmmss' will be used. This can lead to unnecessary rebuilds." + type = string + default = null +} + +variable "source_path" { + description = "Path to folder containing application code" + type = string + default = null +} + +variable "docker_file_path" { + description = "Path to Dockerfile in source package" + type = string + default = "Dockerfile" +} + + +variable "image_tag_mutability" { + description = "The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE`" + type = string + default = "MUTABLE" +} + +variable "scan_on_push" { + description = "Indicates whether images are scanned after being pushed to the repository" + type = bool + default = false +} + +variable "ecr_force_delete" { + description = "If true, will delete the repository even if it contains images." + default = true + type = bool +} + +variable "ecr_repo_tags" { + description = "A map of tags to assign to ECR repository" + type = map(string) + default = {} +} + +variable "build_args" { + description = "A map of Docker build arguments." + type = map(string) + default = {} +} + +variable "ecr_repo_lifecycle_policy" { + description = "A JSON formatted ECR lifecycle policy to automate the cleaning up of unused images." + type = string + default = null +} + +variable "keep_remotely" { + description = "Whether to keep Docker image in the remote registry on destroy operation." + type = bool + default = false +} diff --git a/modules/aws-lambda/modules/docker-build/versions.tf b/modules/aws-lambda/modules/docker-build/versions.tf new file mode 100644 index 0000000..9885459 --- /dev/null +++ b/modules/aws-lambda/modules/docker-build/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.22" + } + docker = { + source = "kreuzwerker/docker" + version = ">= 2.12" + } + } +} diff --git a/modules/aws-lambda/outputs.tf b/modules/aws-lambda/outputs.tf new file mode 100644 index 0000000..f07a345 --- /dev/null +++ b/modules/aws-lambda/outputs.tf @@ -0,0 +1,145 @@ +# Lambda Function +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = try(aws_lambda_function.this[0].arn, "") +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = try(aws_lambda_function.this[0].invoke_arn, "") +} + +output "lambda_function_name" { + description = "The name of the Lambda Function" + value = try(aws_lambda_function.this[0].function_name, "") +} + +output "lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + value = try(aws_lambda_function.this[0].qualified_arn, "") +} + +output "lambda_function_version" { + description = "Latest published version of Lambda Function" + value = try(aws_lambda_function.this[0].version, "") +} + +output "lambda_function_last_modified" { + description = "The date Lambda Function resource was last modified" + value = try(aws_lambda_function.this[0].last_modified, "") +} + +output "lambda_function_kms_key_arn" { + description = "The ARN for the KMS encryption key of Lambda Function" + value = try(aws_lambda_function.this[0].kms_key_arn, "") +} + +output "lambda_function_source_code_hash" { + description = "Base64-encoded representation of raw SHA-256 sum of the zip file" + value = try(aws_lambda_function.this[0].source_code_hash, "") +} + +output "lambda_function_source_code_size" { + description = "The size in bytes of the function .zip file" + value = try(aws_lambda_function.this[0].source_code_size, "") +} + +# Lambda Function URL +output "lambda_function_url" { + description = "The URL of the Lambda Function URL" + value = try(aws_lambda_function_url.this[0].function_url, "") +} + +output "lambda_function_url_id" { + description = "The Lambda Function URL generated id" + value = try(aws_lambda_function_url.this[0].url_id, "") +} + +# Lambda Layer +output "lambda_layer_arn" { + description = "The ARN of the Lambda Layer with version" + value = try(aws_lambda_layer_version.this[0].arn, "") +} + +output "lambda_layer_layer_arn" { + description = "The ARN of the Lambda Layer without version" + value = try(aws_lambda_layer_version.this[0].layer_arn, "") +} + +output "lambda_layer_created_date" { + description = "The date Lambda Layer resource was created" + value = try(aws_lambda_layer_version.this[0].created_date, "") +} + +output "lambda_layer_source_code_size" { + description = "The size in bytes of the Lambda Layer .zip file" + value = try(aws_lambda_layer_version.this[0].source_code_size, "") +} + +output "lambda_layer_version" { + description = "The Lambda Layer version" + value = try(aws_lambda_layer_version.this[0].version, "") +} + +# Lambda Event Source Mapping +output "lambda_event_source_mapping_function_arn" { + description = "The the ARN of the Lambda function the event source mapping is sending events to" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.function_arn } +} + +output "lambda_event_source_mapping_state" { + description = "The state of the event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state } +} + +output "lambda_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state_transition_reason } +} + +output "lambda_event_source_mapping_uuid" { + description = "The UUID of the created event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.uuid } +} + +# IAM Role +output "lambda_role_arn" { + description = "The ARN of the IAM role created for the Lambda Function" + value = try(aws_iam_role.lambda[0].arn, "") +} + +output "lambda_role_name" { + description = "The name of the IAM role created for the Lambda Function" + value = try(aws_iam_role.lambda[0].name, "") +} + +output "lambda_role_unique_id" { + description = "The unique id of the IAM role created for the Lambda Function" + value = try(aws_iam_role.lambda[0].unique_id, "") +} + +# CloudWatch Log Group +output "lambda_cloudwatch_log_group_arn" { + description = "The ARN of the Cloudwatch Log Group" + value = local.log_group_arn +} + +output "lambda_cloudwatch_log_group_name" { + description = "The name of the Cloudwatch Log Group" + value = local.log_group_name +} + +# Deployment package +output "local_filename" { + description = "The filename of zip archive deployed (if deployment was from local)" + value = local.filename +} + +output "s3_object" { + description = "The map with S3 object data of zip archive deployed (if deployment was from S3)" + value = { + bucket = local.s3_bucket + key = local.s3_key + version_id = local.s3_object_version + } +} diff --git a/modules/aws-lambda/package 3.py b/modules/aws-lambda/package 3.py new file mode 100644 index 0000000..1b96608 --- /dev/null +++ b/modules/aws-lambda/package 3.py @@ -0,0 +1,1391 @@ +# coding: utf-8 + +import sys + +if sys.version_info < (3, 6): + raise RuntimeError("A python version 3.6 or newer is required") + +import os +import re +import time +import stat +import json +import shlex +import shutil +import hashlib +import zipfile +import argparse +import datetime +import tempfile +import operator +import platform +import subprocess +from subprocess import check_call, check_output +from contextlib import contextmanager +from base64 import b64encode +import logging + +PY38 = sys.version_info >= (3, 8) +PY37 = sys.version_info >= (3, 7) +PY36 = sys.version_info >= (3, 6) + +WINDOWS = platform.system() == 'Windows' +OSX = platform.system() == 'Darwin' + +################################################################################ +# Logging + +DEBUG2 = 9 +DEBUG3 = 8 +DUMP_ENV = 1 + +log_handler = None +log = logging.getLogger() +cmd_log = logging.getLogger('cmd') + + +def configure_logging(use_tf_stderr=False): + global log_handler + + logging.addLevelName(DEBUG2, 'DEBUG2') + logging.addLevelName(DEBUG3, 'DEBUG3') + logging.addLevelName(DUMP_ENV, 'DUMP_ENV') + + class LogFormatter(logging.Formatter): + default_format = '%(message)s' + formats = { + 'root': default_format, + 'build': default_format, + 'prepare': '[{}] %(name)s: %(message)s'.format(os.getpid()), + 'cmd': '> %(message)s', + '': '%(name)s: %(message)s' + } + + def formatMessage(self, record): + prefix = record.name.rsplit('.') + self._style._fmt = self.formats.get(prefix[0], self.formats['']) + return super().formatMessage(record) + + tf_stderr_fd = 5 + log_stream = sys.stderr + if use_tf_stderr: + try: + if os.isatty(tf_stderr_fd): + log_stream = os.fdopen(tf_stderr_fd, mode='w') + except OSError: + pass + + log_handler = logging.StreamHandler(stream=log_stream) + log_handler.setFormatter(LogFormatter()) + + log.addHandler(log_handler) + log.setLevel(logging.INFO) + + +def dump_env(): + if log.isEnabledFor(DUMP_ENV): + log.debug('ENV: %s', json.dumps(dict(os.environ), indent=2)) + + +################################################################################ +# Backports + +def shlex_join(split_command): + """Return a shell-escaped string from *split_command*.""" + return ' '.join(shlex.quote(arg) for arg in split_command) + + +################################################################################ +# Common functions + +def abort(message): + """Exits with an error message.""" + log.error(message) + sys.exit(1) + + +@contextmanager +def cd(path, silent=False): + """Changes the working directory.""" + cwd = os.getcwd() + if not silent: + cmd_log.info('cd %s', shlex.quote(path)) + try: + os.chdir(path) + yield + finally: + os.chdir(cwd) + + +@contextmanager +def tempdir(dir=None): + """Creates a temporary directory and then deletes it afterwards.""" + prefix = 'terraform-aws-lambda-' + path = tempfile.mkdtemp(prefix=prefix, dir=dir) + cmd_log.info('mktemp -d %sXXXXXXXX # %s', prefix, shlex.quote(path)) + try: + yield path + finally: + shutil.rmtree(path) + + +def list_files(top_path, log=None): + """ + Returns a sorted list of all files in a directory. + """ + + if log: + log = log.getChild('ls') + + results = [] + + for root, dirs, files in os.walk(top_path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + for file_name in files: + file_path = os.path.join(root, file_name) + relative_path = os.path.relpath(file_path, top_path) + results.append(relative_path) + if log: + log.debug(relative_path) + + results.sort() + return results + + +def dataclass(name): + typ = type(name, (dict,), { + '__getattr__': lambda self, x: self.get(x), + '__init__': lambda self, **k: self.update(k), + }) + return typ + + +def datatree(name, **fields): + def decode_json(k, v): + if v and isinstance(v, str) and v[0] in '"[{': + try: + o = json.loads(v) + if isinstance(o, dict): + return dataclass(k)(**o) + return o + except json.JSONDecodeError: + pass + return v + + return dataclass(name)(**dict((( + k, datatree(k, **v) if isinstance(v, dict) else decode_json(k, v)) + for k, v in fields.items()))) + + +def timestamp_now_ns(): + timestamp = datetime.datetime.now().timestamp() + timestamp = int(timestamp * 10 ** 7) * 10 ** 2 + return timestamp + + +def source_code_hash(bytes): + return b64encode(hashlib.sha256(bytes).digest()).decode() + + +def yesno_bool(val): + if val is None: + return + if isinstance(val, bool): + return val + if isinstance(val, int): + return bool(val) + if isinstance(val, str): + if val.isnumeric(): + return bool(int(val)) + val = val.lower() + if val in ('true', 'yes', 'y'): + return True + elif val in ('false', 'no', 'n'): + return False + else: + raise ValueError("Unsupported value: %s" % val) + return False + + +################################################################################ +# Packaging functions + +def emit_dir_content(base_dir): + for root, dirs, files in os.walk(base_dir, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + if root != base_dir: + yield os.path.normpath(root) + for name in files: + yield os.path.normpath(os.path.join(root, name)) + + +def generate_content_hash(source_paths, + hash_func=hashlib.sha256, log=None): + """ + Generate a content hash of the source paths. + """ + + if log: + log = log.getChild('hash') + + hash_obj = hash_func() + + for source_path in source_paths: + if os.path.isdir(source_path): + source_dir = source_path + _log = log if log.isEnabledFor(DEBUG3) else None + for source_file in list_files(source_dir, log=_log): + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(os.path.join(source_dir, source_file)) + else: + source_dir = os.path.dirname(source_path) + source_file = os.path.relpath(source_path, source_dir) + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(source_path) + + return hash_obj + + +def update_hash(hash_obj, file_root, file_path): + """ + Update a hashlib object with the relative path and contents of a file. + """ + + relative_path = os.path.join(file_root, file_path) + hash_obj.update(relative_path.encode()) + + with open(relative_path, 'rb') as open_file: + while True: + data = open_file.read(1024 * 8) + if not data: + break + hash_obj.update(data) + + +class ZipWriteStream: + """""" + + def __init__(self, zip_filename, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=None, + timestamp=None): + + self.timestamp = timestamp + self.filename = zip_filename + + if not (self.filename and isinstance(self.filename, str)): + raise ValueError('Zip file path must be provided') + + self._tmp_filename = None + self._compress_type = compress_type + self._compresslevel = compresslevel + self._zip = None + + self._log = logging.getLogger('zip') + + def open(self): + if self._tmp_filename: + raise zipfile.BadZipFile("ZipStream object can't be reused") + self._ensure_base_path(self.filename) + self._tmp_filename = '{}.tmp'.format(self.filename) + self._log.info("creating '%s' archive", self.filename) + self._zip = zipfile.ZipFile(self._tmp_filename, "w", + self._compress_type) + return self + + def close(self, failed=False): + self._zip.close() + self._zip = None + if failed: + os.unlink(self._tmp_filename) + else: + os.replace(self._tmp_filename, self.filename) + + def __enter__(self): + return self.open() + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None: + self._log.exception("Error during zip archive creation") + self.close(failed=True) + raise SystemExit(1) + self.close() + + def _ensure_open(self): + if self._zip is not None: + return True + if self._tmp_filename: + raise zipfile.BadZipFile("ZipWriteStream object can't be reused") + raise zipfile.BadZipFile('ZipWriteStream should be opened first') + + def _ensure_base_path(self, zip_filename): + archive_dir = os.path.dirname(zip_filename) + + if archive_dir and not os.path.exists(archive_dir): + self._log.info("creating %s", archive_dir) + os.makedirs(archive_dir, exist_ok=True) + + def write_dirs(self, *base_dirs, prefix=None, timestamp=None): + """ + Writes a directory content to a prefix inside of a zip archive + """ + self._ensure_open() + for base_dir in base_dirs: + self._log.info("adding content of directory: %s", base_dir) + for path in emit_dir_content(base_dir): + arcname = os.path.relpath(path, base_dir) + self._write_file(path, prefix, arcname, timestamp) + + def write_files(self, files_stream, prefix=None, timestamp=None): + """ + Expects just files stream, directories will be created automatically + """ + self._ensure_open() + for file_path, arcname in files_stream: + self._write_file(file_path, prefix, arcname, timestamp) + + def write_file(self, file_path, prefix=None, name=None, timestamp=None): + """ + Reads a file and writes it to a prefix + or a full qualified name in a zip archive + """ + self._ensure_open() + self._write_file(file_path, prefix, name, timestamp) + + def _write_file(self, file_path, prefix=None, name=None, timestamp=None): + arcname = name if name else os.path.basename(file_path) + if prefix: + arcname = os.path.join(prefix, arcname) + zinfo = self._make_zinfo_from_file(file_path, arcname) + if zinfo.is_dir(): + self._log.info("adding: %s/", arcname) + else: + self._log.info("adding: %s", arcname) + if timestamp is None: + timestamp = self.timestamp + date_time = self._timestamp_to_date_time(timestamp) + if date_time: + self._update_zinfo(zinfo, date_time=date_time) + self._write_zinfo(zinfo, file_path) + + def write_file_obj(self, file_path, data, prefix=None, timestamp=None): + """ + Write a data to a zip archive by a full qualified archive file path + """ + self._ensure_open() + raise NotImplementedError + + def _write_zinfo(self, zinfo, filename, + compress_type=None, compresslevel=None): + self._ensure_open() + + zip = self._zip + + if not zip.fp: + raise ValueError( + "Attempt to write to ZIP archive that was already closed") + if zip._writing: + raise ValueError( + "Can't write to ZIP archive while an open writing handle exists" + ) + + if zinfo.is_dir(): + zinfo.compress_size = 0 + zinfo.CRC = 0 + else: + if compress_type is not None: + zinfo.compress_type = compress_type + else: + zinfo.compress_type = self._compress_type + + if PY37: + if compresslevel is not None: + zinfo._compresslevel = compresslevel + else: + zinfo._compresslevel = self._compresslevel + + if zinfo.is_dir(): + with zip._lock: + if zip._seekable: + zip.fp.seek(zip.start_dir) + zinfo.header_offset = zip.fp.tell() # Start of header bytes + if zinfo.compress_type == zipfile.ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= 0x02 + + zip._writecheck(zinfo) + zip._didModify = True + + zip.filelist.append(zinfo) + zip.NameToInfo[zinfo.filename] = zinfo + zip.fp.write(zinfo.FileHeader(False)) + zip.start_dir = zip.fp.tell() + else: + with open(filename, "rb") as src, zip.open(zinfo, 'w') as dest: + shutil.copyfileobj(src, dest, 1024 * 8) + + def _make_zinfo_from_file(self, filename, arcname=None): + if PY38: + zinfo_func = zipfile.ZipInfo.from_file + strict_timestamps = self._zip._strict_timestamps + else: + zinfo_func = self._zinfo_from_file + strict_timestamps = True + + return zinfo_func(filename, arcname, + strict_timestamps=strict_timestamps) + + @staticmethod + def _update_zinfo(zinfo, date_time): + zinfo.date_time = date_time + + # Borrowed from python 3.8 zipfile.py library + # due to the need of strict_timestamps functionality. + @staticmethod + def _zinfo_from_file(filename, arcname=None, *, strict_timestamps=True): + """Construct an appropriate ZipInfo for a file on the filesystem. + + filename should be the path to a file or directory on the filesystem. + + arcname is the name which it will have within the archive (by default, + this will be the same as filename, but without a drive letter and with + leading path separators removed). + """ + if isinstance(filename, os.PathLike): + filename = os.fspath(filename) + st = os.stat(filename) + isdir = stat.S_ISDIR(st.st_mode) + mtime = time.localtime(st.st_mtime) + date_time = mtime[0:6] + if strict_timestamps and date_time[0] < 1980: + date_time = (1980, 1, 1, 0, 0, 0) + elif strict_timestamps and date_time[0] > 2107: + date_time = (2107, 12, 31, 23, 59, 59) + # Create ZipInfo instance to store file information + if arcname is None: + arcname = filename + arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) + while arcname[0] in (os.sep, os.altsep): + arcname = arcname[1:] + if isdir: + arcname += '/' + zinfo = zipfile.ZipInfo(arcname, date_time) + zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes + if isdir: + zinfo.file_size = 0 + zinfo.external_attr |= 0x10 # MS-DOS directory flag + else: + zinfo.file_size = st.st_size + + return zinfo + + @staticmethod + def _timestamp_to_date_time(timestamp): + def str_int_to_timestamp(s): + min_zip_ts = datetime.datetime(1980, 1, 1).timestamp() + ts = int(s) + if ts < min_zip_ts: + return min_zip_ts + deg = len(str(int(s))) - 9 + if deg < 0: + ts = ts * 10 ** deg + return ts + + date_time = None + if timestamp is not None: + if isinstance(timestamp, str): + if timestamp.isnumeric(): + timestamp = str_int_to_timestamp(timestamp) + else: + timestamp = float(timestamp) + elif isinstance(timestamp, int): + timestamp = str_int_to_timestamp(str(timestamp)) + + date_time = datetime.datetime.fromtimestamp(timestamp).timetuple() + date_time = date_time[:6] + if date_time[0] < 1980: + raise ValueError('ZIP does not support timestamps before 1980') + return date_time + + +################################################################################ +# Building + +def patterns_list(args, patterns): + _filter = str.strip + if args.pattern_comments: + def _filter(x): + x = x.strip() + p = re.search("^(.*?)[ \t]*(?:[ \t]{2}#.*)?$", x).group(1).rstrip() + if p.startswith('#'): + return + if p: + return p + if isinstance(patterns, str): + return list(filter(None, map(_filter, patterns.splitlines()))) + return patterns + + +class ZipContentFilter: + """""" + + def __init__(self, args): + self._args = args + self._rules = None + self._excludes = set() + self._log = logging.getLogger('zip') + + def compile(self, patterns): + rules = [] + for p in patterns_list(self._args, patterns): + self._log.debug("filter pattern: %s", p) + if p.startswith('!'): + r = re.compile(p[1:]) + rules.append((operator.not_, r)) + else: + r = re.compile(p) + rules.append((None, r)) + self._rules = rules + + def filter(self, path, prefix=None): + path = os.path.normpath(path) + if prefix: + prefix = os.path.normpath(prefix) + rules = self._rules + + def norm_path(path, root, filename=None): + op = os.path.join(root, filename) if filename else root + p = os.path.relpath(root, path) + if prefix: + p = os.path.join(prefix, p) + if filename: + p = os.path.normpath(os.path.join(p, filename)) + return op, p + return op, p + os.sep + + def apply(path): + d = True + for r in rules: + op, regex = r + neg = op is operator.not_ + m = regex.fullmatch(path) + if neg and m: + d = False + elif m: + d = True + if d: + return path + + def emit_dir(dpath, opath): + if apply(dpath): + yield opath + else: + self._log.debug('skip: %s', dpath) + + def emit_file(fpath, opath): + if apply(fpath): + yield opath + else: + self._log.debug('skip: %s', fpath) + + if os.path.isfile(path): + name = os.path.basename(path) + if prefix: + name = os.path.join(prefix, name) + if apply(name): + yield path + else: + for root, dirs, files in os.walk(path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + o, d = norm_path(path, root) + # log.info('od: %s %s', o, d) + if root != path: + yield from emit_dir(d, o) + for name in files: + o, f = norm_path(path, root, name) + # log.info('of: %s %s', o, f) + yield from emit_file(f, o) + + +class BuildPlanManager: + """""" + + def __init__(self, args, log=None): + self._args = args + self._source_paths = None + self._log = log or logging.root + + def hash(self, extra_paths): + if not self._source_paths: + raise ValueError('BuildPlanManager.plan() should be called first') + + content_hash_paths = self._source_paths + extra_paths + + # Generate a hash based on file names and content. Also use the + # runtime value, build command, and content of the build paths + # because they can have an effect on the resulting archive. + self._log.debug("Computing content hash on files...") + content_hash = generate_content_hash(content_hash_paths, + log=self._log) + return content_hash + + def plan(self, source_path, query): + claims = source_path + if not isinstance(source_path, list): + claims = [source_path] + + source_paths = [] + build_plan = [] + + step = lambda *x: build_plan.append(x) + hash = source_paths.append + + def pip_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'requirements.txt') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('pip', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def npm_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'package.json') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('npm', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def commands_step(path, commands): + if not commands: + return + + if isinstance(commands, str): + commands = map(str.strip, commands.splitlines()) + + if path: + path = os.path.normpath(path) + batch = [] + for c in commands: + if isinstance(c, str): + if c.startswith(':zip'): + if path: + hash(path) + else: + # If path doesn't defined for a block with + # commands it will be set to Terraform's + # current working directory + # NB: cwd may vary when using Terraform 0.14+ like: + # `terraform -chdir=...` + path = query.paths.cwd + if batch: + step('sh', path, '\n'.join(batch)) + batch.clear() + c = shlex.split(c) + if len(c) == 3: + _, _path, prefix = c + prefix = prefix.strip() + _path = os.path.normpath(os.path.join(path, _path)) + step('zip:embedded', _path, prefix) + elif len(c) == 2: + prefix = None + _, _path = c + step('zip:embedded', _path, prefix) + elif len(c) == 1: + prefix = None + step('zip:embedded', path, prefix) + else: + raise ValueError( + ":zip invalid call signature, use: " + "':zip [path [prefix_in_zip]]'") + else: + batch.append(c) + + for claim in claims: + if isinstance(claim, str): + path = claim + if not os.path.exists(path): + abort('Could not locate source_path "{path}". Paths are relative to directory where `terraform plan` is being run ("{pwd}")'.format( + path=path, + pwd=os.getcwd() + )) + runtime = query.runtime + if runtime.startswith('python'): + pip_requirements_step( + os.path.join(path, 'requirements.txt')) + elif runtime.startswith('nodejs'): + npm_requirements_step( + os.path.join(path, 'package.json')) + step('zip', path, None) + hash(path) + + elif isinstance(claim, dict): + path = claim.get('path') + patterns = claim.get('patterns') + commands = claim.get('commands') + if patterns: + step('set:filter', patterns_list(self._args, patterns)) + if commands: + commands_step(path, commands) + else: + prefix = claim.get('prefix_in_zip') + pip_requirements = claim.get('pip_requirements') + npm_requirements = claim.get('npm_package_json') + runtime = claim.get('runtime', query.runtime) + + if pip_requirements and runtime.startswith('python'): + if isinstance(pip_requirements, bool) and path: + pip_requirements_step(path, prefix, required=True, tmp_dir=claim.get('pip_tmp_dir')) + else: + pip_requirements_step(pip_requirements, prefix, + required=True, tmp_dir=claim.get('pip_tmp_dir')) + + if npm_requirements and runtime.startswith('nodejs'): + if isinstance(npm_requirements, bool) and path: + npm_requirements_step(path, prefix, required=True, tmp_dir=claim.get('npm_tmp_dir')) + else: + npm_requirements_step(npm_requirements, prefix, + required=True, tmp_dir=claim.get('npm_tmp_dir')) + + if path: + step('zip', path, prefix) + if patterns: + # Take patterns into account when computing hash + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + + for path_from_pattern in pf.filter(path, prefix): + hash(path_from_pattern) + else: + hash(path) + + if patterns: + step('clear:filter') + else: + raise ValueError( + 'Unsupported source_path item: {}'.format(claim)) + + self._source_paths = source_paths + return build_plan + + def execute(self, build_plan, zip_stream, query): + zs = zip_stream + sh_work_dir = None + pf = None + + for action in build_plan: + cmd = action[0] + if cmd.startswith('zip'): + ts = 0 if cmd == 'zip:embedded' else None + source_path, prefix = action[1:] + if sh_work_dir: + if source_path != sh_work_dir: + if not os.path.isfile(source_path): + source_path = sh_work_dir + if os.path.isdir(source_path): + if pf: + self._zip_write_with_filter(zs, pf, source_path, prefix, + timestamp=ts) + else: + zs.write_dirs(source_path, prefix=prefix, timestamp=ts) + else: + zs.write_file(source_path, prefix=prefix, timestamp=ts) + elif cmd == 'pip': + runtime, pip_requirements, prefix, tmp_dir = action[1:] + with install_pip_requirements(query, pip_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'npm': + runtime, npm_requirements, prefix, tmp_dir = action[1:] + with install_npm_requirements(query, npm_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'sh': + r, w = os.pipe() + side_ch = os.fdopen(r) + path, script = action[1:] + script = "{}\npwd >&{}".format(script, w) + + p = subprocess.Popen(script, shell=True, cwd=path, + pass_fds=(w,)) + os.close(w) + sh_work_dir = side_ch.read().strip() + p.wait() + log.info('WD: %s', sh_work_dir) + side_ch.close() + elif cmd == 'set:filter': + patterns = action[1] + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + elif cmd == 'clear:filter': + pf = None + + @staticmethod + def _zip_write_with_filter(zip_stream, path_filter, source_path, prefix, + timestamp=None): + for path in path_filter.filter(source_path, prefix): + if os.path.isdir(source_path): + arcname = os.path.relpath(path, source_path) + else: + arcname = os.path.basename(path) + zip_stream.write_file(path, prefix, arcname, timestamp=timestamp) + + +@contextmanager +def install_pip_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + docker = query.docker + temp_dir = query.temp_dir + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + working_dir = os.getcwd() + + log.info('Installing python requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + python_exec = runtime + subproc_env = None + + if not docker: + if WINDOWS: + python_exec = 'python.exe' + elif OSX: + # Workaround for OSX when XCode command line tools' + # python becomes the main system python interpreter + os_path = '{}:/Library/Developer/CommandLineTools' \ + '/usr/bin'.format(os.environ['PATH']) + subproc_env = os.environ.copy() + subproc_env['PATH'] = os_path + + # Install dependencies into the temporary directory. + with cd(temp_dir): + pip_command = [ + python_exec, '-m', 'pip', + 'install', '--no-compile', + '--prefix=', '--target=.', + '--requirement={}'.format(requirements_filename), + ] + if docker: + with_ssh_agent = docker.with_ssh_agent + pip_cache_dir = docker.docker_pip_cache + if pip_cache_dir: + if isinstance(pip_cache_dir, str): + pip_cache_dir = os.path.abspath( + os.path.join(working_dir, pip_cache_dir)) + else: + pip_cache_dir = os.path.abspath(os.path.join( + working_dir, artifacts_dir, 'cache/pip')) + + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(pip_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent, + pip_cache_dir=pip_cache_dir, + )) + else: + cmd_log.info(shlex_join(pip_command)) + log_handler and log_handler.flush() + try: + check_call(pip_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Python interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +@contextmanager +def install_npm_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + temp_dir = query.temp_dir + docker = query.docker + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + log.info('Installing npm requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + subproc_env = None + if not docker and OSX: + subproc_env = os.environ.copy() + + # Install dependencies into the temporary directory. + with cd(temp_dir): + npm_command = ['npm', 'install'] + if docker: + with_ssh_agent = docker.with_ssh_agent + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(npm_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent + )) + else: + cmd_log.info(shlex_join(npm_command)) + log_handler and log_handler.flush() + try: + check_call(npm_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Nodejs interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +def docker_image_id_command(tag): + """""" + docker_cmd = ['docker', 'images', '--format={{.ID}}', tag] + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_build_command(tag=None, docker_file=None, build_root=False): + """""" + if not (build_root or docker_file): + raise ValueError('docker_build_root or docker_file must be provided') + + docker_cmd = ['docker', 'build'] + + if tag: + docker_cmd.extend(['--tag', tag]) + else: + raise ValueError('docker_image must be specified') + if not build_root: + build_root = os.path.dirname(docker_file) + if docker_file: + docker_cmd.extend(['--file', docker_file]) + docker_cmd.append(build_root) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_run_command(build_root, command, runtime, + image=None, shell=None, ssh_agent=False, + interactive=False, pip_cache_dir=None): + """""" + if platform.system() not in ('Linux', 'Darwin'): + raise RuntimeError("Unsupported platform for docker building") + + workdir = '/var/task' + + docker_cmd = ['docker', 'run', '--rm', '-w', workdir] + + if interactive: + docker_cmd.append('-it') + + bind_path = os.path.abspath(build_root) + docker_cmd.extend(['-v', "{}:{}:z".format(bind_path, workdir)]) + + home = os.environ['HOME'] + docker_cmd.extend([ + # '-v', '{}/.ssh/id_rsa:/root/.ssh/id_rsa:z'.format(home), + '-v', '{}/.ssh/known_hosts:/root/.ssh/known_hosts:z'.format(home), + ]) + + if ssh_agent: + if platform.system() == 'Darwin': + # https://docs.docker.com/docker-for-mac/osxfs/#ssh-agent-forwarding + docker_cmd.extend([ + '--mount', 'type=bind,' + 'src=/run/host-services/ssh-auth.sock,' + 'target=/run/host-services/ssh-auth.sock', + '-e', 'SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock', + ]) + elif platform.system() == 'Linux': + sock = os.environ['SSH_AUTH_SOCK'] # TODO: Handle missing env var + docker_cmd.extend([ + '-v', '{}:/tmp/ssh_sock:z'.format(sock), + '-e', 'SSH_AUTH_SOCK=/tmp/ssh_sock', + ]) + + if platform.system() in ('Linux', 'Darwin'): + if pip_cache_dir: + pip_cache_dir = os.path.abspath(pip_cache_dir) + docker_cmd.extend([ + '-v', '{}:/root/.cache/pip:z'.format(pip_cache_dir), + ]) + + if not image: + image = 'public.ecr.aws/sam/build-{}'.format(runtime) + + docker_cmd.extend(['--entrypoint', '']) + + docker_cmd.append(image) + + assert isinstance(command, list) + if shell: + if not isinstance(shell, str): + shell = '/bin/sh' + docker_cmd.extend([shell, '-c']) + docker_cmd.extend(command) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +################################################################################ +# Commands + +def prepare_command(args): + """ + Generates a content hash of the source_path, which is used to determine if + the Lambda code has changed, ignoring file modification and access times. + + Outputs a filename and a command to run if the archive needs to be built. + """ + + log = logging.getLogger('prepare') + + # Load the query. + query_data = json.load(sys.stdin) + + dump_env() + if log.isEnabledFor(DEBUG2): + if log.isEnabledFor(DEBUG3): + log.debug('QUERY: %s', json.dumps(query_data, indent=2)) + else: + log_excludes = ('source_path', 'hash_extra_paths', 'paths') + qd = {k: v for k, v in query_data.items() if k not in log_excludes} + log.debug('QUERY (excerpt): %s', json.dumps(qd, indent=2)) + + query = datatree('prepare_query', **query_data) + + tf_paths = query.paths + runtime = query.runtime + function_name = query.function_name + artifacts_dir = query.artifacts_dir + hash_extra_paths = query.hash_extra_paths + source_path = query.source_path + hash_extra = query.hash_extra + recreate_missing_package = yesno_bool(args.recreate_missing_package if args.recreate_missing_package is not None else query.recreate_missing_package) + docker = query.docker + + bpm = BuildPlanManager(args, log=log) + build_plan = bpm.plan(source_path, query) + + if log.isEnabledFor(DEBUG2): + log.debug('BUILD_PLAN: %s', json.dumps(build_plan, indent=2)) + + # Expand a Terraform path. references + hash_extra_paths = [p.format(path=tf_paths) for p in hash_extra_paths] + + content_hash = bpm.hash(hash_extra_paths) + content_hash.update(json.dumps(build_plan, sort_keys=True).encode()) + content_hash.update(runtime.encode()) + content_hash.update(hash_extra.encode()) + content_hash = content_hash.hexdigest() + + # Generate a unique filename based on the hash. + filename = os.path.join(artifacts_dir, '{}.zip'.format(content_hash)) + + # Compute timestamp trigger + was_missing = False + filename_path = os.path.join(os.getcwd(), filename) + if recreate_missing_package: + if os.path.exists(filename_path): + st = os.stat(filename_path) + timestamp = st.st_mtime_ns + else: + timestamp = timestamp_now_ns() + was_missing = True + else: + timestamp = "" + + # Replace variables in the build command with calculated values. + build_data = { + 'filename': filename, + 'runtime': runtime, + 'artifacts_dir': artifacts_dir, + 'build_plan': build_plan, + } + if docker: + build_data['docker'] = docker + + build_plan = json.dumps(build_data) + build_plan_filename = os.path.join(artifacts_dir, + '{}.plan.json'.format(content_hash)) + if not os.path.exists(artifacts_dir): + os.makedirs(artifacts_dir, exist_ok=True) + with open(build_plan_filename, 'w') as f: + f.write(build_plan) + + # Output the result to Terraform. + json.dump({ + 'filename': filename, + 'build_plan': build_plan, + 'build_plan_filename': build_plan_filename, + 'timestamp': str(timestamp), + 'was_missing': 'true' if was_missing else 'false', + }, sys.stdout, indent=2) + sys.stdout.write('\n') + + +def build_command(args): + """ + Builds a zip file from the source_dir or source_file. + Installs dependencies with pip or npm automatically. + """ + + log = logging.getLogger('build') + + dump_env() + if log.isEnabledFor(DEBUG2): + log.debug('CMD: python3 %s', shlex_join(sys.argv)) + + with open(args.build_plan_file) as f: + query_data = json.load(f) + query = datatree('build_query', **query_data) + + runtime = query.runtime + filename = query.filename + build_plan = query.build_plan + _timestamp = args.zip_file_timestamp + + timestamp = 0 + if _timestamp.isnumeric(): + timestamp = int(_timestamp) + + if os.path.exists(filename) and not args.force: + log.info('Reused: %s', shlex.quote(filename)) + return + + # Zip up the build plan and write it to the target filename. + # This will be used by the Lambda function as the source code package. + with ZipWriteStream(filename) as zs: + bpm = BuildPlanManager(args, log=log) + bpm.execute(build_plan, zs, query) + + os.utime(filename, ns=(timestamp, timestamp)) + log.info('Created: %s', shlex.quote(filename)) + if log.isEnabledFor(logging.DEBUG): + with open(filename, 'rb') as f: + log.info('Base64sha256: %s', source_code_hash(f.read())) + + +def add_hidden_commands(sub_parsers): + sp = sub_parsers + + def hidden_parser(name, **kwargs): + p = sp.add_parser(name, **kwargs) + sp._choices_actions.pop() # XXX: help=argparse.SUPPRESS - doesn't work + return p + + p = hidden_parser('docker', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_run_command( + args.build_root, args.docker_command, args.runtime, interactive=True))) + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_command', help='A docker container command', + metavar='command', nargs=argparse.REMAINDER) + p.add_argument('-r', '--runtime', help='A docker image runtime', + default='python3.8') + + p = hidden_parser('docker-image', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_build_command( + args.build_root, args.docker_file, args.tag))) + p.add_argument('-t', '--tag', help='A docker image tag') + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_file', help='A docker file path', + nargs=argparse.OPTIONAL) + + def zip_cmd(args): + if args.verbose: + log.setLevel(logging.DEBUG) + with ZipWriteStream(args.zipfile) as zs: + zs.write_dirs(*args.dir, timestamp=args.timestamp) + if log.isEnabledFor(logging.DEBUG): + zipinfo = shutil.which('zipinfo') + if zipinfo: + log.debug('-' * 80) + subprocess.call([zipinfo, args.zipfile]) + log.debug('-' * 80) + log.debug('Source code hash: %s', + source_code_hash(open(args.zipfile, 'rb').read())) + + p = hidden_parser('zip', help='Zip folder with provided files timestamp') + p.set_defaults(command=zip_cmd) + p.add_argument('zipfile', help='Path to a zip file') + p.add_argument('dir', nargs=argparse.ONE_OR_MORE, + help='Path to a directory for packaging') + p.add_argument('-t', '--timestamp', type=int, + help='A timestamp to override for all zip members') + p.add_argument('-v', '--verbose', action='store_true') + + p = hidden_parser('hash', help='Generate content hash for a file') + p.set_defaults( + command=lambda args: print(source_code_hash(args.file.read()))) + p.add_argument('file', help='Path to a file', type=argparse.FileType('rb')) + + +def args_parser(): + ap = argparse.ArgumentParser() + ap.set_defaults(command=lambda _: ap.print_usage()) + sp = ap.add_subparsers(metavar="COMMAND") + + p = sp.add_parser('prepare', + help='compute a filename hash for a zip archive') + p.set_defaults(command=prepare_command) + + p = sp.add_parser('build', + help='build and pack to a zip archive') + p.set_defaults(command=build_command) + p.add_argument('--force', action='store_true', + help='Force rebuilding even if a zip artifact exists') + p.add_argument('-t', '--timestamp', + dest='zip_file_timestamp', required=True, + help='A zip file timestamp generated by the prepare command') + p.add_argument('build_plan_file', metavar='PLAN_FILE', + help='A build plan file provided by the prepare command') + add_hidden_commands(sp) + return ap + + +def main(): + ns = argparse.Namespace( + pattern_comments=yesno_bool(os.environ.get( + 'TF_LAMBDA_PACKAGE_PATTERN_COMMENTS', False)), + recreate_missing_package=os.environ.get( + 'TF_RECREATE_MISSING_LAMBDA_PACKAGE', None), + log_level=os.environ.get('TF_LAMBDA_PACKAGE_LOG_LEVEL', 'INFO'), + ) + + p = args_parser() + args = p.parse_args(namespace=ns) + + if args.command is prepare_command: + configure_logging(use_tf_stderr=True) + else: + configure_logging() + + if args.log_level: + ll = logging._nameToLevel.get(args.log_level) + if ll and logging._checkLevel(ll): + logging.root.setLevel(args.log_level) + + exit(args.command(args)) + + +if __name__ == '__main__': + main() diff --git a/modules/aws-lambda/package 4.py b/modules/aws-lambda/package 4.py new file mode 100644 index 0000000..1b96608 --- /dev/null +++ b/modules/aws-lambda/package 4.py @@ -0,0 +1,1391 @@ +# coding: utf-8 + +import sys + +if sys.version_info < (3, 6): + raise RuntimeError("A python version 3.6 or newer is required") + +import os +import re +import time +import stat +import json +import shlex +import shutil +import hashlib +import zipfile +import argparse +import datetime +import tempfile +import operator +import platform +import subprocess +from subprocess import check_call, check_output +from contextlib import contextmanager +from base64 import b64encode +import logging + +PY38 = sys.version_info >= (3, 8) +PY37 = sys.version_info >= (3, 7) +PY36 = sys.version_info >= (3, 6) + +WINDOWS = platform.system() == 'Windows' +OSX = platform.system() == 'Darwin' + +################################################################################ +# Logging + +DEBUG2 = 9 +DEBUG3 = 8 +DUMP_ENV = 1 + +log_handler = None +log = logging.getLogger() +cmd_log = logging.getLogger('cmd') + + +def configure_logging(use_tf_stderr=False): + global log_handler + + logging.addLevelName(DEBUG2, 'DEBUG2') + logging.addLevelName(DEBUG3, 'DEBUG3') + logging.addLevelName(DUMP_ENV, 'DUMP_ENV') + + class LogFormatter(logging.Formatter): + default_format = '%(message)s' + formats = { + 'root': default_format, + 'build': default_format, + 'prepare': '[{}] %(name)s: %(message)s'.format(os.getpid()), + 'cmd': '> %(message)s', + '': '%(name)s: %(message)s' + } + + def formatMessage(self, record): + prefix = record.name.rsplit('.') + self._style._fmt = self.formats.get(prefix[0], self.formats['']) + return super().formatMessage(record) + + tf_stderr_fd = 5 + log_stream = sys.stderr + if use_tf_stderr: + try: + if os.isatty(tf_stderr_fd): + log_stream = os.fdopen(tf_stderr_fd, mode='w') + except OSError: + pass + + log_handler = logging.StreamHandler(stream=log_stream) + log_handler.setFormatter(LogFormatter()) + + log.addHandler(log_handler) + log.setLevel(logging.INFO) + + +def dump_env(): + if log.isEnabledFor(DUMP_ENV): + log.debug('ENV: %s', json.dumps(dict(os.environ), indent=2)) + + +################################################################################ +# Backports + +def shlex_join(split_command): + """Return a shell-escaped string from *split_command*.""" + return ' '.join(shlex.quote(arg) for arg in split_command) + + +################################################################################ +# Common functions + +def abort(message): + """Exits with an error message.""" + log.error(message) + sys.exit(1) + + +@contextmanager +def cd(path, silent=False): + """Changes the working directory.""" + cwd = os.getcwd() + if not silent: + cmd_log.info('cd %s', shlex.quote(path)) + try: + os.chdir(path) + yield + finally: + os.chdir(cwd) + + +@contextmanager +def tempdir(dir=None): + """Creates a temporary directory and then deletes it afterwards.""" + prefix = 'terraform-aws-lambda-' + path = tempfile.mkdtemp(prefix=prefix, dir=dir) + cmd_log.info('mktemp -d %sXXXXXXXX # %s', prefix, shlex.quote(path)) + try: + yield path + finally: + shutil.rmtree(path) + + +def list_files(top_path, log=None): + """ + Returns a sorted list of all files in a directory. + """ + + if log: + log = log.getChild('ls') + + results = [] + + for root, dirs, files in os.walk(top_path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + for file_name in files: + file_path = os.path.join(root, file_name) + relative_path = os.path.relpath(file_path, top_path) + results.append(relative_path) + if log: + log.debug(relative_path) + + results.sort() + return results + + +def dataclass(name): + typ = type(name, (dict,), { + '__getattr__': lambda self, x: self.get(x), + '__init__': lambda self, **k: self.update(k), + }) + return typ + + +def datatree(name, **fields): + def decode_json(k, v): + if v and isinstance(v, str) and v[0] in '"[{': + try: + o = json.loads(v) + if isinstance(o, dict): + return dataclass(k)(**o) + return o + except json.JSONDecodeError: + pass + return v + + return dataclass(name)(**dict((( + k, datatree(k, **v) if isinstance(v, dict) else decode_json(k, v)) + for k, v in fields.items()))) + + +def timestamp_now_ns(): + timestamp = datetime.datetime.now().timestamp() + timestamp = int(timestamp * 10 ** 7) * 10 ** 2 + return timestamp + + +def source_code_hash(bytes): + return b64encode(hashlib.sha256(bytes).digest()).decode() + + +def yesno_bool(val): + if val is None: + return + if isinstance(val, bool): + return val + if isinstance(val, int): + return bool(val) + if isinstance(val, str): + if val.isnumeric(): + return bool(int(val)) + val = val.lower() + if val in ('true', 'yes', 'y'): + return True + elif val in ('false', 'no', 'n'): + return False + else: + raise ValueError("Unsupported value: %s" % val) + return False + + +################################################################################ +# Packaging functions + +def emit_dir_content(base_dir): + for root, dirs, files in os.walk(base_dir, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + if root != base_dir: + yield os.path.normpath(root) + for name in files: + yield os.path.normpath(os.path.join(root, name)) + + +def generate_content_hash(source_paths, + hash_func=hashlib.sha256, log=None): + """ + Generate a content hash of the source paths. + """ + + if log: + log = log.getChild('hash') + + hash_obj = hash_func() + + for source_path in source_paths: + if os.path.isdir(source_path): + source_dir = source_path + _log = log if log.isEnabledFor(DEBUG3) else None + for source_file in list_files(source_dir, log=_log): + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(os.path.join(source_dir, source_file)) + else: + source_dir = os.path.dirname(source_path) + source_file = os.path.relpath(source_path, source_dir) + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(source_path) + + return hash_obj + + +def update_hash(hash_obj, file_root, file_path): + """ + Update a hashlib object with the relative path and contents of a file. + """ + + relative_path = os.path.join(file_root, file_path) + hash_obj.update(relative_path.encode()) + + with open(relative_path, 'rb') as open_file: + while True: + data = open_file.read(1024 * 8) + if not data: + break + hash_obj.update(data) + + +class ZipWriteStream: + """""" + + def __init__(self, zip_filename, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=None, + timestamp=None): + + self.timestamp = timestamp + self.filename = zip_filename + + if not (self.filename and isinstance(self.filename, str)): + raise ValueError('Zip file path must be provided') + + self._tmp_filename = None + self._compress_type = compress_type + self._compresslevel = compresslevel + self._zip = None + + self._log = logging.getLogger('zip') + + def open(self): + if self._tmp_filename: + raise zipfile.BadZipFile("ZipStream object can't be reused") + self._ensure_base_path(self.filename) + self._tmp_filename = '{}.tmp'.format(self.filename) + self._log.info("creating '%s' archive", self.filename) + self._zip = zipfile.ZipFile(self._tmp_filename, "w", + self._compress_type) + return self + + def close(self, failed=False): + self._zip.close() + self._zip = None + if failed: + os.unlink(self._tmp_filename) + else: + os.replace(self._tmp_filename, self.filename) + + def __enter__(self): + return self.open() + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None: + self._log.exception("Error during zip archive creation") + self.close(failed=True) + raise SystemExit(1) + self.close() + + def _ensure_open(self): + if self._zip is not None: + return True + if self._tmp_filename: + raise zipfile.BadZipFile("ZipWriteStream object can't be reused") + raise zipfile.BadZipFile('ZipWriteStream should be opened first') + + def _ensure_base_path(self, zip_filename): + archive_dir = os.path.dirname(zip_filename) + + if archive_dir and not os.path.exists(archive_dir): + self._log.info("creating %s", archive_dir) + os.makedirs(archive_dir, exist_ok=True) + + def write_dirs(self, *base_dirs, prefix=None, timestamp=None): + """ + Writes a directory content to a prefix inside of a zip archive + """ + self._ensure_open() + for base_dir in base_dirs: + self._log.info("adding content of directory: %s", base_dir) + for path in emit_dir_content(base_dir): + arcname = os.path.relpath(path, base_dir) + self._write_file(path, prefix, arcname, timestamp) + + def write_files(self, files_stream, prefix=None, timestamp=None): + """ + Expects just files stream, directories will be created automatically + """ + self._ensure_open() + for file_path, arcname in files_stream: + self._write_file(file_path, prefix, arcname, timestamp) + + def write_file(self, file_path, prefix=None, name=None, timestamp=None): + """ + Reads a file and writes it to a prefix + or a full qualified name in a zip archive + """ + self._ensure_open() + self._write_file(file_path, prefix, name, timestamp) + + def _write_file(self, file_path, prefix=None, name=None, timestamp=None): + arcname = name if name else os.path.basename(file_path) + if prefix: + arcname = os.path.join(prefix, arcname) + zinfo = self._make_zinfo_from_file(file_path, arcname) + if zinfo.is_dir(): + self._log.info("adding: %s/", arcname) + else: + self._log.info("adding: %s", arcname) + if timestamp is None: + timestamp = self.timestamp + date_time = self._timestamp_to_date_time(timestamp) + if date_time: + self._update_zinfo(zinfo, date_time=date_time) + self._write_zinfo(zinfo, file_path) + + def write_file_obj(self, file_path, data, prefix=None, timestamp=None): + """ + Write a data to a zip archive by a full qualified archive file path + """ + self._ensure_open() + raise NotImplementedError + + def _write_zinfo(self, zinfo, filename, + compress_type=None, compresslevel=None): + self._ensure_open() + + zip = self._zip + + if not zip.fp: + raise ValueError( + "Attempt to write to ZIP archive that was already closed") + if zip._writing: + raise ValueError( + "Can't write to ZIP archive while an open writing handle exists" + ) + + if zinfo.is_dir(): + zinfo.compress_size = 0 + zinfo.CRC = 0 + else: + if compress_type is not None: + zinfo.compress_type = compress_type + else: + zinfo.compress_type = self._compress_type + + if PY37: + if compresslevel is not None: + zinfo._compresslevel = compresslevel + else: + zinfo._compresslevel = self._compresslevel + + if zinfo.is_dir(): + with zip._lock: + if zip._seekable: + zip.fp.seek(zip.start_dir) + zinfo.header_offset = zip.fp.tell() # Start of header bytes + if zinfo.compress_type == zipfile.ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= 0x02 + + zip._writecheck(zinfo) + zip._didModify = True + + zip.filelist.append(zinfo) + zip.NameToInfo[zinfo.filename] = zinfo + zip.fp.write(zinfo.FileHeader(False)) + zip.start_dir = zip.fp.tell() + else: + with open(filename, "rb") as src, zip.open(zinfo, 'w') as dest: + shutil.copyfileobj(src, dest, 1024 * 8) + + def _make_zinfo_from_file(self, filename, arcname=None): + if PY38: + zinfo_func = zipfile.ZipInfo.from_file + strict_timestamps = self._zip._strict_timestamps + else: + zinfo_func = self._zinfo_from_file + strict_timestamps = True + + return zinfo_func(filename, arcname, + strict_timestamps=strict_timestamps) + + @staticmethod + def _update_zinfo(zinfo, date_time): + zinfo.date_time = date_time + + # Borrowed from python 3.8 zipfile.py library + # due to the need of strict_timestamps functionality. + @staticmethod + def _zinfo_from_file(filename, arcname=None, *, strict_timestamps=True): + """Construct an appropriate ZipInfo for a file on the filesystem. + + filename should be the path to a file or directory on the filesystem. + + arcname is the name which it will have within the archive (by default, + this will be the same as filename, but without a drive letter and with + leading path separators removed). + """ + if isinstance(filename, os.PathLike): + filename = os.fspath(filename) + st = os.stat(filename) + isdir = stat.S_ISDIR(st.st_mode) + mtime = time.localtime(st.st_mtime) + date_time = mtime[0:6] + if strict_timestamps and date_time[0] < 1980: + date_time = (1980, 1, 1, 0, 0, 0) + elif strict_timestamps and date_time[0] > 2107: + date_time = (2107, 12, 31, 23, 59, 59) + # Create ZipInfo instance to store file information + if arcname is None: + arcname = filename + arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) + while arcname[0] in (os.sep, os.altsep): + arcname = arcname[1:] + if isdir: + arcname += '/' + zinfo = zipfile.ZipInfo(arcname, date_time) + zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes + if isdir: + zinfo.file_size = 0 + zinfo.external_attr |= 0x10 # MS-DOS directory flag + else: + zinfo.file_size = st.st_size + + return zinfo + + @staticmethod + def _timestamp_to_date_time(timestamp): + def str_int_to_timestamp(s): + min_zip_ts = datetime.datetime(1980, 1, 1).timestamp() + ts = int(s) + if ts < min_zip_ts: + return min_zip_ts + deg = len(str(int(s))) - 9 + if deg < 0: + ts = ts * 10 ** deg + return ts + + date_time = None + if timestamp is not None: + if isinstance(timestamp, str): + if timestamp.isnumeric(): + timestamp = str_int_to_timestamp(timestamp) + else: + timestamp = float(timestamp) + elif isinstance(timestamp, int): + timestamp = str_int_to_timestamp(str(timestamp)) + + date_time = datetime.datetime.fromtimestamp(timestamp).timetuple() + date_time = date_time[:6] + if date_time[0] < 1980: + raise ValueError('ZIP does not support timestamps before 1980') + return date_time + + +################################################################################ +# Building + +def patterns_list(args, patterns): + _filter = str.strip + if args.pattern_comments: + def _filter(x): + x = x.strip() + p = re.search("^(.*?)[ \t]*(?:[ \t]{2}#.*)?$", x).group(1).rstrip() + if p.startswith('#'): + return + if p: + return p + if isinstance(patterns, str): + return list(filter(None, map(_filter, patterns.splitlines()))) + return patterns + + +class ZipContentFilter: + """""" + + def __init__(self, args): + self._args = args + self._rules = None + self._excludes = set() + self._log = logging.getLogger('zip') + + def compile(self, patterns): + rules = [] + for p in patterns_list(self._args, patterns): + self._log.debug("filter pattern: %s", p) + if p.startswith('!'): + r = re.compile(p[1:]) + rules.append((operator.not_, r)) + else: + r = re.compile(p) + rules.append((None, r)) + self._rules = rules + + def filter(self, path, prefix=None): + path = os.path.normpath(path) + if prefix: + prefix = os.path.normpath(prefix) + rules = self._rules + + def norm_path(path, root, filename=None): + op = os.path.join(root, filename) if filename else root + p = os.path.relpath(root, path) + if prefix: + p = os.path.join(prefix, p) + if filename: + p = os.path.normpath(os.path.join(p, filename)) + return op, p + return op, p + os.sep + + def apply(path): + d = True + for r in rules: + op, regex = r + neg = op is operator.not_ + m = regex.fullmatch(path) + if neg and m: + d = False + elif m: + d = True + if d: + return path + + def emit_dir(dpath, opath): + if apply(dpath): + yield opath + else: + self._log.debug('skip: %s', dpath) + + def emit_file(fpath, opath): + if apply(fpath): + yield opath + else: + self._log.debug('skip: %s', fpath) + + if os.path.isfile(path): + name = os.path.basename(path) + if prefix: + name = os.path.join(prefix, name) + if apply(name): + yield path + else: + for root, dirs, files in os.walk(path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + o, d = norm_path(path, root) + # log.info('od: %s %s', o, d) + if root != path: + yield from emit_dir(d, o) + for name in files: + o, f = norm_path(path, root, name) + # log.info('of: %s %s', o, f) + yield from emit_file(f, o) + + +class BuildPlanManager: + """""" + + def __init__(self, args, log=None): + self._args = args + self._source_paths = None + self._log = log or logging.root + + def hash(self, extra_paths): + if not self._source_paths: + raise ValueError('BuildPlanManager.plan() should be called first') + + content_hash_paths = self._source_paths + extra_paths + + # Generate a hash based on file names and content. Also use the + # runtime value, build command, and content of the build paths + # because they can have an effect on the resulting archive. + self._log.debug("Computing content hash on files...") + content_hash = generate_content_hash(content_hash_paths, + log=self._log) + return content_hash + + def plan(self, source_path, query): + claims = source_path + if not isinstance(source_path, list): + claims = [source_path] + + source_paths = [] + build_plan = [] + + step = lambda *x: build_plan.append(x) + hash = source_paths.append + + def pip_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'requirements.txt') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('pip', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def npm_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'package.json') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('npm', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def commands_step(path, commands): + if not commands: + return + + if isinstance(commands, str): + commands = map(str.strip, commands.splitlines()) + + if path: + path = os.path.normpath(path) + batch = [] + for c in commands: + if isinstance(c, str): + if c.startswith(':zip'): + if path: + hash(path) + else: + # If path doesn't defined for a block with + # commands it will be set to Terraform's + # current working directory + # NB: cwd may vary when using Terraform 0.14+ like: + # `terraform -chdir=...` + path = query.paths.cwd + if batch: + step('sh', path, '\n'.join(batch)) + batch.clear() + c = shlex.split(c) + if len(c) == 3: + _, _path, prefix = c + prefix = prefix.strip() + _path = os.path.normpath(os.path.join(path, _path)) + step('zip:embedded', _path, prefix) + elif len(c) == 2: + prefix = None + _, _path = c + step('zip:embedded', _path, prefix) + elif len(c) == 1: + prefix = None + step('zip:embedded', path, prefix) + else: + raise ValueError( + ":zip invalid call signature, use: " + "':zip [path [prefix_in_zip]]'") + else: + batch.append(c) + + for claim in claims: + if isinstance(claim, str): + path = claim + if not os.path.exists(path): + abort('Could not locate source_path "{path}". Paths are relative to directory where `terraform plan` is being run ("{pwd}")'.format( + path=path, + pwd=os.getcwd() + )) + runtime = query.runtime + if runtime.startswith('python'): + pip_requirements_step( + os.path.join(path, 'requirements.txt')) + elif runtime.startswith('nodejs'): + npm_requirements_step( + os.path.join(path, 'package.json')) + step('zip', path, None) + hash(path) + + elif isinstance(claim, dict): + path = claim.get('path') + patterns = claim.get('patterns') + commands = claim.get('commands') + if patterns: + step('set:filter', patterns_list(self._args, patterns)) + if commands: + commands_step(path, commands) + else: + prefix = claim.get('prefix_in_zip') + pip_requirements = claim.get('pip_requirements') + npm_requirements = claim.get('npm_package_json') + runtime = claim.get('runtime', query.runtime) + + if pip_requirements and runtime.startswith('python'): + if isinstance(pip_requirements, bool) and path: + pip_requirements_step(path, prefix, required=True, tmp_dir=claim.get('pip_tmp_dir')) + else: + pip_requirements_step(pip_requirements, prefix, + required=True, tmp_dir=claim.get('pip_tmp_dir')) + + if npm_requirements and runtime.startswith('nodejs'): + if isinstance(npm_requirements, bool) and path: + npm_requirements_step(path, prefix, required=True, tmp_dir=claim.get('npm_tmp_dir')) + else: + npm_requirements_step(npm_requirements, prefix, + required=True, tmp_dir=claim.get('npm_tmp_dir')) + + if path: + step('zip', path, prefix) + if patterns: + # Take patterns into account when computing hash + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + + for path_from_pattern in pf.filter(path, prefix): + hash(path_from_pattern) + else: + hash(path) + + if patterns: + step('clear:filter') + else: + raise ValueError( + 'Unsupported source_path item: {}'.format(claim)) + + self._source_paths = source_paths + return build_plan + + def execute(self, build_plan, zip_stream, query): + zs = zip_stream + sh_work_dir = None + pf = None + + for action in build_plan: + cmd = action[0] + if cmd.startswith('zip'): + ts = 0 if cmd == 'zip:embedded' else None + source_path, prefix = action[1:] + if sh_work_dir: + if source_path != sh_work_dir: + if not os.path.isfile(source_path): + source_path = sh_work_dir + if os.path.isdir(source_path): + if pf: + self._zip_write_with_filter(zs, pf, source_path, prefix, + timestamp=ts) + else: + zs.write_dirs(source_path, prefix=prefix, timestamp=ts) + else: + zs.write_file(source_path, prefix=prefix, timestamp=ts) + elif cmd == 'pip': + runtime, pip_requirements, prefix, tmp_dir = action[1:] + with install_pip_requirements(query, pip_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'npm': + runtime, npm_requirements, prefix, tmp_dir = action[1:] + with install_npm_requirements(query, npm_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'sh': + r, w = os.pipe() + side_ch = os.fdopen(r) + path, script = action[1:] + script = "{}\npwd >&{}".format(script, w) + + p = subprocess.Popen(script, shell=True, cwd=path, + pass_fds=(w,)) + os.close(w) + sh_work_dir = side_ch.read().strip() + p.wait() + log.info('WD: %s', sh_work_dir) + side_ch.close() + elif cmd == 'set:filter': + patterns = action[1] + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + elif cmd == 'clear:filter': + pf = None + + @staticmethod + def _zip_write_with_filter(zip_stream, path_filter, source_path, prefix, + timestamp=None): + for path in path_filter.filter(source_path, prefix): + if os.path.isdir(source_path): + arcname = os.path.relpath(path, source_path) + else: + arcname = os.path.basename(path) + zip_stream.write_file(path, prefix, arcname, timestamp=timestamp) + + +@contextmanager +def install_pip_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + docker = query.docker + temp_dir = query.temp_dir + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + working_dir = os.getcwd() + + log.info('Installing python requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + python_exec = runtime + subproc_env = None + + if not docker: + if WINDOWS: + python_exec = 'python.exe' + elif OSX: + # Workaround for OSX when XCode command line tools' + # python becomes the main system python interpreter + os_path = '{}:/Library/Developer/CommandLineTools' \ + '/usr/bin'.format(os.environ['PATH']) + subproc_env = os.environ.copy() + subproc_env['PATH'] = os_path + + # Install dependencies into the temporary directory. + with cd(temp_dir): + pip_command = [ + python_exec, '-m', 'pip', + 'install', '--no-compile', + '--prefix=', '--target=.', + '--requirement={}'.format(requirements_filename), + ] + if docker: + with_ssh_agent = docker.with_ssh_agent + pip_cache_dir = docker.docker_pip_cache + if pip_cache_dir: + if isinstance(pip_cache_dir, str): + pip_cache_dir = os.path.abspath( + os.path.join(working_dir, pip_cache_dir)) + else: + pip_cache_dir = os.path.abspath(os.path.join( + working_dir, artifacts_dir, 'cache/pip')) + + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(pip_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent, + pip_cache_dir=pip_cache_dir, + )) + else: + cmd_log.info(shlex_join(pip_command)) + log_handler and log_handler.flush() + try: + check_call(pip_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Python interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +@contextmanager +def install_npm_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + temp_dir = query.temp_dir + docker = query.docker + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + log.info('Installing npm requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + subproc_env = None + if not docker and OSX: + subproc_env = os.environ.copy() + + # Install dependencies into the temporary directory. + with cd(temp_dir): + npm_command = ['npm', 'install'] + if docker: + with_ssh_agent = docker.with_ssh_agent + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(npm_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent + )) + else: + cmd_log.info(shlex_join(npm_command)) + log_handler and log_handler.flush() + try: + check_call(npm_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Nodejs interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +def docker_image_id_command(tag): + """""" + docker_cmd = ['docker', 'images', '--format={{.ID}}', tag] + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_build_command(tag=None, docker_file=None, build_root=False): + """""" + if not (build_root or docker_file): + raise ValueError('docker_build_root or docker_file must be provided') + + docker_cmd = ['docker', 'build'] + + if tag: + docker_cmd.extend(['--tag', tag]) + else: + raise ValueError('docker_image must be specified') + if not build_root: + build_root = os.path.dirname(docker_file) + if docker_file: + docker_cmd.extend(['--file', docker_file]) + docker_cmd.append(build_root) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_run_command(build_root, command, runtime, + image=None, shell=None, ssh_agent=False, + interactive=False, pip_cache_dir=None): + """""" + if platform.system() not in ('Linux', 'Darwin'): + raise RuntimeError("Unsupported platform for docker building") + + workdir = '/var/task' + + docker_cmd = ['docker', 'run', '--rm', '-w', workdir] + + if interactive: + docker_cmd.append('-it') + + bind_path = os.path.abspath(build_root) + docker_cmd.extend(['-v', "{}:{}:z".format(bind_path, workdir)]) + + home = os.environ['HOME'] + docker_cmd.extend([ + # '-v', '{}/.ssh/id_rsa:/root/.ssh/id_rsa:z'.format(home), + '-v', '{}/.ssh/known_hosts:/root/.ssh/known_hosts:z'.format(home), + ]) + + if ssh_agent: + if platform.system() == 'Darwin': + # https://docs.docker.com/docker-for-mac/osxfs/#ssh-agent-forwarding + docker_cmd.extend([ + '--mount', 'type=bind,' + 'src=/run/host-services/ssh-auth.sock,' + 'target=/run/host-services/ssh-auth.sock', + '-e', 'SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock', + ]) + elif platform.system() == 'Linux': + sock = os.environ['SSH_AUTH_SOCK'] # TODO: Handle missing env var + docker_cmd.extend([ + '-v', '{}:/tmp/ssh_sock:z'.format(sock), + '-e', 'SSH_AUTH_SOCK=/tmp/ssh_sock', + ]) + + if platform.system() in ('Linux', 'Darwin'): + if pip_cache_dir: + pip_cache_dir = os.path.abspath(pip_cache_dir) + docker_cmd.extend([ + '-v', '{}:/root/.cache/pip:z'.format(pip_cache_dir), + ]) + + if not image: + image = 'public.ecr.aws/sam/build-{}'.format(runtime) + + docker_cmd.extend(['--entrypoint', '']) + + docker_cmd.append(image) + + assert isinstance(command, list) + if shell: + if not isinstance(shell, str): + shell = '/bin/sh' + docker_cmd.extend([shell, '-c']) + docker_cmd.extend(command) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +################################################################################ +# Commands + +def prepare_command(args): + """ + Generates a content hash of the source_path, which is used to determine if + the Lambda code has changed, ignoring file modification and access times. + + Outputs a filename and a command to run if the archive needs to be built. + """ + + log = logging.getLogger('prepare') + + # Load the query. + query_data = json.load(sys.stdin) + + dump_env() + if log.isEnabledFor(DEBUG2): + if log.isEnabledFor(DEBUG3): + log.debug('QUERY: %s', json.dumps(query_data, indent=2)) + else: + log_excludes = ('source_path', 'hash_extra_paths', 'paths') + qd = {k: v for k, v in query_data.items() if k not in log_excludes} + log.debug('QUERY (excerpt): %s', json.dumps(qd, indent=2)) + + query = datatree('prepare_query', **query_data) + + tf_paths = query.paths + runtime = query.runtime + function_name = query.function_name + artifacts_dir = query.artifacts_dir + hash_extra_paths = query.hash_extra_paths + source_path = query.source_path + hash_extra = query.hash_extra + recreate_missing_package = yesno_bool(args.recreate_missing_package if args.recreate_missing_package is not None else query.recreate_missing_package) + docker = query.docker + + bpm = BuildPlanManager(args, log=log) + build_plan = bpm.plan(source_path, query) + + if log.isEnabledFor(DEBUG2): + log.debug('BUILD_PLAN: %s', json.dumps(build_plan, indent=2)) + + # Expand a Terraform path. references + hash_extra_paths = [p.format(path=tf_paths) for p in hash_extra_paths] + + content_hash = bpm.hash(hash_extra_paths) + content_hash.update(json.dumps(build_plan, sort_keys=True).encode()) + content_hash.update(runtime.encode()) + content_hash.update(hash_extra.encode()) + content_hash = content_hash.hexdigest() + + # Generate a unique filename based on the hash. + filename = os.path.join(artifacts_dir, '{}.zip'.format(content_hash)) + + # Compute timestamp trigger + was_missing = False + filename_path = os.path.join(os.getcwd(), filename) + if recreate_missing_package: + if os.path.exists(filename_path): + st = os.stat(filename_path) + timestamp = st.st_mtime_ns + else: + timestamp = timestamp_now_ns() + was_missing = True + else: + timestamp = "" + + # Replace variables in the build command with calculated values. + build_data = { + 'filename': filename, + 'runtime': runtime, + 'artifacts_dir': artifacts_dir, + 'build_plan': build_plan, + } + if docker: + build_data['docker'] = docker + + build_plan = json.dumps(build_data) + build_plan_filename = os.path.join(artifacts_dir, + '{}.plan.json'.format(content_hash)) + if not os.path.exists(artifacts_dir): + os.makedirs(artifacts_dir, exist_ok=True) + with open(build_plan_filename, 'w') as f: + f.write(build_plan) + + # Output the result to Terraform. + json.dump({ + 'filename': filename, + 'build_plan': build_plan, + 'build_plan_filename': build_plan_filename, + 'timestamp': str(timestamp), + 'was_missing': 'true' if was_missing else 'false', + }, sys.stdout, indent=2) + sys.stdout.write('\n') + + +def build_command(args): + """ + Builds a zip file from the source_dir or source_file. + Installs dependencies with pip or npm automatically. + """ + + log = logging.getLogger('build') + + dump_env() + if log.isEnabledFor(DEBUG2): + log.debug('CMD: python3 %s', shlex_join(sys.argv)) + + with open(args.build_plan_file) as f: + query_data = json.load(f) + query = datatree('build_query', **query_data) + + runtime = query.runtime + filename = query.filename + build_plan = query.build_plan + _timestamp = args.zip_file_timestamp + + timestamp = 0 + if _timestamp.isnumeric(): + timestamp = int(_timestamp) + + if os.path.exists(filename) and not args.force: + log.info('Reused: %s', shlex.quote(filename)) + return + + # Zip up the build plan and write it to the target filename. + # This will be used by the Lambda function as the source code package. + with ZipWriteStream(filename) as zs: + bpm = BuildPlanManager(args, log=log) + bpm.execute(build_plan, zs, query) + + os.utime(filename, ns=(timestamp, timestamp)) + log.info('Created: %s', shlex.quote(filename)) + if log.isEnabledFor(logging.DEBUG): + with open(filename, 'rb') as f: + log.info('Base64sha256: %s', source_code_hash(f.read())) + + +def add_hidden_commands(sub_parsers): + sp = sub_parsers + + def hidden_parser(name, **kwargs): + p = sp.add_parser(name, **kwargs) + sp._choices_actions.pop() # XXX: help=argparse.SUPPRESS - doesn't work + return p + + p = hidden_parser('docker', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_run_command( + args.build_root, args.docker_command, args.runtime, interactive=True))) + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_command', help='A docker container command', + metavar='command', nargs=argparse.REMAINDER) + p.add_argument('-r', '--runtime', help='A docker image runtime', + default='python3.8') + + p = hidden_parser('docker-image', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_build_command( + args.build_root, args.docker_file, args.tag))) + p.add_argument('-t', '--tag', help='A docker image tag') + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_file', help='A docker file path', + nargs=argparse.OPTIONAL) + + def zip_cmd(args): + if args.verbose: + log.setLevel(logging.DEBUG) + with ZipWriteStream(args.zipfile) as zs: + zs.write_dirs(*args.dir, timestamp=args.timestamp) + if log.isEnabledFor(logging.DEBUG): + zipinfo = shutil.which('zipinfo') + if zipinfo: + log.debug('-' * 80) + subprocess.call([zipinfo, args.zipfile]) + log.debug('-' * 80) + log.debug('Source code hash: %s', + source_code_hash(open(args.zipfile, 'rb').read())) + + p = hidden_parser('zip', help='Zip folder with provided files timestamp') + p.set_defaults(command=zip_cmd) + p.add_argument('zipfile', help='Path to a zip file') + p.add_argument('dir', nargs=argparse.ONE_OR_MORE, + help='Path to a directory for packaging') + p.add_argument('-t', '--timestamp', type=int, + help='A timestamp to override for all zip members') + p.add_argument('-v', '--verbose', action='store_true') + + p = hidden_parser('hash', help='Generate content hash for a file') + p.set_defaults( + command=lambda args: print(source_code_hash(args.file.read()))) + p.add_argument('file', help='Path to a file', type=argparse.FileType('rb')) + + +def args_parser(): + ap = argparse.ArgumentParser() + ap.set_defaults(command=lambda _: ap.print_usage()) + sp = ap.add_subparsers(metavar="COMMAND") + + p = sp.add_parser('prepare', + help='compute a filename hash for a zip archive') + p.set_defaults(command=prepare_command) + + p = sp.add_parser('build', + help='build and pack to a zip archive') + p.set_defaults(command=build_command) + p.add_argument('--force', action='store_true', + help='Force rebuilding even if a zip artifact exists') + p.add_argument('-t', '--timestamp', + dest='zip_file_timestamp', required=True, + help='A zip file timestamp generated by the prepare command') + p.add_argument('build_plan_file', metavar='PLAN_FILE', + help='A build plan file provided by the prepare command') + add_hidden_commands(sp) + return ap + + +def main(): + ns = argparse.Namespace( + pattern_comments=yesno_bool(os.environ.get( + 'TF_LAMBDA_PACKAGE_PATTERN_COMMENTS', False)), + recreate_missing_package=os.environ.get( + 'TF_RECREATE_MISSING_LAMBDA_PACKAGE', None), + log_level=os.environ.get('TF_LAMBDA_PACKAGE_LOG_LEVEL', 'INFO'), + ) + + p = args_parser() + args = p.parse_args(namespace=ns) + + if args.command is prepare_command: + configure_logging(use_tf_stderr=True) + else: + configure_logging() + + if args.log_level: + ll = logging._nameToLevel.get(args.log_level) + if ll and logging._checkLevel(ll): + logging.root.setLevel(args.log_level) + + exit(args.command(args)) + + +if __name__ == '__main__': + main() diff --git a/modules/aws-lambda/package.py b/modules/aws-lambda/package.py new file mode 100644 index 0000000..1b96608 --- /dev/null +++ b/modules/aws-lambda/package.py @@ -0,0 +1,1391 @@ +# coding: utf-8 + +import sys + +if sys.version_info < (3, 6): + raise RuntimeError("A python version 3.6 or newer is required") + +import os +import re +import time +import stat +import json +import shlex +import shutil +import hashlib +import zipfile +import argparse +import datetime +import tempfile +import operator +import platform +import subprocess +from subprocess import check_call, check_output +from contextlib import contextmanager +from base64 import b64encode +import logging + +PY38 = sys.version_info >= (3, 8) +PY37 = sys.version_info >= (3, 7) +PY36 = sys.version_info >= (3, 6) + +WINDOWS = platform.system() == 'Windows' +OSX = platform.system() == 'Darwin' + +################################################################################ +# Logging + +DEBUG2 = 9 +DEBUG3 = 8 +DUMP_ENV = 1 + +log_handler = None +log = logging.getLogger() +cmd_log = logging.getLogger('cmd') + + +def configure_logging(use_tf_stderr=False): + global log_handler + + logging.addLevelName(DEBUG2, 'DEBUG2') + logging.addLevelName(DEBUG3, 'DEBUG3') + logging.addLevelName(DUMP_ENV, 'DUMP_ENV') + + class LogFormatter(logging.Formatter): + default_format = '%(message)s' + formats = { + 'root': default_format, + 'build': default_format, + 'prepare': '[{}] %(name)s: %(message)s'.format(os.getpid()), + 'cmd': '> %(message)s', + '': '%(name)s: %(message)s' + } + + def formatMessage(self, record): + prefix = record.name.rsplit('.') + self._style._fmt = self.formats.get(prefix[0], self.formats['']) + return super().formatMessage(record) + + tf_stderr_fd = 5 + log_stream = sys.stderr + if use_tf_stderr: + try: + if os.isatty(tf_stderr_fd): + log_stream = os.fdopen(tf_stderr_fd, mode='w') + except OSError: + pass + + log_handler = logging.StreamHandler(stream=log_stream) + log_handler.setFormatter(LogFormatter()) + + log.addHandler(log_handler) + log.setLevel(logging.INFO) + + +def dump_env(): + if log.isEnabledFor(DUMP_ENV): + log.debug('ENV: %s', json.dumps(dict(os.environ), indent=2)) + + +################################################################################ +# Backports + +def shlex_join(split_command): + """Return a shell-escaped string from *split_command*.""" + return ' '.join(shlex.quote(arg) for arg in split_command) + + +################################################################################ +# Common functions + +def abort(message): + """Exits with an error message.""" + log.error(message) + sys.exit(1) + + +@contextmanager +def cd(path, silent=False): + """Changes the working directory.""" + cwd = os.getcwd() + if not silent: + cmd_log.info('cd %s', shlex.quote(path)) + try: + os.chdir(path) + yield + finally: + os.chdir(cwd) + + +@contextmanager +def tempdir(dir=None): + """Creates a temporary directory and then deletes it afterwards.""" + prefix = 'terraform-aws-lambda-' + path = tempfile.mkdtemp(prefix=prefix, dir=dir) + cmd_log.info('mktemp -d %sXXXXXXXX # %s', prefix, shlex.quote(path)) + try: + yield path + finally: + shutil.rmtree(path) + + +def list_files(top_path, log=None): + """ + Returns a sorted list of all files in a directory. + """ + + if log: + log = log.getChild('ls') + + results = [] + + for root, dirs, files in os.walk(top_path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + for file_name in files: + file_path = os.path.join(root, file_name) + relative_path = os.path.relpath(file_path, top_path) + results.append(relative_path) + if log: + log.debug(relative_path) + + results.sort() + return results + + +def dataclass(name): + typ = type(name, (dict,), { + '__getattr__': lambda self, x: self.get(x), + '__init__': lambda self, **k: self.update(k), + }) + return typ + + +def datatree(name, **fields): + def decode_json(k, v): + if v and isinstance(v, str) and v[0] in '"[{': + try: + o = json.loads(v) + if isinstance(o, dict): + return dataclass(k)(**o) + return o + except json.JSONDecodeError: + pass + return v + + return dataclass(name)(**dict((( + k, datatree(k, **v) if isinstance(v, dict) else decode_json(k, v)) + for k, v in fields.items()))) + + +def timestamp_now_ns(): + timestamp = datetime.datetime.now().timestamp() + timestamp = int(timestamp * 10 ** 7) * 10 ** 2 + return timestamp + + +def source_code_hash(bytes): + return b64encode(hashlib.sha256(bytes).digest()).decode() + + +def yesno_bool(val): + if val is None: + return + if isinstance(val, bool): + return val + if isinstance(val, int): + return bool(val) + if isinstance(val, str): + if val.isnumeric(): + return bool(int(val)) + val = val.lower() + if val in ('true', 'yes', 'y'): + return True + elif val in ('false', 'no', 'n'): + return False + else: + raise ValueError("Unsupported value: %s" % val) + return False + + +################################################################################ +# Packaging functions + +def emit_dir_content(base_dir): + for root, dirs, files in os.walk(base_dir, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + if root != base_dir: + yield os.path.normpath(root) + for name in files: + yield os.path.normpath(os.path.join(root, name)) + + +def generate_content_hash(source_paths, + hash_func=hashlib.sha256, log=None): + """ + Generate a content hash of the source paths. + """ + + if log: + log = log.getChild('hash') + + hash_obj = hash_func() + + for source_path in source_paths: + if os.path.isdir(source_path): + source_dir = source_path + _log = log if log.isEnabledFor(DEBUG3) else None + for source_file in list_files(source_dir, log=_log): + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(os.path.join(source_dir, source_file)) + else: + source_dir = os.path.dirname(source_path) + source_file = os.path.relpath(source_path, source_dir) + update_hash(hash_obj, source_dir, source_file) + if log: + log.debug(source_path) + + return hash_obj + + +def update_hash(hash_obj, file_root, file_path): + """ + Update a hashlib object with the relative path and contents of a file. + """ + + relative_path = os.path.join(file_root, file_path) + hash_obj.update(relative_path.encode()) + + with open(relative_path, 'rb') as open_file: + while True: + data = open_file.read(1024 * 8) + if not data: + break + hash_obj.update(data) + + +class ZipWriteStream: + """""" + + def __init__(self, zip_filename, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=None, + timestamp=None): + + self.timestamp = timestamp + self.filename = zip_filename + + if not (self.filename and isinstance(self.filename, str)): + raise ValueError('Zip file path must be provided') + + self._tmp_filename = None + self._compress_type = compress_type + self._compresslevel = compresslevel + self._zip = None + + self._log = logging.getLogger('zip') + + def open(self): + if self._tmp_filename: + raise zipfile.BadZipFile("ZipStream object can't be reused") + self._ensure_base_path(self.filename) + self._tmp_filename = '{}.tmp'.format(self.filename) + self._log.info("creating '%s' archive", self.filename) + self._zip = zipfile.ZipFile(self._tmp_filename, "w", + self._compress_type) + return self + + def close(self, failed=False): + self._zip.close() + self._zip = None + if failed: + os.unlink(self._tmp_filename) + else: + os.replace(self._tmp_filename, self.filename) + + def __enter__(self): + return self.open() + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None: + self._log.exception("Error during zip archive creation") + self.close(failed=True) + raise SystemExit(1) + self.close() + + def _ensure_open(self): + if self._zip is not None: + return True + if self._tmp_filename: + raise zipfile.BadZipFile("ZipWriteStream object can't be reused") + raise zipfile.BadZipFile('ZipWriteStream should be opened first') + + def _ensure_base_path(self, zip_filename): + archive_dir = os.path.dirname(zip_filename) + + if archive_dir and not os.path.exists(archive_dir): + self._log.info("creating %s", archive_dir) + os.makedirs(archive_dir, exist_ok=True) + + def write_dirs(self, *base_dirs, prefix=None, timestamp=None): + """ + Writes a directory content to a prefix inside of a zip archive + """ + self._ensure_open() + for base_dir in base_dirs: + self._log.info("adding content of directory: %s", base_dir) + for path in emit_dir_content(base_dir): + arcname = os.path.relpath(path, base_dir) + self._write_file(path, prefix, arcname, timestamp) + + def write_files(self, files_stream, prefix=None, timestamp=None): + """ + Expects just files stream, directories will be created automatically + """ + self._ensure_open() + for file_path, arcname in files_stream: + self._write_file(file_path, prefix, arcname, timestamp) + + def write_file(self, file_path, prefix=None, name=None, timestamp=None): + """ + Reads a file and writes it to a prefix + or a full qualified name in a zip archive + """ + self._ensure_open() + self._write_file(file_path, prefix, name, timestamp) + + def _write_file(self, file_path, prefix=None, name=None, timestamp=None): + arcname = name if name else os.path.basename(file_path) + if prefix: + arcname = os.path.join(prefix, arcname) + zinfo = self._make_zinfo_from_file(file_path, arcname) + if zinfo.is_dir(): + self._log.info("adding: %s/", arcname) + else: + self._log.info("adding: %s", arcname) + if timestamp is None: + timestamp = self.timestamp + date_time = self._timestamp_to_date_time(timestamp) + if date_time: + self._update_zinfo(zinfo, date_time=date_time) + self._write_zinfo(zinfo, file_path) + + def write_file_obj(self, file_path, data, prefix=None, timestamp=None): + """ + Write a data to a zip archive by a full qualified archive file path + """ + self._ensure_open() + raise NotImplementedError + + def _write_zinfo(self, zinfo, filename, + compress_type=None, compresslevel=None): + self._ensure_open() + + zip = self._zip + + if not zip.fp: + raise ValueError( + "Attempt to write to ZIP archive that was already closed") + if zip._writing: + raise ValueError( + "Can't write to ZIP archive while an open writing handle exists" + ) + + if zinfo.is_dir(): + zinfo.compress_size = 0 + zinfo.CRC = 0 + else: + if compress_type is not None: + zinfo.compress_type = compress_type + else: + zinfo.compress_type = self._compress_type + + if PY37: + if compresslevel is not None: + zinfo._compresslevel = compresslevel + else: + zinfo._compresslevel = self._compresslevel + + if zinfo.is_dir(): + with zip._lock: + if zip._seekable: + zip.fp.seek(zip.start_dir) + zinfo.header_offset = zip.fp.tell() # Start of header bytes + if zinfo.compress_type == zipfile.ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= 0x02 + + zip._writecheck(zinfo) + zip._didModify = True + + zip.filelist.append(zinfo) + zip.NameToInfo[zinfo.filename] = zinfo + zip.fp.write(zinfo.FileHeader(False)) + zip.start_dir = zip.fp.tell() + else: + with open(filename, "rb") as src, zip.open(zinfo, 'w') as dest: + shutil.copyfileobj(src, dest, 1024 * 8) + + def _make_zinfo_from_file(self, filename, arcname=None): + if PY38: + zinfo_func = zipfile.ZipInfo.from_file + strict_timestamps = self._zip._strict_timestamps + else: + zinfo_func = self._zinfo_from_file + strict_timestamps = True + + return zinfo_func(filename, arcname, + strict_timestamps=strict_timestamps) + + @staticmethod + def _update_zinfo(zinfo, date_time): + zinfo.date_time = date_time + + # Borrowed from python 3.8 zipfile.py library + # due to the need of strict_timestamps functionality. + @staticmethod + def _zinfo_from_file(filename, arcname=None, *, strict_timestamps=True): + """Construct an appropriate ZipInfo for a file on the filesystem. + + filename should be the path to a file or directory on the filesystem. + + arcname is the name which it will have within the archive (by default, + this will be the same as filename, but without a drive letter and with + leading path separators removed). + """ + if isinstance(filename, os.PathLike): + filename = os.fspath(filename) + st = os.stat(filename) + isdir = stat.S_ISDIR(st.st_mode) + mtime = time.localtime(st.st_mtime) + date_time = mtime[0:6] + if strict_timestamps and date_time[0] < 1980: + date_time = (1980, 1, 1, 0, 0, 0) + elif strict_timestamps and date_time[0] > 2107: + date_time = (2107, 12, 31, 23, 59, 59) + # Create ZipInfo instance to store file information + if arcname is None: + arcname = filename + arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) + while arcname[0] in (os.sep, os.altsep): + arcname = arcname[1:] + if isdir: + arcname += '/' + zinfo = zipfile.ZipInfo(arcname, date_time) + zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes + if isdir: + zinfo.file_size = 0 + zinfo.external_attr |= 0x10 # MS-DOS directory flag + else: + zinfo.file_size = st.st_size + + return zinfo + + @staticmethod + def _timestamp_to_date_time(timestamp): + def str_int_to_timestamp(s): + min_zip_ts = datetime.datetime(1980, 1, 1).timestamp() + ts = int(s) + if ts < min_zip_ts: + return min_zip_ts + deg = len(str(int(s))) - 9 + if deg < 0: + ts = ts * 10 ** deg + return ts + + date_time = None + if timestamp is not None: + if isinstance(timestamp, str): + if timestamp.isnumeric(): + timestamp = str_int_to_timestamp(timestamp) + else: + timestamp = float(timestamp) + elif isinstance(timestamp, int): + timestamp = str_int_to_timestamp(str(timestamp)) + + date_time = datetime.datetime.fromtimestamp(timestamp).timetuple() + date_time = date_time[:6] + if date_time[0] < 1980: + raise ValueError('ZIP does not support timestamps before 1980') + return date_time + + +################################################################################ +# Building + +def patterns_list(args, patterns): + _filter = str.strip + if args.pattern_comments: + def _filter(x): + x = x.strip() + p = re.search("^(.*?)[ \t]*(?:[ \t]{2}#.*)?$", x).group(1).rstrip() + if p.startswith('#'): + return + if p: + return p + if isinstance(patterns, str): + return list(filter(None, map(_filter, patterns.splitlines()))) + return patterns + + +class ZipContentFilter: + """""" + + def __init__(self, args): + self._args = args + self._rules = None + self._excludes = set() + self._log = logging.getLogger('zip') + + def compile(self, patterns): + rules = [] + for p in patterns_list(self._args, patterns): + self._log.debug("filter pattern: %s", p) + if p.startswith('!'): + r = re.compile(p[1:]) + rules.append((operator.not_, r)) + else: + r = re.compile(p) + rules.append((None, r)) + self._rules = rules + + def filter(self, path, prefix=None): + path = os.path.normpath(path) + if prefix: + prefix = os.path.normpath(prefix) + rules = self._rules + + def norm_path(path, root, filename=None): + op = os.path.join(root, filename) if filename else root + p = os.path.relpath(root, path) + if prefix: + p = os.path.join(prefix, p) + if filename: + p = os.path.normpath(os.path.join(p, filename)) + return op, p + return op, p + os.sep + + def apply(path): + d = True + for r in rules: + op, regex = r + neg = op is operator.not_ + m = regex.fullmatch(path) + if neg and m: + d = False + elif m: + d = True + if d: + return path + + def emit_dir(dpath, opath): + if apply(dpath): + yield opath + else: + self._log.debug('skip: %s', dpath) + + def emit_file(fpath, opath): + if apply(fpath): + yield opath + else: + self._log.debug('skip: %s', fpath) + + if os.path.isfile(path): + name = os.path.basename(path) + if prefix: + name = os.path.join(prefix, name) + if apply(name): + yield path + else: + for root, dirs, files in os.walk(path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() + o, d = norm_path(path, root) + # log.info('od: %s %s', o, d) + if root != path: + yield from emit_dir(d, o) + for name in files: + o, f = norm_path(path, root, name) + # log.info('of: %s %s', o, f) + yield from emit_file(f, o) + + +class BuildPlanManager: + """""" + + def __init__(self, args, log=None): + self._args = args + self._source_paths = None + self._log = log or logging.root + + def hash(self, extra_paths): + if not self._source_paths: + raise ValueError('BuildPlanManager.plan() should be called first') + + content_hash_paths = self._source_paths + extra_paths + + # Generate a hash based on file names and content. Also use the + # runtime value, build command, and content of the build paths + # because they can have an effect on the resulting archive. + self._log.debug("Computing content hash on files...") + content_hash = generate_content_hash(content_hash_paths, + log=self._log) + return content_hash + + def plan(self, source_path, query): + claims = source_path + if not isinstance(source_path, list): + claims = [source_path] + + source_paths = [] + build_plan = [] + + step = lambda *x: build_plan.append(x) + hash = source_paths.append + + def pip_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'requirements.txt') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('pip', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def npm_requirements_step(path, prefix=None, required=False, tmp_dir=None): + requirements = path + if os.path.isdir(path): + requirements = os.path.join(path, 'package.json') + if not os.path.isfile(requirements): + if required: + raise RuntimeError( + 'File not found: {}'.format(requirements)) + else: + step('npm', runtime, requirements, prefix, tmp_dir) + hash(requirements) + + def commands_step(path, commands): + if not commands: + return + + if isinstance(commands, str): + commands = map(str.strip, commands.splitlines()) + + if path: + path = os.path.normpath(path) + batch = [] + for c in commands: + if isinstance(c, str): + if c.startswith(':zip'): + if path: + hash(path) + else: + # If path doesn't defined for a block with + # commands it will be set to Terraform's + # current working directory + # NB: cwd may vary when using Terraform 0.14+ like: + # `terraform -chdir=...` + path = query.paths.cwd + if batch: + step('sh', path, '\n'.join(batch)) + batch.clear() + c = shlex.split(c) + if len(c) == 3: + _, _path, prefix = c + prefix = prefix.strip() + _path = os.path.normpath(os.path.join(path, _path)) + step('zip:embedded', _path, prefix) + elif len(c) == 2: + prefix = None + _, _path = c + step('zip:embedded', _path, prefix) + elif len(c) == 1: + prefix = None + step('zip:embedded', path, prefix) + else: + raise ValueError( + ":zip invalid call signature, use: " + "':zip [path [prefix_in_zip]]'") + else: + batch.append(c) + + for claim in claims: + if isinstance(claim, str): + path = claim + if not os.path.exists(path): + abort('Could not locate source_path "{path}". Paths are relative to directory where `terraform plan` is being run ("{pwd}")'.format( + path=path, + pwd=os.getcwd() + )) + runtime = query.runtime + if runtime.startswith('python'): + pip_requirements_step( + os.path.join(path, 'requirements.txt')) + elif runtime.startswith('nodejs'): + npm_requirements_step( + os.path.join(path, 'package.json')) + step('zip', path, None) + hash(path) + + elif isinstance(claim, dict): + path = claim.get('path') + patterns = claim.get('patterns') + commands = claim.get('commands') + if patterns: + step('set:filter', patterns_list(self._args, patterns)) + if commands: + commands_step(path, commands) + else: + prefix = claim.get('prefix_in_zip') + pip_requirements = claim.get('pip_requirements') + npm_requirements = claim.get('npm_package_json') + runtime = claim.get('runtime', query.runtime) + + if pip_requirements and runtime.startswith('python'): + if isinstance(pip_requirements, bool) and path: + pip_requirements_step(path, prefix, required=True, tmp_dir=claim.get('pip_tmp_dir')) + else: + pip_requirements_step(pip_requirements, prefix, + required=True, tmp_dir=claim.get('pip_tmp_dir')) + + if npm_requirements and runtime.startswith('nodejs'): + if isinstance(npm_requirements, bool) and path: + npm_requirements_step(path, prefix, required=True, tmp_dir=claim.get('npm_tmp_dir')) + else: + npm_requirements_step(npm_requirements, prefix, + required=True, tmp_dir=claim.get('npm_tmp_dir')) + + if path: + step('zip', path, prefix) + if patterns: + # Take patterns into account when computing hash + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + + for path_from_pattern in pf.filter(path, prefix): + hash(path_from_pattern) + else: + hash(path) + + if patterns: + step('clear:filter') + else: + raise ValueError( + 'Unsupported source_path item: {}'.format(claim)) + + self._source_paths = source_paths + return build_plan + + def execute(self, build_plan, zip_stream, query): + zs = zip_stream + sh_work_dir = None + pf = None + + for action in build_plan: + cmd = action[0] + if cmd.startswith('zip'): + ts = 0 if cmd == 'zip:embedded' else None + source_path, prefix = action[1:] + if sh_work_dir: + if source_path != sh_work_dir: + if not os.path.isfile(source_path): + source_path = sh_work_dir + if os.path.isdir(source_path): + if pf: + self._zip_write_with_filter(zs, pf, source_path, prefix, + timestamp=ts) + else: + zs.write_dirs(source_path, prefix=prefix, timestamp=ts) + else: + zs.write_file(source_path, prefix=prefix, timestamp=ts) + elif cmd == 'pip': + runtime, pip_requirements, prefix, tmp_dir = action[1:] + with install_pip_requirements(query, pip_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'npm': + runtime, npm_requirements, prefix, tmp_dir = action[1:] + with install_npm_requirements(query, npm_requirements, tmp_dir) as rd: + if rd: + if pf: + self._zip_write_with_filter(zs, pf, rd, prefix, + timestamp=0) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == 'sh': + r, w = os.pipe() + side_ch = os.fdopen(r) + path, script = action[1:] + script = "{}\npwd >&{}".format(script, w) + + p = subprocess.Popen(script, shell=True, cwd=path, + pass_fds=(w,)) + os.close(w) + sh_work_dir = side_ch.read().strip() + p.wait() + log.info('WD: %s', sh_work_dir) + side_ch.close() + elif cmd == 'set:filter': + patterns = action[1] + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + elif cmd == 'clear:filter': + pf = None + + @staticmethod + def _zip_write_with_filter(zip_stream, path_filter, source_path, prefix, + timestamp=None): + for path in path_filter.filter(source_path, prefix): + if os.path.isdir(source_path): + arcname = os.path.relpath(path, source_path) + else: + arcname = os.path.basename(path) + zip_stream.write_file(path, prefix, arcname, timestamp=timestamp) + + +@contextmanager +def install_pip_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + docker = query.docker + temp_dir = query.temp_dir + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + working_dir = os.getcwd() + + log.info('Installing python requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + python_exec = runtime + subproc_env = None + + if not docker: + if WINDOWS: + python_exec = 'python.exe' + elif OSX: + # Workaround for OSX when XCode command line tools' + # python becomes the main system python interpreter + os_path = '{}:/Library/Developer/CommandLineTools' \ + '/usr/bin'.format(os.environ['PATH']) + subproc_env = os.environ.copy() + subproc_env['PATH'] = os_path + + # Install dependencies into the temporary directory. + with cd(temp_dir): + pip_command = [ + python_exec, '-m', 'pip', + 'install', '--no-compile', + '--prefix=', '--target=.', + '--requirement={}'.format(requirements_filename), + ] + if docker: + with_ssh_agent = docker.with_ssh_agent + pip_cache_dir = docker.docker_pip_cache + if pip_cache_dir: + if isinstance(pip_cache_dir, str): + pip_cache_dir = os.path.abspath( + os.path.join(working_dir, pip_cache_dir)) + else: + pip_cache_dir = os.path.abspath(os.path.join( + working_dir, artifacts_dir, 'cache/pip')) + + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(pip_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent, + pip_cache_dir=pip_cache_dir, + )) + else: + cmd_log.info(shlex_join(pip_command)) + log_handler and log_handler.flush() + try: + check_call(pip_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Python interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +@contextmanager +def install_npm_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + temp_dir = query.temp_dir + docker = query.docker + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + output = check_output(docker_image_id_command(docker_image)) + if output: + docker_image_tag_id = output.decode().strip() + log.debug("DOCKER TAG ID: %s -> %s", + docker_image, docker_image_tag_id) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + check_call(docker_cmd) + ok = True + elif docker_file or docker_build_root: + raise ValueError('docker_image must be specified ' + 'for a custom image future references') + + log.info('Installing npm requirements: %s', requirements_file) + with tempdir(tmp_dir) as temp_dir: + requirements_filename = os.path.basename(requirements_file) + target_file = os.path.join(temp_dir, requirements_filename) + shutil.copyfile(requirements_file, target_file) + + subproc_env = None + if not docker and OSX: + subproc_env = os.environ.copy() + + # Install dependencies into the temporary directory. + with cd(temp_dir): + npm_command = ['npm', 'install'] + if docker: + with_ssh_agent = docker.with_ssh_agent + chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) + shell_command = [shlex_join(npm_command), '&&', + shlex_join(['chown', '-R', + chown_mask, '.'])] + shell_command = [' '.join(shell_command)] + check_call(docker_run_command( + '.', shell_command, runtime, + image=docker_image_tag_id, + shell=True, ssh_agent=with_ssh_agent + )) + else: + cmd_log.info(shlex_join(npm_command)) + log_handler and log_handler.flush() + try: + check_call(npm_command, env=subproc_env) + except FileNotFoundError as e: + raise RuntimeError( + "Nodejs interpreter version equal " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + os.remove(target_file) + yield temp_dir + + +def docker_image_id_command(tag): + """""" + docker_cmd = ['docker', 'images', '--format={{.ID}}', tag] + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_build_command(tag=None, docker_file=None, build_root=False): + """""" + if not (build_root or docker_file): + raise ValueError('docker_build_root or docker_file must be provided') + + docker_cmd = ['docker', 'build'] + + if tag: + docker_cmd.extend(['--tag', tag]) + else: + raise ValueError('docker_image must be specified') + if not build_root: + build_root = os.path.dirname(docker_file) + if docker_file: + docker_cmd.extend(['--file', docker_file]) + docker_cmd.append(build_root) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +def docker_run_command(build_root, command, runtime, + image=None, shell=None, ssh_agent=False, + interactive=False, pip_cache_dir=None): + """""" + if platform.system() not in ('Linux', 'Darwin'): + raise RuntimeError("Unsupported platform for docker building") + + workdir = '/var/task' + + docker_cmd = ['docker', 'run', '--rm', '-w', workdir] + + if interactive: + docker_cmd.append('-it') + + bind_path = os.path.abspath(build_root) + docker_cmd.extend(['-v', "{}:{}:z".format(bind_path, workdir)]) + + home = os.environ['HOME'] + docker_cmd.extend([ + # '-v', '{}/.ssh/id_rsa:/root/.ssh/id_rsa:z'.format(home), + '-v', '{}/.ssh/known_hosts:/root/.ssh/known_hosts:z'.format(home), + ]) + + if ssh_agent: + if platform.system() == 'Darwin': + # https://docs.docker.com/docker-for-mac/osxfs/#ssh-agent-forwarding + docker_cmd.extend([ + '--mount', 'type=bind,' + 'src=/run/host-services/ssh-auth.sock,' + 'target=/run/host-services/ssh-auth.sock', + '-e', 'SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock', + ]) + elif platform.system() == 'Linux': + sock = os.environ['SSH_AUTH_SOCK'] # TODO: Handle missing env var + docker_cmd.extend([ + '-v', '{}:/tmp/ssh_sock:z'.format(sock), + '-e', 'SSH_AUTH_SOCK=/tmp/ssh_sock', + ]) + + if platform.system() in ('Linux', 'Darwin'): + if pip_cache_dir: + pip_cache_dir = os.path.abspath(pip_cache_dir) + docker_cmd.extend([ + '-v', '{}:/root/.cache/pip:z'.format(pip_cache_dir), + ]) + + if not image: + image = 'public.ecr.aws/sam/build-{}'.format(runtime) + + docker_cmd.extend(['--entrypoint', '']) + + docker_cmd.append(image) + + assert isinstance(command, list) + if shell: + if not isinstance(shell, str): + shell = '/bin/sh' + docker_cmd.extend([shell, '-c']) + docker_cmd.extend(command) + + cmd_log.info(shlex_join(docker_cmd)) + log_handler and log_handler.flush() + return docker_cmd + + +################################################################################ +# Commands + +def prepare_command(args): + """ + Generates a content hash of the source_path, which is used to determine if + the Lambda code has changed, ignoring file modification and access times. + + Outputs a filename and a command to run if the archive needs to be built. + """ + + log = logging.getLogger('prepare') + + # Load the query. + query_data = json.load(sys.stdin) + + dump_env() + if log.isEnabledFor(DEBUG2): + if log.isEnabledFor(DEBUG3): + log.debug('QUERY: %s', json.dumps(query_data, indent=2)) + else: + log_excludes = ('source_path', 'hash_extra_paths', 'paths') + qd = {k: v for k, v in query_data.items() if k not in log_excludes} + log.debug('QUERY (excerpt): %s', json.dumps(qd, indent=2)) + + query = datatree('prepare_query', **query_data) + + tf_paths = query.paths + runtime = query.runtime + function_name = query.function_name + artifacts_dir = query.artifacts_dir + hash_extra_paths = query.hash_extra_paths + source_path = query.source_path + hash_extra = query.hash_extra + recreate_missing_package = yesno_bool(args.recreate_missing_package if args.recreate_missing_package is not None else query.recreate_missing_package) + docker = query.docker + + bpm = BuildPlanManager(args, log=log) + build_plan = bpm.plan(source_path, query) + + if log.isEnabledFor(DEBUG2): + log.debug('BUILD_PLAN: %s', json.dumps(build_plan, indent=2)) + + # Expand a Terraform path. references + hash_extra_paths = [p.format(path=tf_paths) for p in hash_extra_paths] + + content_hash = bpm.hash(hash_extra_paths) + content_hash.update(json.dumps(build_plan, sort_keys=True).encode()) + content_hash.update(runtime.encode()) + content_hash.update(hash_extra.encode()) + content_hash = content_hash.hexdigest() + + # Generate a unique filename based on the hash. + filename = os.path.join(artifacts_dir, '{}.zip'.format(content_hash)) + + # Compute timestamp trigger + was_missing = False + filename_path = os.path.join(os.getcwd(), filename) + if recreate_missing_package: + if os.path.exists(filename_path): + st = os.stat(filename_path) + timestamp = st.st_mtime_ns + else: + timestamp = timestamp_now_ns() + was_missing = True + else: + timestamp = "" + + # Replace variables in the build command with calculated values. + build_data = { + 'filename': filename, + 'runtime': runtime, + 'artifacts_dir': artifacts_dir, + 'build_plan': build_plan, + } + if docker: + build_data['docker'] = docker + + build_plan = json.dumps(build_data) + build_plan_filename = os.path.join(artifacts_dir, + '{}.plan.json'.format(content_hash)) + if not os.path.exists(artifacts_dir): + os.makedirs(artifacts_dir, exist_ok=True) + with open(build_plan_filename, 'w') as f: + f.write(build_plan) + + # Output the result to Terraform. + json.dump({ + 'filename': filename, + 'build_plan': build_plan, + 'build_plan_filename': build_plan_filename, + 'timestamp': str(timestamp), + 'was_missing': 'true' if was_missing else 'false', + }, sys.stdout, indent=2) + sys.stdout.write('\n') + + +def build_command(args): + """ + Builds a zip file from the source_dir or source_file. + Installs dependencies with pip or npm automatically. + """ + + log = logging.getLogger('build') + + dump_env() + if log.isEnabledFor(DEBUG2): + log.debug('CMD: python3 %s', shlex_join(sys.argv)) + + with open(args.build_plan_file) as f: + query_data = json.load(f) + query = datatree('build_query', **query_data) + + runtime = query.runtime + filename = query.filename + build_plan = query.build_plan + _timestamp = args.zip_file_timestamp + + timestamp = 0 + if _timestamp.isnumeric(): + timestamp = int(_timestamp) + + if os.path.exists(filename) and not args.force: + log.info('Reused: %s', shlex.quote(filename)) + return + + # Zip up the build plan and write it to the target filename. + # This will be used by the Lambda function as the source code package. + with ZipWriteStream(filename) as zs: + bpm = BuildPlanManager(args, log=log) + bpm.execute(build_plan, zs, query) + + os.utime(filename, ns=(timestamp, timestamp)) + log.info('Created: %s', shlex.quote(filename)) + if log.isEnabledFor(logging.DEBUG): + with open(filename, 'rb') as f: + log.info('Base64sha256: %s', source_code_hash(f.read())) + + +def add_hidden_commands(sub_parsers): + sp = sub_parsers + + def hidden_parser(name, **kwargs): + p = sp.add_parser(name, **kwargs) + sp._choices_actions.pop() # XXX: help=argparse.SUPPRESS - doesn't work + return p + + p = hidden_parser('docker', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_run_command( + args.build_root, args.docker_command, args.runtime, interactive=True))) + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_command', help='A docker container command', + metavar='command', nargs=argparse.REMAINDER) + p.add_argument('-r', '--runtime', help='A docker image runtime', + default='python3.8') + + p = hidden_parser('docker-image', help='Run docker build') + p.set_defaults(command=lambda args: subprocess.call(docker_build_command( + args.build_root, args.docker_file, args.tag))) + p.add_argument('-t', '--tag', help='A docker image tag') + p.add_argument('build_root', help='A docker build root folder') + p.add_argument('docker_file', help='A docker file path', + nargs=argparse.OPTIONAL) + + def zip_cmd(args): + if args.verbose: + log.setLevel(logging.DEBUG) + with ZipWriteStream(args.zipfile) as zs: + zs.write_dirs(*args.dir, timestamp=args.timestamp) + if log.isEnabledFor(logging.DEBUG): + zipinfo = shutil.which('zipinfo') + if zipinfo: + log.debug('-' * 80) + subprocess.call([zipinfo, args.zipfile]) + log.debug('-' * 80) + log.debug('Source code hash: %s', + source_code_hash(open(args.zipfile, 'rb').read())) + + p = hidden_parser('zip', help='Zip folder with provided files timestamp') + p.set_defaults(command=zip_cmd) + p.add_argument('zipfile', help='Path to a zip file') + p.add_argument('dir', nargs=argparse.ONE_OR_MORE, + help='Path to a directory for packaging') + p.add_argument('-t', '--timestamp', type=int, + help='A timestamp to override for all zip members') + p.add_argument('-v', '--verbose', action='store_true') + + p = hidden_parser('hash', help='Generate content hash for a file') + p.set_defaults( + command=lambda args: print(source_code_hash(args.file.read()))) + p.add_argument('file', help='Path to a file', type=argparse.FileType('rb')) + + +def args_parser(): + ap = argparse.ArgumentParser() + ap.set_defaults(command=lambda _: ap.print_usage()) + sp = ap.add_subparsers(metavar="COMMAND") + + p = sp.add_parser('prepare', + help='compute a filename hash for a zip archive') + p.set_defaults(command=prepare_command) + + p = sp.add_parser('build', + help='build and pack to a zip archive') + p.set_defaults(command=build_command) + p.add_argument('--force', action='store_true', + help='Force rebuilding even if a zip artifact exists') + p.add_argument('-t', '--timestamp', + dest='zip_file_timestamp', required=True, + help='A zip file timestamp generated by the prepare command') + p.add_argument('build_plan_file', metavar='PLAN_FILE', + help='A build plan file provided by the prepare command') + add_hidden_commands(sp) + return ap + + +def main(): + ns = argparse.Namespace( + pattern_comments=yesno_bool(os.environ.get( + 'TF_LAMBDA_PACKAGE_PATTERN_COMMENTS', False)), + recreate_missing_package=os.environ.get( + 'TF_RECREATE_MISSING_LAMBDA_PACKAGE', None), + log_level=os.environ.get('TF_LAMBDA_PACKAGE_LOG_LEVEL', 'INFO'), + ) + + p = args_parser() + args = p.parse_args(namespace=ns) + + if args.command is prepare_command: + configure_logging(use_tf_stderr=True) + else: + configure_logging() + + if args.log_level: + ll = logging._nameToLevel.get(args.log_level) + if ll and logging._checkLevel(ll): + logging.root.setLevel(args.log_level) + + exit(args.command(args)) + + +if __name__ == '__main__': + main() diff --git a/modules/aws-lambda/package.tf b/modules/aws-lambda/package.tf new file mode 100644 index 0000000..ca473a4 --- /dev/null +++ b/modules/aws-lambda/package.tf @@ -0,0 +1,74 @@ +locals { + python = (substr(pathexpand("~"), 0, 1) == "/") ? "python3" : "python.exe" +} + +# Generates a filename for the zip archive based on the content of the files +# in source_path. The filename will change when the source code changes. +data "external" "archive_prepare" { + count = var.create && var.create_package ? 1 : 0 + + program = [local.python, "${path.module}/package.py", "prepare"] + + query = { + paths = jsonencode({ + module = path.module + root = path.root + cwd = path.cwd + }) + + docker = var.build_in_docker ? jsonencode({ + docker_pip_cache = var.docker_pip_cache + docker_build_root = var.docker_build_root + docker_file = var.docker_file + docker_image = var.docker_image + with_ssh_agent = var.docker_with_ssh_agent + }) : null + + artifacts_dir = var.artifacts_dir + runtime = var.runtime + source_path = jsonencode(var.source_path) + hash_extra = var.hash_extra + hash_extra_paths = jsonencode( + [ + # Temporary fix when building from multiple locations + # We should take into account content of package.py when counting hash + # Related issue: https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/63 + # "${path.module}/package.py" + ] + ) + + recreate_missing_package = var.recreate_missing_package + } +} + +# This transitive resource used as a bridge between a state stored +# in a Terraform plan and a call of a build command on the apply stage +# to transfer a noticeable amount of data +resource "local_file" "archive_plan" { + count = var.create && var.create_package ? 1 : 0 + + content = data.external.archive_prepare[0].result.build_plan + filename = data.external.archive_prepare[0].result.build_plan_filename + directory_permission = "0755" + file_permission = "0644" +} + +# Build the zip archive whenever the filename changes. +resource "null_resource" "archive" { + count = var.create && var.create_package ? 1 : 0 + + triggers = { + filename = data.external.archive_prepare[0].result.filename + timestamp = data.external.archive_prepare[0].result.timestamp + } + + provisioner "local-exec" { + interpreter = [ + local.python, "${path.module}/package.py", "build", + "--timestamp", data.external.archive_prepare[0].result.timestamp + ] + command = data.external.archive_prepare[0].result.build_plan_filename + } + + depends_on = [local_file.archive_plan] +} diff --git a/modules/aws-lambda/variables.tf b/modules/aws-lambda/variables.tf new file mode 100644 index 0000000..81bd505 --- /dev/null +++ b/modules/aws-lambda/variables.tf @@ -0,0 +1,674 @@ +variable "create" { + description = "Controls whether resources should be created" + type = bool + default = true +} + +variable "create_package" { + description = "Controls whether Lambda package should be created" + type = bool + default = true +} + +variable "create_function" { + description = "Controls whether Lambda Function resource should be created" + type = bool + default = true +} + +variable "create_layer" { + description = "Controls whether Lambda Layer resource should be created" + type = bool + default = false +} + +variable "create_role" { + description = "Controls whether IAM role for Lambda Function should be created" + type = bool + default = true +} + +variable "create_lambda_function_url" { + description = "Controls whether the Lambda Function URL resource should be created" + type = bool + default = false +} + +########### +# Function +########### + +variable "lambda_at_edge" { + description = "Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function" + type = bool + default = false +} + +variable "function_name" { + description = "A unique name for your Lambda Function" + type = string + default = "" +} + +variable "handler" { + description = "Lambda Function entrypoint in your code" + type = string + default = "" +} + +variable "runtime" { + description = "Lambda Function runtime" + type = string + default = "" + + # validation { + # condition = can(var.create && contains(["nodejs10.x", "nodejs12.x", "java8", "java11", "python2.7", " python3.6", "python3.7", "python3.8", "dotnetcore2.1", "dotnetcore3.1", "go1.x", "ruby2.5", "ruby2.7", "provided"], var.runtime)) + # error_message = "The runtime value must be one of supported by AWS Lambda." + # } +} + + + +variable "description" { + description = "Description of your Lambda Function (or Layer)" + type = string + default = "" +} + +variable "layers" { + description = "List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function." + type = list(string) + default = null +} + +variable "architectures" { + description = "Instruction set architecture for your Lambda function. Valid values are [\"x86_64\"] and [\"arm64\"]." + type = list(string) + default = null +} + +variable "kms_key_arn" { + description = "The ARN of KMS key to use by your Lambda Function" + type = string + default = null +} + +variable "memory_size" { + description = "Amount of memory in MB your Lambda Function can use at runtime. Valid value between 128 MB to 10,240 MB (10 GB), in 64 MB increments." + type = number + default = 128 +} + +variable "ephemeral_storage_size" { + description = "Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB)." + type = number + default = 512 +} + +variable "publish" { + description = "Whether to publish creation/change as new Lambda Function Version." + type = bool + default = false +} + +variable "reserved_concurrent_executions" { + description = "The amount of reserved concurrent executions for this Lambda Function. A value of 0 disables Lambda Function from being triggered and -1 removes any concurrency limitations. Defaults to Unreserved Concurrency Limits -1." + type = number + default = -1 +} + +variable "timeout" { + description = "The amount of time your Lambda Function has to run in seconds." + type = number + default = 3 +} + +variable "dead_letter_target_arn" { + description = "The ARN of an SNS topic or SQS queue to notify when an invocation fails." + type = string + default = null +} + +variable "environment_variables" { + description = "A map that defines environment variables for the Lambda Function." + type = map(string) + default = {} +} + +variable "tracing_mode" { + description = "Tracing mode of the Lambda Function. Valid value can be either PassThrough or Active." + type = string + default = null +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +variable "vpc_id" { + description = "vpc id" + type = string + default = "" +} + +variable "vpc_security_group_ids" { + description = "List of security group ids when Lambda Function should run in the VPC." + type = list(string) + default = null +} + +variable "tags" { + description = "A map of tags to assign to resources." + type = map(string) + default = {} +} + +variable "s3_object_tags" { + description = "A map of tags to assign to S3 bucket object." + type = map(string) + default = {} +} + +variable "s3_object_tags_only" { + description = "Set to true to not merge tags with s3_object_tags. Useful to avoid breaching S3 Object 10 tag limit." + type = bool + default = false +} + +variable "package_type" { + description = "The Lambda deployment package type. Valid options: Zip or Image" + type = string + default = "Zip" +} + +variable "image_uri" { + description = "The ECR image URI containing the function's deployment package." + type = string + default = null +} + +variable "image_config_entry_point" { + description = "The ENTRYPOINT for the docker image" + type = list(string) + default = [] + +} +variable "image_config_command" { + description = "The CMD for the docker image" + type = list(string) + default = [] +} + +variable "image_config_working_directory" { + description = "The working directory for the docker image" + type = string + default = null +} + +############### +# Function URL +############### + +variable "create_unqualified_alias_lambda_function_url" { + description = "Whether to use unqualified alias pointing to $LATEST version in Lambda Function URL" + type = bool + default = true +} + +variable "authorization_type" { + description = "The type of authentication that the Lambda Function URL uses. Set to 'AWS_IAM' to restrict access to authenticated IAM users only. Set to 'NONE' to bypass IAM authentication and create a public endpoint." + type = string + default = "NONE" +} + +variable "cors" { + description = "CORS settings to be used by the Lambda Function URL" + type = any + default = {} +} + +######## +# Layer +######## + +variable "layer_name" { + description = "Name of Lambda Layer to create" + type = string + default = "" +} + +variable "layer_skip_destroy" { + description = "Whether to retain the old version of a previously deployed Lambda Layer." + type = bool + default = false +} + +variable "license_info" { + description = "License info for your Lambda Layer. Eg, MIT or full url of a license." + type = string + default = "" +} + +variable "compatible_runtimes" { + description = "A list of Runtimes this layer is compatible with. Up to 5 runtimes can be specified." + type = list(string) + default = [] +} + +variable "compatible_architectures" { + description = "A list of Architectures Lambda layer is compatible with. Currently x86_64 and arm64 can be specified." + type = list(string) + default = null +} + +############################ +# Lambda Async Event Config +############################ + +variable "create_async_event_config" { + description = "Controls whether async event configuration for Lambda Function/Alias should be created" + type = bool + default = false +} + +variable "create_current_version_async_event_config" { + description = "Whether to allow async event configuration on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources)" + type = bool + default = true +} + +variable "create_unqualified_alias_async_event_config" { + description = "Whether to allow async event configuration on unqualified alias pointing to $LATEST version" + type = bool + default = true +} + +variable "maximum_event_age_in_seconds" { + description = "Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600." + type = number + default = null +} + +variable "maximum_retry_attempts" { + description = "Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2." + type = number + default = null +} + +variable "destination_on_failure" { + description = "Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations" + type = string + default = null +} + +variable "destination_on_success" { + description = "Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations" + type = string + default = null +} + +########################## +# Provisioned Concurrency +########################## + +variable "provisioned_concurrent_executions" { + description = "Amount of capacity to allocate. Set to 1 or greater to enable, or set to 0 to disable provisioned concurrency." + type = number + default = -1 +} + +############################################ +# Lambda Permissions (for allowed triggers) +############################################ + +variable "create_current_version_allowed_triggers" { + description = "Whether to allow triggers on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources)" + type = bool + default = true +} + +variable "create_unqualified_alias_allowed_triggers" { + description = "Whether to allow triggers on unqualified alias pointing to $LATEST version" + type = bool + default = true +} + +variable "allowed_triggers" { + description = "Map of allowed triggers to create Lambda permissions" + type = map(any) + default = {} +} + +############################################ +# Lambda Event Source Mapping +############################################ + +variable "event_source_mapping" { + description = "Map of event source mapping" + type = any + default = {} +} + +################# +# CloudWatch Logs +################# + +variable "use_existing_cloudwatch_log_group" { + description = "Whether to use an existing CloudWatch log group or create new" + type = bool + default = false +} + +variable "cloudwatch_logs_retention_in_days" { + description = "Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653." + type = number + default = null +} + +variable "cloudwatch_logs_kms_key_id" { + description = "The ARN of the KMS Key to use when encrypting log data." + type = string + default = null +} + +variable "cloudwatch_logs_tags" { + description = "A map of tags to assign to the resource." + type = map(string) + default = {} +} + +###### +# IAM +###### + +variable "role_name" { + description = "Name of IAM role to use for Lambda Function" + type = string + default = null +} + +variable "role_description" { + description = "Description of IAM role to use for Lambda Function" + type = string + default = null +} + +variable "role_path" { + description = "Path of IAM role to use for Lambda Function" + type = string + default = null +} + +variable "role_force_detach_policies" { + description = "Specifies to force detaching any policies the IAM role has before destroying it." + type = bool + default = true +} + +variable "role_permissions_boundary" { + description = "The ARN of the policy that is used to set the permissions boundary for the IAM role used by Lambda Function" + type = string + default = null +} + +variable "role_tags" { + description = "A map of tags to assign to IAM role" + type = map(string) + default = {} +} + +########### +# Policies +########### + +variable "attach_cloudwatch_logs_policy" { + description = "Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function" + type = bool + default = true +} + +variable "attach_dead_letter_policy" { + description = "Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_network_policy" { + description = "Controls whether VPC/network policy should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_tracing_policy" { + description = "Controls whether X-Ray tracing policy should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_async_event_policy" { + description = "Controls whether async event policy should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_policy_json" { + description = "Controls whether policy_json should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_policy_jsons" { + description = "Controls whether policy_jsons should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_policy" { + description = "Controls whether policy should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "attach_policies" { + description = "Controls whether list of policies should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "policy_path" { + description = "Path of policies to that should be added to IAM role for Lambda Function" + type = string + default = null +} + +variable "number_of_policy_jsons" { + description = "Number of policies JSON to attach to IAM role for Lambda Function" + type = number + default = 0 +} + +variable "number_of_policies" { + description = "Number of policies to attach to IAM role for Lambda Function" + type = number + default = 0 +} + +variable "attach_policy_statements" { + description = "Controls whether policy_statements should be added to IAM role for Lambda Function" + type = bool + default = false +} + +variable "trusted_entities" { + description = "List of additional trusted entities for assuming Lambda Function role (trust relationship)" + type = any + default = [] +} + +variable "assume_role_policy_statements" { + description = "Map of dynamic policy statements for assuming Lambda Function role (trust relationship)" + type = any + default = {} +} + +variable "policy_json" { + description = "An additional policy document as JSON to attach to the Lambda Function role" + type = string + default = null +} + +variable "policy_jsons" { + description = "List of additional policy documents as JSON to attach to Lambda Function role" + type = list(string) + default = [] +} + +variable "policy" { + description = "An additional policy document ARN to attach to the Lambda Function role" + type = string + default = null +} + +variable "policies" { + description = "List of policy statements ARN to attach to Lambda Function role" + type = list(string) + default = [] +} + +variable "policy_statements" { + description = "Map of dynamic policy statements to attach to Lambda Function role" + type = any + default = {} +} + +variable "file_system_arn" { + description = "The Amazon Resource Name (ARN) of the Amazon EFS Access Point that provides access to the file system." + type = string + default = null +} + +variable "file_system_local_mount_path" { + description = "The path where the function can access the file system, starting with /mnt/." + type = string + default = null +} + +########################## +# Build artifact settings +########################## + +variable "artifacts_dir" { + description = "Directory name where artifacts should be stored" + type = string + default = "builds" +} + +variable "s3_prefix" { + description = "Directory name where artifacts should be stored in the S3 bucket. If unset, the path from `artifacts_dir` is used" + type = string + default = null +} + +variable "ignore_source_code_hash" { + description = "Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately." + type = bool + default = false +} + +variable "local_existing_package" { + description = "The absolute path to an existing zip-file to use" + type = string + default = null +} + +variable "s3_existing_package" { + description = "The S3 bucket object with keys bucket, key, version pointing to an existing zip-file to use" + type = map(string) + default = null +} + +variable "store_on_s3" { + description = "Whether to store produced artifacts on S3 or locally." + type = bool + default = false +} + +variable "s3_object_storage_class" { + description = "Specifies the desired Storage Class for the artifact uploaded to S3. Can be either STANDARD, REDUCED_REDUNDANCY, ONEZONE_IA, INTELLIGENT_TIERING, or STANDARD_IA." + type = string + default = "ONEZONE_IA" # Cheaper than STANDARD and it is enough for Lambda deployments +} + +variable "s3_bucket" { + description = "S3 bucket to store artifacts" + type = string + default = null +} + +variable "s3_acl" { + description = "The canned ACL to apply. Valid values are private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, and bucket-owner-full-control. Defaults to private." + type = string + default = "private" +} + +variable "s3_server_side_encryption" { + description = "Specifies server-side encryption of the object in S3. Valid values are \"AES256\" and \"aws:kms\"." + type = string + default = null +} + +variable "source_path" { + description = "The absolute path to a local file or directory containing your Lambda source code" + type = any # string | list(string | map(any)) + default = null +} + +variable "hash_extra" { + description = "The string to add into hashing function. Useful when building same source path for different functions." + type = string + default = "" +} + +variable "build_in_docker" { + description = "Whether to build dependencies in Docker" + type = bool + default = false +} + +variable "docker_file" { + description = "Path to a Dockerfile when building in Docker" + type = string + default = "" +} + +variable "docker_build_root" { + description = "Root dir where to build in Docker" + type = string + default = "" +} + +variable "docker_image" { + description = "Docker image to use for the build" + type = string + default = "" +} + +variable "docker_with_ssh_agent" { + description = "Whether to pass SSH_AUTH_SOCK into docker environment or not" + type = bool + default = false +} + +variable "docker_pip_cache" { + description = "Whether to mount a shared pip cache folder into docker environment or not" + type = any + default = null +} + +variable "recreate_missing_package" { + description = "Whether to recreate missing Lambda package if it is missing locally or not" + type = bool + default = true +} diff --git a/modules/aws-lambda/versions.tf b/modules/aws-lambda/versions.tf new file mode 100644 index 0000000..094732c --- /dev/null +++ b/modules/aws-lambda/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.9" + } + external = { + source = "hashicorp/external" + version = ">= 1.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-mongodbatlas-access/README.md b/modules/aws-mongodbatlas-access/README.md new file mode 100644 index 0000000..26a171a --- /dev/null +++ b/modules/aws-mongodbatlas-access/README.md @@ -0,0 +1,42 @@ +# terraform-aws-mongodbatlas-cluster +Terraform module which helps to enable access configuration for Atlas cluster on AWS + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [mongodbatlas](#requirement\_mongodbatlas) | >= 1.4.5 | + +## Providers + +| Name | Version | +|------|---------| +| [mongodbatlas](#provider\_mongodbatlas) | >= 1.4.5 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [mongodbatlas_cloud_provider_access_setup.cloud_provider_access_role](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cloud_provider_access_setup) | resource | +| [mongodbatlas_project.project](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/data-sources/project) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [atlas\_project\_name](#input\_atlas\_project\_name) | MongoDB atlas project name | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [atlas\_assumed\_role\_external\_id](#output\_atlas\_assumed\_role\_external\_id) | n/a | +| [atlas\_aws\_account\_arn](#output\_atlas\_aws\_account\_arn) | n/a | +| [atlas\_role\_id](#output\_atlas\_role\_id) | n/a | + \ No newline at end of file diff --git a/modules/aws-mongodbatlas-access/main.tf b/modules/aws-mongodbatlas-access/main.tf new file mode 100644 index 0000000..5d53355 --- /dev/null +++ b/modules/aws-mongodbatlas-access/main.tf @@ -0,0 +1,42 @@ +# --------------------------------------------------------------------------------------------------------------------- +# CREATE AN ATLAS PROJECT THAT THE CLUSTER WILL RUN INSIDE +# --------------------------------------------------------------------------------------------------------------------- + +data "mongodbatlas_project" "project" { + count = var.create_mongodbatlas_project ? 0 : 1 + name = var.project_name +} + +resource "mongodbatlas_project" "project" { + count = var.create_mongodbatlas_project ? 1 : 0 + name = var.project_name + org_id = var.org_id + + #Associate teams and privileges if passed, if not - run with an empty object + dynamic "teams" { + for_each = var.teams + + content { + team_id = mongodbatlas_team.team[teams.key].team_id + role_names = [teams.value.role] + } + } + +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE TEAMS FROM **EXISTING USERS** +# --------------------------------------------------------------------------------------------------------------------- + +resource "mongodbatlas_team" "team" { + for_each = var.teams + + org_id = var.org_id + name = each.key + usernames = each.value.users +} + +resource "mongodbatlas_cloud_provider_access_setup" "cloud_provider_access_role" { + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + provider_name = "AWS" +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-access/outputs.tf b/modules/aws-mongodbatlas-access/outputs.tf new file mode 100644 index 0000000..6fe8dc5 --- /dev/null +++ b/modules/aws-mongodbatlas-access/outputs.tf @@ -0,0 +1,17 @@ +output "atlas_assumed_role_external_id" { + value = mongodbatlas_cloud_provider_access_setup.cloud_provider_access_role.aws_config[0].atlas_assumed_role_external_id +} + +output "atlas_aws_account_arn" { + value = mongodbatlas_cloud_provider_access_setup.cloud_provider_access_role.aws_config[0].atlas_aws_account_arn +} + +output "atlas_role_id" { + value = mongodbatlas_cloud_provider_access_setup.cloud_provider_access_role.role_id +} + +output "project_id" { + description = "Mongoatlas project ID" + value = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id +} + diff --git a/modules/aws-mongodbatlas-access/variables.tf b/modules/aws-mongodbatlas-access/variables.tf new file mode 100644 index 0000000..44ebca2 --- /dev/null +++ b/modules/aws-mongodbatlas-access/variables.tf @@ -0,0 +1,22 @@ +variable "project_name" { + description = "The name of the project you want to create" + type = string +} + +variable "create_mongodbatlas_project" { + type = bool + description = "Create mongoDB atlas project flag" + default = false + +} + +variable "org_id" { + description = "The ID of the Atlas organization you want to create the project within" + type = string +} + +variable "teams" { + description = "An object that contains all the groups that should be created in the project" + type = map(any) + default = {} +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-access/versions.tf b/modules/aws-mongodbatlas-access/versions.tf new file mode 100644 index 0000000..d59a131 --- /dev/null +++ b/modules/aws-mongodbatlas-access/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + version = ">= 1.4.5" + } + } +} diff --git a/modules/aws-mongodbatlas-auth/README.md b/modules/aws-mongodbatlas-auth/README.md new file mode 100644 index 0000000..6543583 --- /dev/null +++ b/modules/aws-mongodbatlas-auth/README.md @@ -0,0 +1,39 @@ +# terraform-aws-mongodbatlas-cluster +Terraform module which grants the configuration at the Atlas cluster to AWS + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [mongodbatlas](#requirement\_mongodbatlas) | >= 1.4.5 | + +## Providers + +| Name | Version | +|------|---------| +| [mongodbatlas](#provider\_mongodbatlas) | >= 1.4.5 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [mongodbatlas_cloud_provider_access_authorization.auth_role](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cloud_provider_access_authorization) | resource | +| [mongodbatlas_project.project](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/data-sources/project) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [atlas\_project\_name](#input\_atlas\_project\_name) | MongoDB atlas project id | `string` | n/a | yes | +| [atlas\_role\_id](#input\_atlas\_role\_id) | MongoDB atlas role id | `string` | n/a | yes | +| [aws\_iam\_role\_arn](#input\_aws\_iam\_role\_arn) | AWS IAM Role arn | `string` | n/a | yes | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/modules/aws-mongodbatlas-auth/aws-roles.tf b/modules/aws-mongodbatlas-auth/aws-roles.tf new file mode 100644 index 0000000..dc6a13b --- /dev/null +++ b/modules/aws-mongodbatlas-auth/aws-roles.tf @@ -0,0 +1,47 @@ +resource "aws_iam_role_policy" "policy" { + name = "mongodb_atlas_kms_policy" + role = aws_iam_role.role.id + + policy = <<-EOF + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:DescribeKey" + ], + "Resource": [ + "${var.aws_kms_key_arn}" + ] + } + ] + } + EOF +} + +resource "aws_iam_role" "role" { + name = "mongodb_atlas_kms_role" + + assume_role_policy = <<-EOF + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "${var.atlas_aws_account_arn}" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "${var.atlas_assumed_role_external_id}" + } + } + } + ] + } + EOF +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-auth/main.tf b/modules/aws-mongodbatlas-auth/main.tf new file mode 100644 index 0000000..6594712 --- /dev/null +++ b/modules/aws-mongodbatlas-auth/main.tf @@ -0,0 +1,13 @@ +data "mongodbatlas_project" "project" { + name = var.project_name + +} + +resource "mongodbatlas_cloud_provider_access_authorization" "auth_role" { + project_id = data.mongodbatlas_project.project.id + role_id = var.atlas_role_id + + aws { + iam_assumed_role_arn = aws_iam_role.role.arn + } +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-auth/outputs.tf b/modules/aws-mongodbatlas-auth/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-mongodbatlas-auth/variables.tf b/modules/aws-mongodbatlas-auth/variables.tf new file mode 100644 index 0000000..0b82289 --- /dev/null +++ b/modules/aws-mongodbatlas-auth/variables.tf @@ -0,0 +1,23 @@ +variable "project_name" { + type = string + description = "MongoDB atlas project id" +} +variable "atlas_role_id" { + type = string + description = "MongoDB atlas role id" +} + +variable "aws_kms_key_arn" { + type = string + description = "AWS KMS Key arn" +} + +variable "atlas_aws_account_arn" { + type = string + description = "Atlas AWS account arn" +} + +variable "atlas_assumed_role_external_id" { + type = string + description = "Atlas assumed role external id" +} diff --git a/modules/aws-mongodbatlas-auth/versions.tf b/modules/aws-mongodbatlas-auth/versions.tf new file mode 100644 index 0000000..d59a131 --- /dev/null +++ b/modules/aws-mongodbatlas-auth/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + version = ">= 1.4.5" + } + } +} diff --git a/modules/aws-mongodbatlas-cluster/README.md b/modules/aws-mongodbatlas-cluster/README.md new file mode 100644 index 0000000..2886bfe --- /dev/null +++ b/modules/aws-mongodbatlas-cluster/README.md @@ -0,0 +1,183 @@ +# terraform-aws-mongodbatlas-cluster +Terraform module which creates a MongoDB Atlas cluster on AWS + +These types of resources are supported: +* [Project](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/project) +* [Cluster](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cluster) +* [Teams](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/team) +* [Whitelists](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/project_ip_whitelist.html) +* [Network Peering](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/network_peering.html) + +## Terraform versions + +Terraform versions >=0.12 are supported + +## Usage + +```hcl +module "atlas_cluster" { + source = "./terraform-aws-mongodbatlas-cluster" + + project_name = "my-project" + org_id = "5edf67f9b9b528996228111" + + teams = { + Devops: { + users: ["example@mail.io", "user@mail.io"] + role: "GROUP_OWNER" + }, + DevTeam: { + users: ["developer@mail.io",] + role: "GROUP_READ_ONLY" + } + } + + white_lists = { + "example comment": "10.0.0.1/32", + "second example": "10.10.10.8/32" + } + + region = "EU_WEST_1" + + cluster_name = "MyCluster" + + instance_type = "M30" + mongodb_major_ver = 4.2 + cluster_type = "REPLICASET" + num_shards = 1 + replication_specs_region_configs = [ + { + region_name = "EU-WEST-1" + electable_nodes = 2 + priority = 7 + read_only_nodes = 0 + }, + { + region_name = "EU-WEST-2" + electable_nodes = 1 + priority = 6 + read_only_nodes = 0 + } + ] + cloud_backup = true + +} +``` +## Prerequisites +* [MongoDB Cloud account](https://www.mongodb.com/cloud) +* [MongoDB Atlas Organization](https://cloud.mongodb.com/v2#/preferences/organizations/create) +* [MongoDB Atlas API key](https://www.terraform.io/docs/providers/mongodbatlas/index.html) + +#### Manual tasks: + +* Configure the API key before applying the module +* Get your MongoDB Atlas Organization ID from the UI + +#### Create Teams: +In case you want to create Teams and associate them with the project, users must be added in advance and making sure they have accepted the invitation. +Users who have not accepted an invitation to join the organization cannot be added as team members. + +## VPC Peering +The following information is required: +* [Access to AWS](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) +* VPC ID +* [AWS account ID](https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html) +* Region +* [Routable cidr block](https://www.terraform.io/docs/providers/mongodbatlas/r/network_peering.html#route_table_cidr_block) + +You can add them manually, or through other Terraform resources, and pass it to the module via ```vpc_peer``` variable: +```hcl + vpc_peer = { + vpc_peer1 : { + aws_account_id : "020201234877" + region : "eu-west-1" + vpc_id : "vpc-0240c8a47312svc3e" + route_table_cidr_block : "172.16.0.0/16" + }, + vpc_peer2 : { + aws_account_id : "0205432147877" + region : "eu-central-1" + vpc_id : "vpc-0f0dd82430bhv0e1a" + route_table_cidr_block : "172.17.0.0/16" + } + } +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.12 | +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [mongodbatlas](#requirement\_mongodbatlas) | >= 1.4.5 | + +## Providers + +| Name | Version | +|------|---------| +| [mongodbatlas](#provider\_mongodbatlas) | >= 1.4.5 | +| [random](#provider\_random) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [mongodbatlas_cluster.cluster](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cluster) | resource | +| [mongodbatlas_database_user.admin](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/database_user) | resource | +| [mongodbatlas_network_peering.mongo_peer](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/network_peering) | resource | +| [mongodbatlas_project.project](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/project) | resource | +| [mongodbatlas_project_ip_access_list.whitelists](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/project_ip_access_list) | resource | +| [mongodbatlas_team.team](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/team) | resource | +| [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [mongodbatlas_project.project](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/data-sources/project) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_scaling\_disk\_gb\_enabled](#input\_auto\_scaling\_disk\_gb\_enabled) | Indicating if disk auto-scaling is enabled | `bool` | `true` | no | +| [cloud\_backup](#input\_cloud\_backup) | Indicating if the cluster uses Cloud Backup for backups | `bool` | `true` | no | +| [cluster\_name](#input\_cluster\_name) | The cluster name | `string` | n/a | yes | +| [cluster\_type](#input\_cluster\_type) | The MongoDB Atlas cluster type - SHARDED/REPLICASET/GEOSHARDED | `string` | n/a | yes | +| [create\_mongodbatlas\_project](#input\_create\_mongodbatlas\_project) | Create mongoDB atlas project flag | `bool` | `false` | no | +| [disk\_size\_gb](#input\_disk\_size\_gb) | Capacity,in gigabytes,of the host’s root volume | `number` | `null` | no | +| [encryption\_at\_rest\_provider](#input\_encryption\_at\_rest\_provider) | Possible values are AWS, GCP, AZURE or NONE | `string` | `""` | no | +| [instance\_type](#input\_instance\_type) | The Atlas instance-type name | `string` | n/a | yes | +| [mongodb\_major\_ver](#input\_mongodb\_major\_ver) | The MongoDB cluster major version | `number` | n/a | yes | +| [num\_shards](#input\_num\_shards) | number of shards | `number` | n/a | yes | +| [org\_id](#input\_org\_id) | The ID of the Atlas organization you want to create the project within | `string` | n/a | yes | +| [pit\_enabled](#input\_pit\_enabled) | Indicating if the cluster uses Continuous Cloud Backup, if set to true - cloud\_backup must also be set to true | `bool` | `false` | no | +| [project\_name](#input\_project\_name) | The name of the project you want to create | `string` | n/a | yes | +| [provider\_disk\_iops](#input\_provider\_disk\_iops) | The maximum IOPS the system can perform | `number` | `null` | no | +| [region](#input\_region) | The AWS region-name that the cluster will be deployed on | `string` | n/a | yes | +| [replication\_specs\_region\_configs](#input\_replication\_specs\_region\_configs) | Region configs for replication specs | `list(any)` | n/a | yes | +| [teams](#input\_teams) | An object that contains all the groups that should be created in the project | `map(any)` | `{}` | no | +| [volume\_type](#input\_volume\_type) | STANDARD or PROVISIONED for IOPS higher than the default instance IOPS | `string` | `"STANDARD"` | no | +| [vpc\_peer](#input\_vpc\_peer) | An object that contains all VPC peering requests from the cluster to AWS VPC's | `map(any)` | `{}` | no | +| [white\_lists](#input\_white\_lists) | An object that contains all the network white-lists that should be created in the project | `map(any)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_id](#output\_cluster\_id) | The cluster ID | +| [cluster\_name](#output\_cluster\_name) | Mongo Atlas cluster name | +| [connection\_strings](#output\_connection\_strings) | Set of connection strings that your applications use to connect to this cluster | +| [container\_id](#output\_container\_id) | The Network Peering Container ID | +| [database\_admin\_password](#output\_database\_admin\_password) | Database Admin password | +| [database\_admin\_username](#output\_database\_admin\_username) | Database Admin username | +| [mongo\_db\_version](#output\_mongo\_db\_version) | Version of MongoDB the cluster runs, in major-version.minor-version format | +| [mongo\_uri](#output\_mongo\_uri) | Base connection string for the cluster | +| [mongo\_uri\_updated](#output\_mongo\_uri\_updated) | Lists when the connection string was last updated | +| [mongo\_uri\_with\_options](#output\_mongo\_uri\_with\_options) | connection string for connecting to the Atlas cluster. Includes the replicaSet, ssl, and authSource query parameters in the connection string with values appropriate for the cluster | +| [paused](#output\_paused) | Flag that indicates whether the cluster is paused or not | +| [project\_id](#output\_project\_id) | Mongoatlas project ID | +| [srv\_address](#output\_srv\_address) | Connection string for connecting to the Atlas cluster. The +srv modifier forces the connection to use TLS/SSL | +| [state\_name](#output\_state\_name) | Current state of the cluster | +| [vpc\_peering\_ids](#output\_vpc\_peering\_ids) | Mongoatlas VPC Peering IDs | + \ No newline at end of file diff --git a/modules/aws-mongodbatlas-cluster/main.tf b/modules/aws-mongodbatlas-cluster/main.tf new file mode 100644 index 0000000..defed78 --- /dev/null +++ b/modules/aws-mongodbatlas-cluster/main.tf @@ -0,0 +1,189 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# REQUIRE A SPECIFIC TERRAFORM VERSION OR HIGHER +# This module has been updated with 0.12 syntax, which means it is no longer compatible with any versions below 0.12. +# ---------------------------------------------------------------------------------------------------------------------- + +terraform { + required_version = ">= 0.12" +} + +locals { + network_containers = { for container in data.mongodbatlas_network_containers.aws_containers.results : lower(replace(container.region_name, "_", "-")) => { "id" = container.id, "atlas_cidr_block" = container.atlas_cidr_block } } +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE AN ATLAS PROJECT THAT THE CLUSTER WILL RUN INSIDE +# --------------------------------------------------------------------------------------------------------------------- + +data "mongodbatlas_project" "project" { + count = var.create_mongodbatlas_project ? 0 : 1 + name = var.project_name +} + +resource "mongodbatlas_project" "project" { + count = var.create_mongodbatlas_project ? 1 : 0 + name = var.project_name + org_id = var.org_id + + #Associate teams and privileges if passed, if not - run with an empty object + dynamic "teams" { + for_each = var.teams + + content { + team_id = mongodbatlas_team.team[teams.key].team_id + role_names = [teams.value.role] + } + } + +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE TEAMS FROM **EXISTING USERS** +# --------------------------------------------------------------------------------------------------------------------- + +resource "mongodbatlas_team" "team" { + for_each = var.teams + + org_id = var.org_id + name = each.key + usernames = each.value.users +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE ADMIN USER +# --------------------------------------------------------------------------------------------------------------------- + +resource "random_password" "password" { + length = 14 + special = false + upper = false +} + +resource "mongodbatlas_database_user" "admin" { + + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + username = "${var.cluster_name}-admin" + password = random_password.password.result + auth_database_name = "admin" + + roles { + role_name = "atlasAdmin" + database_name = "admin" + } + scopes { + name = var.cluster_name + type = "CLUSTER" + } +} + + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE NETWORK WHITE-LISTS FOR ACCESSING THE PROJECT +# --------------------------------------------------------------------------------------------------------------------- + +#Optionall, if no variable is passed, the loop will run on an empty object. + +resource "mongodbatlas_project_ip_access_list" "whitelists_with_cidr" { + for_each = var.white_lists_with_cidr + + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + comment = each.key + cidr_block = each.value +} + +resource "mongodbatlas_project_ip_access_list" "whitelists_with_aws_sg" { + for_each = var.white_lists_with_security_group + + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + comment = each.key + aws_security_group = each.value +} + + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE MONGODB ATLAS CLUSTER IN THE PROJECT +# --------------------------------------------------------------------------------------------------------------------- + +resource "mongodbatlas_cluster" "cluster" { + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + name = var.cluster_name + disk_size_gb = var.disk_size_gb + num_shards = var.num_shards + cloud_backup = var.cloud_backup + cluster_type = var.cluster_type + provider_name = local.cloud_provider + provider_instance_size_name = var.instance_type + mongo_db_major_version = var.mongodb_major_ver + pit_enabled = var.pit_enabled + auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled + provider_volume_type = var.volume_type + provider_disk_iops = var.provider_disk_iops + encryption_at_rest_provider = var.encryption_at_rest_provider + + + replication_specs { + num_shards = var.num_shards + dynamic "regions_config" { + for_each = var.replication_specs_region_configs + content { + region_name = lookup(regions_config.value, "region_name") + electable_nodes = lookup(regions_config.value, "electable_nodes") + priority = lookup(regions_config.value, "priority") + read_only_nodes = lookup(regions_config.value, "read_only_nodes") + } + } + + } + depends_on = [ + mongodbatlas_custom_dns_configuration_cluster_aws.aws, + mongodbatlas_encryption_at_rest.aws_encryption, + ] + +} + +# --------------------------------------------------------------------------------------------------------------------- +# Encryption at Rest +# --------------------------------------------------------------------------------------------------------------------- + + +resource "mongodbatlas_encryption_at_rest" "aws_encryption" { + count = var.encryption_at_rest_provider != "" ? 1 : 0 + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + + aws_kms_config { + enabled = true + customer_master_key_id = lookup(var.aws_kms_config, "customer_master_key_id") + region = lookup(var.aws_kms_config, "region") + role_id = lookup(var.aws_kms_config, "atlas_role_id") + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE AWS PEER REQUESTS TO AWS VPC +# --------------------------------------------------------------------------------------------------------------------- + + +resource "mongodbatlas_network_peering" "mongo_peer" { + for_each = var.vpc_peer + + accepter_region_name = each.value.accepter_region + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + container_id = lookup(local.network_containers[each.value.atlas_region], "id", null) + provider_name = local.cloud_provider + route_table_cidr_block = each.value.route_table_cidr_block + vpc_id = each.value.vpc_id + aws_account_id = each.value.aws_account_id +} + +data "mongodbatlas_network_containers" "aws_containers" { + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + provider_name = "AWS" + + depends_on = [mongodbatlas_cluster.cluster] +} + + +resource "mongodbatlas_custom_dns_configuration_cluster_aws" "aws" { + project_id = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id + enabled = true +} diff --git a/modules/aws-mongodbatlas-cluster/outputs.tf b/modules/aws-mongodbatlas-cluster/outputs.tf new file mode 100644 index 0000000..26fce98 --- /dev/null +++ b/modules/aws-mongodbatlas-cluster/outputs.tf @@ -0,0 +1,134 @@ +output "cluster_id" { + value = mongodbatlas_cluster.cluster.cluster_id + description = "The cluster ID" +} + +output "mongo_db_version" { + value = mongodbatlas_cluster.cluster.mongo_db_version + description = "Version of MongoDB the cluster runs, in major-version.minor-version format" +} + +output "mongo_uri" { + value = mongodbatlas_cluster.cluster.mongo_uri + description = "Base connection string for the cluster" +} + +output "mongo_uri_updated" { + value = mongodbatlas_cluster.cluster.mongo_uri_updated + description = "Lists when the connection string was last updated" +} + +output "mongo_uri_with_options" { + value = mongodbatlas_cluster.cluster.mongo_uri_with_options + description = "connection string for connecting to the Atlas cluster. Includes the replicaSet, ssl, and authSource query parameters in the connection string with values appropriate for the cluster" +} + +output "connection_strings" { + value = mongodbatlas_cluster.cluster.connection_strings + description = "Set of connection strings that your applications use to connect to this cluster" +} + +output "mongo_uri_srv_private" { + value = mongodbatlas_cluster.cluster.connection_strings[0].private_srv + description = "Private SRV connection string for the cluster" +} + +output "mongo_uri_private" { + value = mongodbatlas_cluster.cluster.connection_strings[0].private + description = "Private connection string for the cluster" +} + +output "container_id" { + value = mongodbatlas_cluster.cluster.container_id + description = "The Network Peering Container ID" +} + +output "paused" { + value = mongodbatlas_cluster.cluster.paused + description = "Flag that indicates whether the cluster is paused or not" +} + +output "srv_address" { + value = mongodbatlas_cluster.cluster.srv_address + description = "Connection string for connecting to the Atlas cluster. The +srv modifier forces the connection to use TLS/SSL" +} + +output "state_name" { + value = mongodbatlas_cluster.cluster.state_name + description = "Current state of the cluster" +} + + +output "vpc_peering_ids" { + value = { + for key, value in mongodbatlas_network_peering.mongo_peer : key => value.connection_id + } + description = "Mongoatlas VPC Peering IDs" +} + + +output "database_admin_username" { + description = "Database Admin username" + value = mongodbatlas_database_user.admin.username + +} + +output "database_admin_password" { + description = "Database Admin password" + value = random_password.password.result + sensitive = true + +} + +output "cluster_name" { + description = "Mongo Atlas cluster name" + value = var.cluster_name + +} + +output "project_id" { + description = "Mongoatlas project ID" + value = var.create_mongodbatlas_project ? mongodbatlas_project.project[0].id : data.mongodbatlas_project.project[0].id +} + +output "mongodbatlas_aws_containers" { + value = try(local.network_containers, {}) +} + +output "vpc_peering_info_primary" { + value = [ + for k, v in local.network_containers : + { + requester_cidr = v.atlas_cidr_block + peer_id = length([for p in mongodbatlas_network_peering.mongo_peer : p.connection_id if p.container_id == v.id && p.accepter_region_name == var.region]) > 0 ? element([for p in mongodbatlas_network_peering.mongo_peer : p.connection_id if p.container_id == v.id && p.accepter_region_name == var.region], 0) : "" + } + ] + description = "Mongoatlas VPC Peering Info to AWS Primary Region" +} + +output "vpc_peering_info_secondary" { + value = [ + for k, v in local.network_containers : + { + requester_cidr = v.atlas_cidr_block + peer_id = length([for p in mongodbatlas_network_peering.mongo_peer : p.connection_id if p.container_id == v.id && p.accepter_region_name != var.region]) > 0 ? element([for p in mongodbatlas_network_peering.mongo_peer : p.connection_id if p.container_id == v.id && p.accepter_region_name != var.region], 0) : "" + } + ] + description = "Mongoatlas VPC Peering Info to AWS Secondary Region" +} + +output "vpc_peering_ids_primary" { + value = [ + for p in mongodbatlas_network_peering.mongo_peer : p.connection_id + if p.accepter_region_name == var.region + ] + description = "Mongoatlas VPC Peering IDs for AWS primary region" +} + +output "vpc_peering_ids_secondary" { + value = [ + for p in mongodbatlas_network_peering.mongo_peer : p.connection_id + if p.accepter_region_name != var.region + ] + description = "Mongoatlas VPC Peering IDs for AWS primary region" +} diff --git a/modules/aws-mongodbatlas-cluster/variables.tf b/modules/aws-mongodbatlas-cluster/variables.tf new file mode 100644 index 0000000..231561d --- /dev/null +++ b/modules/aws-mongodbatlas-cluster/variables.tf @@ -0,0 +1,135 @@ +variable "project_name" { + description = "The name of the project you want to create" + type = string +} + +variable "create_mongodbatlas_project" { + type = bool + description = "Create mongoDB atlas project flag" + default = false + +} + +variable "org_id" { + description = "The ID of the Atlas organization you want to create the project within" + type = string +} + +variable "teams" { + description = "An object that contains all the groups that should be created in the project" + type = map(any) + default = {} +} + +variable "aws_kms_config" { + description = "AWS KMS config for encrption at rest" + type = map(any) + default = {} +} + +variable "white_lists_with_cidr" { + description = "An object that contains all the network white-lists that should be created in the project" + type = map(any) + default = {} +} + +variable "white_lists_with_security_group" { + description = "An object that contains all the network white-lists that should be created in the project" + type = map(any) + default = {} +} + +variable "region" { + description = "The AWS region-name that the cluster will be deployed on" + type = string +} + +variable "replication_specs_region_configs" { + description = "Region configs for replication specs" + type = list(any) +} + +variable "cluster_name" { + description = "The cluster name" + type = string +} + +variable "instance_type" { + description = "The Atlas instance-type name" + type = string +} + +variable "mongodb_major_ver" { + description = "The MongoDB cluster major version" + type = number +} + +variable "cluster_type" { + description = "The MongoDB Atlas cluster type - SHARDED/REPLICASET/GEOSHARDED" + type = string +} + +variable "num_shards" { + description = "number of shards" + type = number +} + +variable "cloud_backup" { + description = "Indicating if the cluster uses Cloud Backup for backups" + type = bool + default = true +} + +variable "pit_enabled" { + description = "Indicating if the cluster uses Continuous Cloud Backup, if set to true - cloud_backup must also be set to true" + type = bool + default = false +} + +variable "disk_size_gb" { + description = "Capacity,in gigabytes,of the host’s root volume" + type = number + default = null +} + +variable "auto_scaling_disk_gb_enabled" { + description = "Indicating if disk auto-scaling is enabled" + type = bool + default = true +} + +variable "volume_type" { + description = "STANDARD or PROVISIONED for IOPS higher than the default instance IOPS" + type = string + default = "STANDARD" +} + +variable "provider_disk_iops" { + description = "The maximum IOPS the system can perform" + type = number + default = null +} + + +variable "vpc_peer" { + description = "An object that contains all VPC peering requests from the cluster to AWS VPC's" + type = map(any) + default = {} +} + +variable "encryption_at_rest_provider" { + description = "Possible values are AWS, GCP, AZURE or NONE" + type = string + default = "" +} + +locals { + cloud_provider = "AWS" +} + + + + + + + diff --git a/modules/aws-mongodbatlas-cluster/versions.tf b/modules/aws-mongodbatlas-cluster/versions.tf new file mode 100644 index 0000000..5e8b30a --- /dev/null +++ b/modules/aws-mongodbatlas-cluster/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + version = ">= 1.4.5" + } + } +} + + diff --git a/modules/aws-mongodbatlas-database-user/README.md b/modules/aws-mongodbatlas-database-user/README.md new file mode 100644 index 0000000..bf57088 --- /dev/null +++ b/modules/aws-mongodbatlas-database-user/README.md @@ -0,0 +1,85 @@ +# terraform-aws-mongodbatlas-database-user +Terraform module which creates a Mongodb Atlas Database User + + + +## Terraform versions + +Terraform versions >=0.13.1 are supported + +## Usage + +```hcl +module "atlas_database_user" { + source = "./terraform-aws-mongodbatlas-database-user" + + cluster_name = + project_id = + database_users = { + test-user-aws-iam = { + username = + auth_database_name = "$external" + aws_iam_type = "ROLE" + role_name = "readWriteAnyDatabase" + database_name = "admin" + }, + test-user-auto-generate-credentials = { + username = "test_auto_generate_password" + auth_database_name = "admin" + role_name = "readWriteAnyDatabase" + database_name = "admin" + }, + } +} + +``` +## Prerequisites +* [MongoDB Cloud account](https://www.mongodb.com/cloud) +* [MongoDB Atlas Organization](https://cloud.mongodb.com/v2#/preferences/organizations/create) +* [MongoDB Atlas API key](https://www.terraform.io/docs/providers/mongodbatlas/index.html) + +#### Manual tasks: + +* Configure the API key before applying the module +* Get your MongoDB Atlas Organization ID from the UI + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [mongodbatlas](#requirement\_mongodbatlas) | >= 1.4.5 | + +## Providers + +| Name | Version | +|------|---------| +| [mongodbatlas](#provider\_mongodbatlas) | >= 1.4.5 | +| [random](#provider\_random) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [mongodbatlas_database_user.this](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/database_user) | resource | +| [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_name](#input\_cluster\_name) | Mongoatlas cluster name | `any` | n/a | yes | +| [database\_users](#input\_database\_users) | n/a | `map(any)` | `{}` | no | +| [project\_id](#input\_project\_id) | Mongoatlas project ID | `any` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [database\_user\_credentials](#output\_database\_user\_credentials) | Map of database users and passwords | + \ No newline at end of file diff --git a/modules/aws-mongodbatlas-database-user/main.tf b/modules/aws-mongodbatlas-database-user/main.tf new file mode 100644 index 0000000..9836ebf --- /dev/null +++ b/modules/aws-mongodbatlas-database-user/main.tf @@ -0,0 +1,27 @@ +resource "random_password" "password" { + for_each = { for key, value in var.database_users : key => value if lookup(value, "aws_iam_type", null) == null } + length = 14 + special = false + upper = false +} + +resource "mongodbatlas_database_user" "this" { + for_each = var.database_users + username = lookup(each.value, "username") + password = lookup(each.value, "aws_iam_type", null) == null ? random_password.password[each.key].result : null + project_id = var.project_id + auth_database_name = lookup(each.value, "auth_database_name") + aws_iam_type = lookup(each.value, "aws_iam_type", null) + + roles { + role_name = lookup(each.value, "role_name") + database_name = lookup(each.value, "database_name") + } + + + scopes { + name = var.cluster_name + type = "CLUSTER" + } + +} diff --git a/modules/aws-mongodbatlas-database-user/outputs.tf b/modules/aws-mongodbatlas-database-user/outputs.tf new file mode 100644 index 0000000..0f37986 --- /dev/null +++ b/modules/aws-mongodbatlas-database-user/outputs.tf @@ -0,0 +1,7 @@ +output "database_user_credentials" { + description = "Map of database users and passwords" + value = merge( + { for key, value in var.database_users : lookup(value, "username") => lookup(value, "password") if lookup(value, "password", null) != null }, + { for key, value in var.database_users : lookup(value, "username") => random_password.password[key].result if(lookup(value, "password", null) == null && lookup(value, "aws_iam_type", null) == null) }) + sensitive = true +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-database-user/variables.tf b/modules/aws-mongodbatlas-database-user/variables.tf new file mode 100644 index 0000000..ba8acf3 --- /dev/null +++ b/modules/aws-mongodbatlas-database-user/variables.tf @@ -0,0 +1,12 @@ +variable "database_users" { + type = map(any) + default = {} +} + +variable "cluster_name" { + description = "Mongoatlas cluster name" +} + +variable "project_id" { + description = "Mongoatlas project ID" +} \ No newline at end of file diff --git a/modules/aws-mongodbatlas-database-user/versions.tf b/modules/aws-mongodbatlas-database-user/versions.tf new file mode 100644 index 0000000..5e8b30a --- /dev/null +++ b/modules/aws-mongodbatlas-database-user/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + version = ">= 1.4.5" + } + } +} + + diff --git a/modules/aws-mq/.gitignore b/modules/aws-mq/.gitignore new file mode 100644 index 0000000..fd71656 --- /dev/null +++ b/modules/aws-mq/.gitignore @@ -0,0 +1,12 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +**/.idea +**/*.iml + +**/.build-harness +**/build-harness diff --git a/modules/aws-mq/README.md b/modules/aws-mq/README.md new file mode 100644 index 0000000..6f98cf1 --- /dev/null +++ b/modules/aws-mq/README.md @@ -0,0 +1,133 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [aws](#requirement\_aws) | >= 3.0 | +| [random](#requirement\_random) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | +| [random](#provider\_random) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [security\_group](#module\_security\_group) | cloudposse/security-group/aws | 2.0.1 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_mq_broker.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/mq_broker) | resource | +| [aws_ssm_parameter.mq_application_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.mq_application_username](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.mq_master_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.mq_master_username](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [random_password.mq_admin_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [random_password.mq_application_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [random_pet.mq_admin_user](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [random_pet.mq_application_user](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_security\_group\_rules](#input\_additional\_security\_group\_rules) | A list of Security Group rule objects to add to the created security group, in addition to the ones
this module normally creates. (To suppress the module's rules, set `create_security_group` to false
and supply your own security group(s) via `associated_security_group_ids`.)
The keys and values of the objects are fully compatible with the `aws_security_group_rule` resource, except
for `security_group_id` which will be ignored, and the optional "key" which, if provided, must be unique and known at "plan" time.
For more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
and https://github.com/cloudposse/terraform-aws-security-group. | `list(any)` | `[]` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [allow\_all\_egress](#input\_allow\_all\_egress) | If `true`, the created security group will allow egress on all ports and protocols to all IP addresses.
If this is false and no egress rules are otherwise specified, then no egress will be allowed. | `bool` | `true` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | A list of IPv4 CIDRs to allow access to the security group created by this module.
The length of this list must be known at "plan" time. | `list(string)` | `[]` | no | +| [allowed\_ingress\_ports](#input\_allowed\_ingress\_ports) | List of TCP ports to allow access to in the created security group.
Default is to allow access to all ports. Set `create_security_group` to `false` to disable.
Note: List of ports must be known at "plan" time. | `list(number)` | `[]` | no | +| [allowed\_ipv6\_cidr\_blocks](#input\_allowed\_ipv6\_cidr\_blocks) | A list of IPv6 CIDRs to allow access to the security group created by this module.
The length of this list must be known at "plan" time. | `list(string)` | `[]` | no | +| [allowed\_ipv6\_prefix\_list\_ids](#input\_allowed\_ipv6\_prefix\_list\_ids) | A list of IPv6 Prefix Lists IDs to allow access to the security group created by this module.
The length of this list must be known at "plan" time. | `list(string)` | `[]` | no | +| [allowed\_security\_group\_ids](#input\_allowed\_security\_group\_ids) | A list of IDs of Security Groups to allow access to the security group created by this module.
The length of this list must be known at "plan" time. | `list(string)` | `[]` | no | +| [allowed\_security\_groups](#input\_allowed\_security\_groups) | DEPRECATED: Use `allowed_security_group_ids` instead.
A list of Security Group IDs to to be allowed to connect to the broker instance. | `list(string)` | `[]` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any cluster modifications are applied immediately, or during the next maintenance window | `bool` | `false` | no | +| [associated\_security\_group\_ids](#input\_associated\_security\_group\_ids) | A list of IDs of Security Groups to associate the created resource with, in addition to the created security group.
These security groups will not be modified and, if `create_security_group` is `false`, must have rules providing the desired access. | `list(string)` | `[]` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [audit\_log\_enabled](#input\_audit\_log\_enabled) | Enables audit logging. User management action made using JMX or the ActiveMQ Web Console is logged | `bool` | `true` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Enables automatic upgrades to new minor versions for brokers, as Apache releases the versions | `bool` | `false` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_security\_group](#input\_create\_security\_group) | Set `true` to create and configure a new security group. If false, `associated_security_group_ids` must be provided. | `bool` | `true` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [deployment\_mode](#input\_deployment\_mode) | The deployment mode of the broker. Supported: SINGLE\_INSTANCE and ACTIVE\_STANDBY\_MULTI\_AZ | `string` | `"ACTIVE_STANDBY_MULTI_AZ"` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [encryption\_enabled](#input\_encryption\_enabled) | Flag to enable/disable Amazon MQ encryption at rest | `bool` | `true` | no | +| [engine\_type](#input\_engine\_type) | Type of broker engine, `ActiveMQ` or `RabbitMQ` | `string` | `"ActiveMQ"` | no | +| [engine\_version](#input\_engine\_version) | The version of the broker engine. See https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/broker-engine.html for more details | `string` | `"5.15.14"` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [existing\_security\_groups](#input\_existing\_security\_groups) | DEPRECATED: Use `associated_security_group_ids` instead.
List of existing Security Group IDs to place the broker into. | `list(string)` | `[]` | no | +| [general\_log\_enabled](#input\_general\_log\_enabled) | Enables general logging via CloudWatch | `bool` | `true` | no | +| [host\_instance\_type](#input\_host\_instance\_type) | The broker's instance type. e.g. mq.t2.micro or mq.m4.large | `string` | `"mq.t3.micro"` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [kms\_mq\_key\_arn](#input\_kms\_mq\_key\_arn) | ARN of the AWS KMS key used for Amazon MQ encryption | `string` | `null` | no | +| [kms\_ssm\_key\_arn](#input\_kms\_ssm\_key\_arn) | ARN of the AWS KMS key used for SSM encryption | `string` | `"alias/aws/ssm"` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [maintenance\_day\_of\_week](#input\_maintenance\_day\_of\_week) | The maintenance day of the week. e.g. MONDAY, TUESDAY, or WEDNESDAY | `string` | `"SUNDAY"` | no | +| [maintenance\_time\_of\_day](#input\_maintenance\_time\_of\_day) | The maintenance time, in 24-hour format. e.g. 02:00 | `string` | `"03:00"` | no | +| [maintenance\_time\_zone](#input\_maintenance\_time\_zone) | The maintenance time zone, in either the Country/City format, or the UTC offset format. e.g. CET | `string` | `"UTC"` | no | +| [mq\_admin\_password](#input\_mq\_admin\_password) | Admin password | `list(string)` | `[]` | no | +| [mq\_admin\_password\_ssm\_parameter\_name](#input\_mq\_admin\_password\_ssm\_parameter\_name) | SSM parameter name for Admin password | `string` | `"mq_admin_password"` | no | +| [mq\_admin\_user](#input\_mq\_admin\_user) | Admin username | `list(string)` | `[]` | no | +| [mq\_admin\_user\_ssm\_parameter\_name](#input\_mq\_admin\_user\_ssm\_parameter\_name) | SSM parameter name for Admin username | `string` | `"mq_admin_username"` | no | +| [mq\_application\_password](#input\_mq\_application\_password) | Application password | `list(string)` | `[]` | no | +| [mq\_application\_password\_ssm\_parameter\_name](#input\_mq\_application\_password\_ssm\_parameter\_name) | SSM parameter name for Application password | `string` | `"mq_application_password"` | no | +| [mq\_application\_user](#input\_mq\_application\_user) | Application username | `list(string)` | `[]` | no | +| [mq\_application\_user\_ssm\_parameter\_name](#input\_mq\_application\_user\_ssm\_parameter\_name) | SSM parameter name for Application username | `string` | `"mq_application_username"` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [overwrite\_ssm\_parameter](#input\_overwrite\_ssm\_parameter) | Whether to overwrite an existing SSM parameter | `bool` | `true` | no | +| [preserve\_security\_group\_id](#input\_preserve\_security\_group\_id) | When `false` and `security_group_create_before_destroy` is `true`, changes to security group rules
cause a new security group to be created with the new rules, and the existing security group is then
replaced with the new one, eliminating any service interruption.
When `true` or when changing the value (from `false` to `true` or from `true` to `false`),
existing security group rules will be deleted before new ones are created, resulting in a service interruption,
but preserving the security group itself.
**NOTE:** Setting this to `true` does not guarantee the security group will never be replaced,
it only keeps changes to the security group rules from triggering a replacement.
See the [terraform-aws-security-group README](https://github.com/cloudposse/terraform-aws-security-group) for further discussion. | `bool` | `false` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Whether to enable connections from applications outside of the VPC that hosts the broker's subnets | `bool` | `false` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [security\_group\_create\_before\_destroy](#input\_security\_group\_create\_before\_destroy) | Set `true` to enable Terraform `create_before_destroy` behavior on the created security group.
We only recommend setting this `false` if you are importing an existing security group
that you do not want replaced and therefore need full control over its name.
Note that changing this value will always cause the security group to be replaced. | `bool` | `true` | no | +| [security\_group\_create\_timeout](#input\_security\_group\_create\_timeout) | How long to wait for the security group to be created. | `string` | `"10m"` | no | +| [security\_group\_delete\_timeout](#input\_security\_group\_delete\_timeout) | How long to retry on `DependencyViolation` errors during security group deletion from
lingering ENIs left by certain AWS services such as Elastic Load Balancing. | `string` | `"15m"` | no | +| [security\_group\_description](#input\_security\_group\_description) | The description to assign to the created Security Group.
Warning: Changing the description causes the security group to be replaced. | `string` | `"Managed by Terraform"` | no | +| [security\_group\_name](#input\_security\_group\_name) | The name to assign to the created security group. Must be unique within the VPC.
If not provided, will be derived from the `null-label.context` passed in.
If `create_before_destroy` is true, will be used as a name prefix. | `list(string)` | `[]` | no | +| [ssm\_parameter\_name\_format](#input\_ssm\_parameter\_name\_format) | SSM parameter name format | `string` | `"/%s/%s"` | no | +| [ssm\_path](#input\_ssm\_path) | The first parameter to substitute in `ssm_parameter_name_format` | `string` | `"mq"` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of VPC subnet IDs | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [use\_aws\_owned\_key](#input\_use\_aws\_owned\_key) | Boolean to enable an AWS owned Key Management Service (KMS) Customer Master Key (CMK) for Amazon MQ encryption that is not in your account | `bool` | `true` | no | +| [use\_existing\_security\_groups](#input\_use\_existing\_security\_groups) | DEPRECATED: Use `create_security_group` instead.
Historical description: Set to `true` to disable Security Group creation | `bool` | `null` | no | +| [vpc\_id](#input\_vpc\_id) | The ID of the VPC to create the broker in | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [admin\_username](#output\_admin\_username) | AmazonMQ admin username | +| [application\_username](#output\_application\_username) | AmazonMQ application username | +| [broker\_arn](#output\_broker\_arn) | AmazonMQ broker ARN | +| [broker\_id](#output\_broker\_id) | AmazonMQ broker ID | +| [primary\_amqp\_ssl\_endpoint](#output\_primary\_amqp\_ssl\_endpoint) | AmazonMQ primary AMQP+SSL endpoint | +| [primary\_console\_url](#output\_primary\_console\_url) | AmazonMQ active web console URL | +| [primary\_ip\_address](#output\_primary\_ip\_address) | AmazonMQ primary IP address | +| [primary\_mqtt\_ssl\_endpoint](#output\_primary\_mqtt\_ssl\_endpoint) | AmazonMQ primary MQTT+SSL endpoint | +| [primary\_ssl\_endpoint](#output\_primary\_ssl\_endpoint) | AmazonMQ primary SSL endpoint | +| [primary\_stomp\_ssl\_endpoint](#output\_primary\_stomp\_ssl\_endpoint) | AmazonMQ primary STOMP+SSL endpoint | +| [primary\_wss\_endpoint](#output\_primary\_wss\_endpoint) | AmazonMQ primary WSS endpoint | +| [secondary\_amqp\_ssl\_endpoint](#output\_secondary\_amqp\_ssl\_endpoint) | AmazonMQ secondary AMQP+SSL endpoint | +| [secondary\_console\_url](#output\_secondary\_console\_url) | AmazonMQ secondary web console URL | +| [secondary\_ip\_address](#output\_secondary\_ip\_address) | AmazonMQ secondary IP address | +| [secondary\_mqtt\_ssl\_endpoint](#output\_secondary\_mqtt\_ssl\_endpoint) | AmazonMQ secondary MQTT+SSL endpoint | +| [secondary\_ssl\_endpoint](#output\_secondary\_ssl\_endpoint) | AmazonMQ secondary SSL endpoint | +| [secondary\_stomp\_ssl\_endpoint](#output\_secondary\_stomp\_ssl\_endpoint) | AmazonMQ secondary STOMP+SSL endpoint | +| [secondary\_wss\_endpoint](#output\_secondary\_wss\_endpoint) | AmazonMQ secondary WSS endpoint | +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the created security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the created security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the created security group | + diff --git a/modules/aws-mq/main.tf b/modules/aws-mq/main.tf new file mode 100644 index 0000000..861be3c --- /dev/null +++ b/modules/aws-mq/main.tf @@ -0,0 +1,113 @@ +locals { + mq_admin_user_enabled = var.engine_type == "ActiveMQ" + + mq_admin_user_needed = local.mq_admin_user_enabled && length(var.mq_admin_user) == 0 + mq_admin_user = local.mq_admin_user_needed ? random_pet.mq_admin_user[0].id : try(var.mq_admin_user[0], "") + + mq_admin_password_needed = local.mq_admin_user_enabled && length(var.mq_admin_password) == 0 + mq_admin_password = local.mq_admin_password_needed ? random_password.mq_admin_password[0].result : try(var.mq_admin_password[0], "") + + mq_application_user_needed = length(var.mq_application_user) == 0 + mq_application_user = local.mq_application_user_needed ? random_pet.mq_application_user[0].id : try(var.mq_application_user[0], "") + + mq_application_password_needed = length(var.mq_application_password) == 0 + mq_application_password = local.mq_application_password_needed ? random_password.mq_application_password[0].result : try(var.mq_application_password[0], "") + + mq_logs = { logs = { "general_log_enabled" : var.general_log_enabled, "audit_log_enabled" : var.audit_log_enabled } } + + broker_security_groups = try([var.security_group_id], []) + subnet_ids = local.mq_admin_user_needed ? [data.aws_subnets.private_subnets_with_queue_tag.ids[0], data.aws_subnets.private_subnets_with_queue_tag.ids[1]] : data.aws_subnets.private_subnets_with_queue_tag.ids +} + +resource "random_pet" "mq_admin_user" { + count = local.mq_admin_user_needed ? 1 : 0 + length = 2 + separator = "-" +} + +resource "random_password" "mq_admin_password" { + count = local.mq_admin_password_needed ? 1 : 0 + length = 24 + special = false +} + +resource "random_pet" "mq_application_user" { + count = local.mq_application_user_needed ? 1 : 0 + length = 2 + separator = "-" +} + +resource "random_password" "mq_application_password" { + count = local.mq_application_password_needed ? 1 : 0 + length = 24 + special = false +} + +data "aws_subnets" "private_subnets_with_queue_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } +} + +resource "aws_mq_broker" "default" { + count = 1 + broker_name = var.broker_name + deployment_mode = var.deployment_mode + engine_type = var.engine_type + engine_version = var.engine_version + host_instance_type = var.host_instance_type + auto_minor_version_upgrade = var.auto_minor_version_upgrade + apply_immediately = var.apply_immediately + publicly_accessible = var.publicly_accessible + subnet_ids = local.subnet_ids + tags = var.tags + + security_groups = local.broker_security_groups + + dynamic "encryption_options" { + for_each = var.encryption_enabled ? ["true"] : [] + content { + kms_key_id = var.kms_mq_key_arn + use_aws_owned_key = var.use_aws_owned_key + } + } + + # NOTE: Omit logs block if both general and audit logs disabled: + # https://github.com/hashicorp/terraform-provider-aws/issues/18067 + dynamic "logs" { + for_each = { + for logs, type in local.mq_logs : logs => type + if type.general_log_enabled || type.audit_log_enabled + } + content { + general = logs.value["general_log_enabled"] + audit = logs.value["audit_log_enabled"] + } + } + + maintenance_window_start_time { + day_of_week = var.maintenance_day_of_week + time_of_day = var.maintenance_time_of_day + time_zone = var.maintenance_time_zone + } + + dynamic "user" { + for_each = local.mq_admin_user_enabled ? ["true"] : [] + content { + username = local.mq_admin_user + password = local.mq_admin_password + groups = ["admin"] + console_access = true + } + } + + user { + username = local.mq_application_user + password = local.mq_application_password + } +} diff --git a/modules/aws-mq/outputs.tf b/modules/aws-mq/outputs.tf new file mode 100644 index 0000000..960ead0 --- /dev/null +++ b/modules/aws-mq/outputs.tf @@ -0,0 +1,51 @@ +output "broker_id" { + value = join("", aws_mq_broker.default.*.id) + description = "AmazonMQ broker ID" +} + +output "broker_arn" { + value = join("", aws_mq_broker.default.*.arn) + description = "AmazonMQ broker ARN" +} + +output "broker_name" { + value = join("", aws_mq_broker.default.*.broker_name) + description = "AmazonMQ broker name" +} + +output "primary_console_url" { + value = try(aws_mq_broker.default[0].instances[0].console_url, "") + description = "AmazonMQ active web console URL" +} + +output "admin_username" { + value = local.mq_admin_user + description = "AmazonMQ admin username" +} + +output "admin_password" { + value = local.mq_admin_password + description = "AmazonMQ admin password" + sensitive = true +} + +output "application_username" { + value = local.mq_application_user + description = "AmazonMQ application username" +} + +output "application_password" { + value = local.mq_application_password + description = "AmazonMQ application user password" + sensitive = true +} + +output "endpoints" { + value = aws_mq_broker.default[0].instances.0.endpoints + description = "Endpoints of the MQ instances" +} + +output "endpoints_all" { + value = aws_mq_broker.default[0].instances[*].endpoints + description = "Endpoints of the all MQ instances" +} diff --git a/modules/aws-mq/variables.tf b/modules/aws-mq/variables.tf new file mode 100644 index 0000000..f2e9cc4 --- /dev/null +++ b/modules/aws-mq/variables.tf @@ -0,0 +1,202 @@ +variable "broker_name" { + type = string + description = "The name of the broker" + default = "mq-cluster" +} + +variable "tags" { + type = map(string) + description = "Tags of the broker" + default = {} +} + +variable "apply_immediately" { + type = bool + description = "Specifies whether any cluster modifications are applied immediately, or during the next maintenance window" + default = false +} + +variable "auto_minor_version_upgrade" { + type = bool + description = "Enables automatic upgrades to new minor versions for brokers, as Apache releases the versions" + default = false +} + +variable "deployment_mode" { + type = string + description = "The deployment mode of the broker. Supported: SINGLE_INSTANCE and ACTIVE_STANDBY_MULTI_AZ" + default = "ACTIVE_STANDBY_MULTI_AZ" +} + +variable "engine_type" { + type = string + description = "Type of broker engine, `ActiveMQ` or `RabbitMQ`" + default = "ActiveMQ" +} + +variable "engine_version" { + type = string + description = "The version of the broker engine. See https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/broker-engine.html for more details" +} + +variable "host_instance_type" { + type = string + description = "The broker's instance type. e.g. mq.t2.micro or mq.m4.large" + default = "mq.m5.large" +} + +variable "publicly_accessible" { + type = bool + description = "Whether to enable connections from applications outside of the VPC that hosts the broker's subnets" + default = false +} + +variable "general_log_enabled" { + type = bool + description = "Enables general logging via CloudWatch" + default = true +} + +variable "audit_log_enabled" { + type = bool + description = "Enables audit logging. User management action made using JMX or the ActiveMQ Web Console is logged" + default = true +} + +variable "maintenance_day_of_week" { + type = string + description = "The maintenance day of the week. e.g. MONDAY, TUESDAY, or WEDNESDAY" + default = "SUNDAY" +} + +variable "maintenance_time_of_day" { + type = string + description = "The maintenance time, in 24-hour format. e.g. 02:00" + default = "03:00" +} + +variable "maintenance_time_zone" { + type = string + description = "The maintenance time zone, in either the Country/City format, or the UTC offset format. e.g. CET" + default = "UTC" +} + +variable "mq_admin_user" { + type = list(string) + description = "Admin username" + default = [] +} + +variable "mq_admin_password" { + type = list(string) + description = "Admin password" + default = [] + sensitive = true +} + +variable "mq_application_user" { + type = list(string) + description = "Application username" + default = [] +} + +variable "mq_application_password" { + type = list(string) + description = "Application password" + default = [] + sensitive = true +} + +variable "vpc_id" { + type = string + description = "The ID of the VPC to create the broker in" +} + +variable "overwrite_ssm_parameter" { + type = bool + description = "Whether to overwrite an existing SSM parameter" + default = true +} + +variable "ssm_parameter_name_format" { + type = string + description = "SSM parameter name format" + default = "/%s/%s" +} + +variable "ssm_path" { + type = string + description = "The first parameter to substitute in `ssm_parameter_name_format`" + default = "mq" +} + +variable "mq_admin_user_ssm_parameter_name" { + type = string + description = "SSM parameter name for Admin username" + default = "mq_admin_username" +} + +variable "mq_admin_password_ssm_parameter_name" { + type = string + description = "SSM parameter name for Admin password" + default = "mq_admin_password" +} + +variable "mq_application_user_ssm_parameter_name" { + type = string + description = "SSM parameter name for Application username" + default = "mq_application_username" +} + +variable "mq_application_password_ssm_parameter_name" { + type = string + description = "SSM parameter name for Application password" + default = "mq_application_password" +} + + + +variable "kms_ssm_key_arn" { + type = string + description = "ARN of the AWS KMS key used for SSM encryption" + default = "alias/aws/ssm" +} + +variable "encryption_enabled" { + type = bool + description = "Flag to enable/disable Amazon MQ encryption at rest" + default = true +} + +variable "kms_mq_key_arn" { + type = string + description = "ARN of the AWS KMS key used for Amazon MQ encryption" + default = null +} + +variable "use_aws_owned_key" { + type = bool + description = "Boolean to enable an AWS owned Key Management Service (KMS) Customer Master Key (CMK) for Amazon MQ encryption that is not in your account" + default = true +} + +variable "allowed_ingress_ports" { + type = list(number) + description = <<-EOT + List of TCP ports to allow access to in the created security group. + Default is to allow access to all ports. Set `create_security_group` to `false` to disable. + Note: List of ports must be known at "plan" time. + EOT + default = [] +} + +variable "security_group_id" { + type = string + description = "The id of the securiry group to use for the cluster" +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} \ No newline at end of file diff --git a/modules/aws-mq/versions.tf b/modules/aws-mq/versions.tf new file mode 100644 index 0000000..b1598cd --- /dev/null +++ b/modules/aws-mq/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + } +} diff --git a/modules/aws-msk-apache-kafka-cluster-master/.gitignore b/modules/aws-msk-apache-kafka-cluster-master/.gitignore new file mode 100644 index 0000000..f123b94 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/.gitignore @@ -0,0 +1,20 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* +.terraform +.terraform.tfstate.lock.info +.terraform.lock.hcl + +**/.idea +**/*.iml + +# Cloud Posse Build Harness https://github.com/cloudposse/build-harness +**/.build-harness +**/build-harness + +# Crash log files +crash.log +test.log diff --git a/modules/aws-msk-apache-kafka-cluster-master/README.md b/modules/aws-msk-apache-kafka-cluster-master/README.md new file mode 100644 index 0000000..707d0b4 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/README.md @@ -0,0 +1,217 @@ + +## AWS MSK + +Terraform module to provision [Amazon Managed Streaming](https://aws.amazon.com/msk/) for [Apache Kafka](https://aws.amazon.com/msk/what-is-kafka/) + + +## Terragrunt Usage in terragrunt.hcl file + +```hcl +include "root" { + path = find_in_parent_folders() +} + +include "envcommon" { + path = "${dirname(find_in_parent_folders())}/_envcommon/kafka/kafka.hcl" +} + +locals { +# Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl")) + region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.inputs.env + region = local.region_vars.inputs.region + +} + +dependency "vpc" { + config_path = "../vpc" + mock_outputs = { + vpc_id = "known after apply" + private_subnets = ["known after apply"] + subnet_ids = ["known after apply"] + default_security_group_id = "known after apply" + } +} + +dependency "route53" { + config_path = "../route53/route53-zones" + mock_outputs = { + route53_zone_zone_id = "known after apply" + } +} + +inputs = { + + namespace = "" + stage = "" + name = "kafka-cluster" + vpc_id = dependency.vpc.outputs.vpc_id + #zone_id = dependency.route53.outputs.route53_zone_zone_id + subnet_ids = dependency.vpc.outputs.private_subnets + associated_security_group_ids = dependency.security-groups.outputs.security_group_id + kafka_version = "2.6.0" + number_of_broker_nodes = 1 + broker_instance_type = "kafka.t3.small" + autoscaling_enabled = false + broker_volume_size = "15" + client_broker = "TLS_PLAINTEXT" + encryption_in_cluster = false + + allowed_cidr_blocks = [ + "185.139.196.1/32", + "12.172.166.246/32", + "173.31.0.0/16", + "54.154.91.195/32", + "35.197.232.204/32", + "10.222.0.0/16" + ] +} +``` + +## Terraform Usage + +Here's how to invoke this example module in your projects + +```hcl +module "kafka" { + source = "inf.modules/terraform-aws-msk-apache-kafka-cluster-master" + + namespace = "eg" + stage = "prod" + name = "app" + vpc_id = "vpc-XXXXXXXX" + zone_id = "Z14EN2YD427LRQ" + subnet_ids = ["subnet-XXXXXXXXX", "subnet-YYYYYYYY"] + kafka_version = "2.4.1" + number_of_broker_nodes = 2 # this has to be a multiple of the # of subnet_ids + broker_instance_type = "kafka.m5.large" + + # security groups to put on the cluster itself + associated_security_group_ids = ["sg-XXXXXXXXX", "sg-YYYYYYYY"] + # security groups to give access to the cluster + allowed_security_group_ids = ["sg-XXXXXXXXX", "sg-YYYYYYYY"] +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14.0 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [broker\_security\_group](#module\_broker\_security\_group) | cloudposse/security-group/aws | 1.0.1 | +| [hostname](#module\_hostname) | cloudposse/route53-cluster-hostname/aws | 0.12.2 | +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_target.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_msk_cluster.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster) | resource | +| [aws_msk_configuration.config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_configuration) | resource | +| [aws_msk_scram_secret_association.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_scram_secret_association) | resource | +| [aws_msk_broker_nodes.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/msk_broker_nodes) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_security\_group\_rules](#input\_additional\_security\_group\_rules) | A list of Security Group rule objects to add to the created security group, in addition to the ones
this module normally creates. (To suppress the module's rules, set `create_security_group` to false
and supply your own security group via `associated_security_group_ids`.)
The keys and values of the objects are fully compatible with the `aws_security_group_rule` resource, except
for `security_group_id` which will be ignored, and the optional "key" which, if provided, must be unique and known at "plan" time.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . | `list(any)` | `[]` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of CIDR blocks to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [allowed\_security\_group\_ids](#input\_allowed\_security\_group\_ids) | A list of IDs of Security Groups to allow access to the security group created by this module. | `list(string)` | `[]` | no | +| [associated\_security\_group\_ids](#input\_associated\_security\_group\_ids) | A list of IDs of Security Groups to associate the created resource with, in addition to the created security group.
These security groups will not be modified and, if `create_security_group` is `false`, must have rules providing the desired access. | `list(string)` | `[]` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [autoscaling\_enabled](#input\_autoscaling\_enabled) | To automatically expand your cluster's storage in response to increased usage, you can enable this. [More info](https://docs.aws.amazon.com/msk/latest/developerguide/msk-autoexpand.html) | `bool` | `true` | no | +| [broker\_instance\_type](#input\_broker\_instance\_type) | The instance type to use for the Kafka brokers | `string` | n/a | yes | +| [broker\_per\_zone](#input\_broker\_per\_zone) | Number of Kafka brokers per zone. | `number` | `1` | no | +| [broker\_volume\_size](#input\_broker\_volume\_size) | The size in GiB of the EBS volume for the data drive on each broker node | `number` | `1000` | no | +| [certificate\_authority\_arns](#input\_certificate\_authority\_arns) | List of ACM Certificate Authority Amazon Resource Names (ARNs) to be used for TLS client authentication | `list(string)` | `[]` | no | +| [client\_allow\_unauthenticated](#input\_client\_allow\_unauthenticated) | Enables unauthenticated access. | `bool` | `false` | no | +| [client\_broker](#input\_client\_broker) | Encryption setting for data in transit between clients and brokers. Valid values: `TLS`, `TLS_PLAINTEXT`, and `PLAINTEXT` | `string` | `"TLS"` | no | +| [client\_sasl\_iam\_enabled](#input\_client\_sasl\_iam\_enabled) | Enables client authentication via IAM policies (cannot be set to `true` at the same time as `client_sasl_*_enabled`). | `bool` | `false` | no | +| [client\_sasl\_scram\_enabled](#input\_client\_sasl\_scram\_enabled) | Enables SCRAM client authentication via AWS Secrets Manager (cannot be set to `true` at the same time as `client_tls_auth_enabled`). | `bool` | `false` | no | +| [client\_sasl\_scram\_secret\_association\_arns](#input\_client\_sasl\_scram\_secret\_association\_arns) | List of AWS Secrets Manager secret ARNs for scram authentication (cannot be set to `true` at the same time as `client_tls_auth_enabled`). | `list(string)` | `[]` | no | +| [client\_tls\_auth\_enabled](#input\_client\_tls\_auth\_enabled) | Set `true` to enable the Client TLS Authentication | `bool` | `false` | no | +| [cloudwatch\_logs\_enabled](#input\_cloudwatch\_logs\_enabled) | Indicates whether you want to enable or disable streaming broker logs to Cloudwatch Logs | `bool` | `false` | no | +| [cloudwatch\_logs\_log\_group](#input\_cloudwatch\_logs\_log\_group) | Name of the Cloudwatch Log Group to deliver logs to | `string` | `null` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create\_security\_group](#input\_create\_security\_group) | Set `true` to create and configure a new security group. If false, `associated_security_group_ids` must be provided. | `bool` | `true` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [encryption\_at\_rest\_kms\_key\_arn](#input\_encryption\_at\_rest\_kms\_key\_arn) | You may specify a KMS key short ID or ARN (it will always output an ARN) to use for encrypting your data at rest | `string` | `""` | no | +| [encryption\_in\_cluster](#input\_encryption\_in\_cluster) | Whether data communication among broker nodes is encrypted | `bool` | `true` | no | +| [enhanced\_monitoring](#input\_enhanced\_monitoring) | Specify the desired enhanced MSK CloudWatch monitoring level. Valid values: `DEFAULT`, `PER_BROKER`, and `PER_TOPIC_PER_BROKER` | `string` | `"DEFAULT"` | no | +| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [firehose\_delivery\_stream](#input\_firehose\_delivery\_stream) | Name of the Kinesis Data Firehose delivery stream to deliver logs to | `string` | `""` | no | +| [firehose\_logs\_enabled](#input\_firehose\_logs\_enabled) | Indicates whether you want to enable or disable streaming broker logs to Kinesis Data Firehose | `bool` | `false` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [jmx\_exporter\_enabled](#input\_jmx\_exporter\_enabled) | Set `true` to enable the JMX Exporter | `bool` | `false` | no | +| [kafka\_version](#input\_kafka\_version) | The desired Kafka software version | `string` | n/a | yes | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [node\_exporter\_enabled](#input\_node\_exporter\_enabled) | Set `true` to enable the Node Exporter | `bool` | `false` | no | +| [properties](#input\_properties) | Contents of the server.properties file. Supported properties are documented in the [MSK Developer Guide](https://docs.aws.amazon.com/msk/latest/developerguide/msk-configuration-properties.html) | `map(string)` | `{}` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [s3\_logs\_bucket](#input\_s3\_logs\_bucket) | Name of the S3 bucket to deliver logs to | `string` | `""` | no | +| [s3\_logs\_enabled](#input\_s3\_logs\_enabled) | Indicates whether you want to enable or disable streaming broker logs to S3 | `bool` | `false` | no | +| [s3\_logs\_prefix](#input\_s3\_logs\_prefix) | Prefix to append to the S3 folder name logs are delivered to | `string` | `""` | no | +| [security\_group\_create\_before\_destroy](#input\_security\_group\_create\_before\_destroy) | Set `true` to enable Terraform `create_before_destroy` behavior on the created security group.
We recommend setting this `true` on new security groups, but default it to `false` because `true`
will cause existing security groups to be replaced, possibly requiring the resource to be deleted and recreated.
Note that changing this value will always cause the security group to be replaced. | `bool` | `false` | no | +| [security\_group\_create\_timeout](#input\_security\_group\_create\_timeout) | How long to wait for the security group to be created. | `string` | `"10m"` | no | +| [security\_group\_delete\_timeout](#input\_security\_group\_delete\_timeout) | How long to retry on `DependencyViolation` errors during security group deletion from
lingering ENIs left by certain AWS services such as Elastic Load Balancing. | `string` | `"15m"` | no | +| [security\_group\_description](#input\_security\_group\_description) | The description to assign to the created Security Group.
Warning: Changing the description causes the security group to be replaced. | `string` | `"MSK broker access"` | no | +| [security\_group\_name](#input\_security\_group\_name) | The name to assign to the created security group. Must be unique within the VPC.
If not provided, will be derived from the `null-label.context` passed in.
If `create_before_destroy` is true, will be used as a name prefix. | `list(string)` | `[]` | no | +| [security\_group\_rule\_description](#input\_security\_group\_rule\_description) | The description to place on each security group rule. The %s will be replaced with the protocol name. | `string` | `"Allow inbound %s traffic"` | no | +| [security\_groups](#input\_security\_groups) | DEPRECATED: Use `allowed_security_group_ids` instead.
List of security group IDs to be allowed to connect to the cluster | `list(string)` | `[]` | no | +| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [storage\_autoscaling\_disable\_scale\_in](#input\_storage\_autoscaling\_disable\_scale\_in) | If the value is true, scale in is disabled and the target tracking policy won't remove capacity from the scalable resource. | `bool` | `false` | no | +| [storage\_autoscaling\_max\_capacity](#input\_storage\_autoscaling\_max\_capacity) | Maximum size the autoscaling policy can scale storage. Defaults to `broker_volume_size` | `number` | `null` | no | +| [storage\_autoscaling\_target\_value](#input\_storage\_autoscaling\_target\_value) | Percentage of storage used to trigger autoscaled storage increase | `number` | `60` | no | +| [subnet\_ids](#input\_subnet\_ids) | Subnet IDs for Client Broker | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| [vpc\_id](#input\_vpc\_id) | VPC ID where subnets will be created (e.g. `vpc-aceb2723`) | `string` | n/a | yes | +| [zone\_id](#input\_zone\_id) | Route53 DNS Zone ID for MSK broker hostnames | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [all\_brokers](#output\_all\_brokers) | A list of all brokers | +| [bootstrap\_brokers](#output\_bootstrap\_brokers) | A comma separated list of one or more hostname:port pairs of kafka brokers suitable to boostrap connectivity to the kafka cluster | +| [bootstrap\_brokers\_iam](#output\_bootstrap\_brokers\_iam) | A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity using SASL/IAM to the kafka cluster. | +| [bootstrap\_brokers\_scram](#output\_bootstrap\_brokers\_scram) | A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity using SASL/SCRAM to the kafka cluster. | +| [bootstrap\_brokers\_tls](#output\_bootstrap\_brokers\_tls) | A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity to the kafka cluster | +| [cluster\_arn](#output\_cluster\_arn) | Amazon Resource Name (ARN) of the MSK cluster | +| [cluster\_name](#output\_cluster\_name) | MSK Cluster name | +| [config\_arn](#output\_config\_arn) | Amazon Resource Name (ARN) of the configuration | +| [current\_version](#output\_current\_version) | Current version of the MSK Cluster used for updates | +| [hostname](#output\_hostname) | Comma separated list of one or more MSK Cluster Broker DNS hostname | +| [latest\_revision](#output\_latest\_revision) | Latest revision of the configuration | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group rule | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group rule | +| [zookeeper\_connect\_string](#output\_zookeeper\_connect\_string) | A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster | + \ No newline at end of file diff --git a/modules/aws-msk-apache-kafka-cluster-master/main.tf b/modules/aws-msk-apache-kafka-cluster-master/main.tf new file mode 100644 index 0000000..89b2877 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/main.tf @@ -0,0 +1,277 @@ +locals { + enabled = var.enabled + + brokers = local.enabled ? flatten(data.aws_msk_broker_nodes.default[0].node_info_list.*.endpoints) : [] + # If var.storage_autoscaling_max_capacity is not set, don't autoscale past current size + broker_volume_size_max = coalesce(var.storage_autoscaling_max_capacity, var.broker_volume_size) + + # var.client_broker types + plaintext = "PLAINTEXT" + tls_plaintext = "TLS_PLAINTEXT" + tls = "TLS" + + # The following ports are not configurable. See: https://docs.aws.amazon.com/msk/latest/developerguide/client-access.html#port-info + protocols = { + plaintext = { + name = "plaintext" + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#bootstrap_brokers + enabled = contains([local.plaintext, local.tls_plaintext], var.client_broker) + port = 9092 + } + tls = { + name = "TLS" + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#bootstrap_brokers_tls + enabled = contains([local.tls_plaintext, local.tls], var.client_broker) + port = 9094 + } + sasl_scram = { + name = "SASL/SCRAM" + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#bootstrap_brokers_sasl_scram + enabled = var.client_sasl_scram_enabled && contains([local.tls_plaintext, local.tls], var.client_broker) + port = 9096 + } + sasl_iam = { + name = "SASL/IAM" + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#bootstrap_brokers_sasl_iam + enabled = var.client_sasl_iam_enabled && contains([local.tls_plaintext, local.tls], var.client_broker) + port = 9098 + } + # The following two protocols are always enabled. + # See: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#zookeeper_connect_string + # and https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#zookeeper_connect_string_tls + zookeeper_plaintext = { + name = "Zookeeper plaintext" + enabled = true + port = 2181 + } + zookeeper_tls = { + name = "Zookeeper TLS" + enabled = true + port = 2182 + } + # The following two protocols are enabled on demand of user + # See: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#jmx_exporter + # and https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/msk_cluster#node_exporter + # and https://docs.aws.amazon.com/msk/latest/developerguide/open-monitoring.html#set-up-prometheus-host + jmx_exporter = { + name = "JMX Exporter" + enabled = var.jmx_exporter_enabled + port = 11001 + } + node_exporter = { + name = "Node Exporter" + enabled = var.node_exporter_enabled + port = 11002 + } + + } + + credential = var.client_sasl_scram_enabled ? jsonencode({ + username = "msk" + password = random_password.password[0].result + }) : null +} + +data "aws_msk_broker_nodes" "default" { + count = local.enabled ? 1 : 0 + + cluster_arn = join("", aws_msk_cluster.default.*.arn) +} + +data "aws_subnets" "private_subnets_with_queue_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } +} + +resource "random_password" "password" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + length = 10 + special = false +} + + +resource "aws_secretsmanager_secret" "sm" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + name = var.secret_name + description = "MSK sasl scram auth secret" + recovery_window_in_days = 30 + kms_key_id = var.encryption_at_rest_kms_key_arn + depends_on = [random_password.password[0]] +} + +resource "aws_secretsmanager_secret_version" "sm-sv" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + secret_id = aws_secretsmanager_secret.sm[0].arn + secret_string = local.credential +} + +data "aws_iam_policy_document" "sm-policy" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + statement { + sid = "AWSKafkaResourcePolicy" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["kafka.amazonaws.com"] + } + + actions = ["secretsmanager:getSecretValue"] + resources = [aws_secretsmanager_secret.sm[0].arn] + } +} + +resource "aws_secretsmanager_secret_policy" "policy" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + secret_arn = aws_secretsmanager_secret.sm[0].arn + policy = data.aws_iam_policy_document.sm-policy[0].json +} + +resource "aws_msk_configuration" "config" { + count = local.enabled ? 1 : 0 + kafka_versions = [var.kafka_version] + name = var.name + description = "Manages an Amazon Managed Streaming for Kafka configuration" + + server_properties = join("\n", [for k in keys(var.properties) : format("%s = %s", k, var.properties[k])]) +} + +resource "aws_msk_cluster" "default" { + #bridgecrew:skip=BC_AWS_LOGGING_18:Skipping `Amazon MSK cluster logging is not enabled` check since it can be enabled with cloudwatch_logs_enabled = true + #bridgecrew:skip=BC_AWS_LOGGING_18:Skipping `Amazon MSK cluster logging is not enabled` check since it can be enabled with cloudwatch_logs_enabled = true + #bridgecrew:skip=BC_AWS_GENERAL_32:Skipping `MSK cluster encryption at rest and in transit is not enabled` check since it can be enabled with encryption_in_cluster = true + count = local.enabled ? 1 : 0 + cluster_name = var.name + kafka_version = var.kafka_version + number_of_broker_nodes = var.broker_per_zone * length(length(try(data.aws_subnets.private_subnets_with_queue_tag.ids, null)) == 0 ? var.subnet_ids : try(data.aws_subnets.private_subnets_with_queue_tag.ids, null)) + enhanced_monitoring = var.enhanced_monitoring + + broker_node_group_info { + instance_type = var.broker_instance_type + #ebs_volume_size = var.broker_volume_size + client_subnets = length(data.aws_subnets.private_subnets_with_queue_tag.ids) == 0 ? var.subnet_ids : try(data.aws_subnets.private_subnets_with_queue_tag.ids, null) + security_groups = [var.associated_security_group_ids] + storage_info { + ebs_storage_info { + volume_size = var.broker_volume_size + } + } + connectivity_info { + public_access { + type = var.public_access_enabled ? "SERVICE_PROVIDED_EIPS" : "DISABLED" + } + } + } + configuration_info { + arn = aws_msk_configuration.config[0].arn + revision = aws_msk_configuration.config[0].latest_revision + } + + encryption_info { + encryption_in_transit { + client_broker = var.client_broker + in_cluster = var.encryption_in_cluster + } + encryption_at_rest_kms_key_arn = var.encryption_at_rest_kms_key_arn + } + + dynamic "client_authentication" { + for_each = var.client_tls_auth_enabled || var.client_sasl_scram_enabled || var.client_sasl_iam_enabled ? [1] : [] + content { + dynamic "tls" { + for_each = var.client_tls_auth_enabled ? [1] : [] + content { + certificate_authority_arns = var.certificate_authority_arns + } + } + dynamic "sasl" { + for_each = var.client_sasl_scram_enabled || var.client_sasl_iam_enabled ? [1] : [] + content { + scram = var.client_sasl_scram_enabled + iam = var.client_sasl_iam_enabled + } + } + unauthenticated = var.client_allow_unauthenticated + } + } + + open_monitoring { + prometheus { + jmx_exporter { + enabled_in_broker = var.jmx_exporter_enabled + } + node_exporter { + enabled_in_broker = var.node_exporter_enabled + } + } + } + + logging_info { + broker_logs { + cloudwatch_logs { + enabled = var.cloudwatch_logs_enabled + log_group = var.cloudwatch_logs_log_group + } + firehose { + enabled = var.firehose_logs_enabled + delivery_stream = var.firehose_delivery_stream + } + s3 { + enabled = var.s3_logs_enabled + bucket = var.s3_logs_bucket + prefix = var.s3_logs_prefix + } + } + } + + lifecycle { + ignore_changes = [ + # Ignore changes to ebs_volume_size in favor of autoscaling policy + broker_node_group_info[0].storage_info[0].ebs_storage_info[0].volume_size, + ] + } + + tags = var.tags +} + +resource "aws_msk_scram_secret_association" "default" { + count = local.enabled && var.client_sasl_scram_enabled ? 1 : 0 + + cluster_arn = aws_msk_cluster.default[0].arn + secret_arn_list = [aws_secretsmanager_secret.sm[0].arn] +} + +resource "aws_appautoscaling_target" "default" { + count = local.enabled && var.autoscaling_enabled ? 1 : 0 + + max_capacity = local.broker_volume_size_max + min_capacity = 1 + resource_id = aws_msk_cluster.default[0].arn + scalable_dimension = "kafka:broker-storage:VolumeSize" + service_namespace = "kafka" +} + +resource "aws_appautoscaling_policy" "default" { + count = local.enabled && var.autoscaling_enabled ? 1 : 0 + + name = "${aws_msk_cluster.default[0].cluster_name}-broker-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_msk_cluster.default[0].arn + scalable_dimension = join("", aws_appautoscaling_target.default.*.scalable_dimension) + service_namespace = join("", aws_appautoscaling_target.default.*.service_namespace) + + target_tracking_scaling_policy_configuration { + disable_scale_in = var.storage_autoscaling_disable_scale_in + predefined_metric_specification { + predefined_metric_type = "KafkaBrokerStorageUtilization" + } + + target_value = var.storage_autoscaling_target_value + } +} diff --git a/modules/aws-msk-apache-kafka-cluster-master/outputs.tf b/modules/aws-msk-apache-kafka-cluster-master/outputs.tf new file mode 100644 index 0000000..55342a7 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/outputs.tf @@ -0,0 +1,59 @@ +output "cluster_arn" { + description = "Amazon Resource Name (ARN) of the MSK cluster" + value = join("", aws_msk_cluster.default.*.arn) +} + +output "bootstrap_brokers" { + description = "A comma separated list of one or more hostname:port pairs of kafka brokers suitable to boostrap connectivity to the kafka cluster" + value = join(",", aws_msk_cluster.default.*.bootstrap_brokers) +} + +output "bootstrap_brokers_tls" { + description = "A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity to the kafka cluster" + value = join(",", aws_msk_cluster.default.*.bootstrap_brokers_tls) +} + +output "bootstrap_brokers_scram" { + description = "A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity using SASL/SCRAM to the kafka cluster." + value = join(",", aws_msk_cluster.default.*.bootstrap_brokers_sasl_scram) +} + +output "bootstrap_brokers_iam" { + description = "A comma separated list of one or more DNS names (or IPs) and TLS port pairs kafka brokers suitable to boostrap connectivity using SASL/IAM to the kafka cluster." + value = join(",", aws_msk_cluster.default.*.bootstrap_brokers_sasl_iam) +} + +output "all_brokers" { + description = "A list of all brokers" + value = local.brokers +} + +output "current_version" { + description = "Current version of the MSK Cluster used for updates" + value = join("", aws_msk_cluster.default.*.current_version) +} + +output "zookeeper_connect_string" { + description = "A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster" + value = join(",", aws_msk_cluster.default.*.zookeeper_connect_string) +} + +output "config_arn" { + description = "Amazon Resource Name (ARN) of the configuration" + value = join("", aws_msk_configuration.config.*.arn) +} + +output "latest_revision" { + description = "Latest revision of the configuration" + value = join("", aws_msk_configuration.config.*.latest_revision) +} + +output "cluster_name" { + description = "MSK Cluster name" + value = join("", aws_msk_cluster.default.*.cluster_name) +} + +output "bootstrap_brokers_public_sasl_scram" { + value = one(aws_msk_cluster.default[*].bootstrap_brokers_public_sasl_scram) + description = "Comma separated list of one or more DNS names (or IP addresses) and SASL SCRAM port pairs for public access to the Kafka cluster using SASL/SCRAM" +} \ No newline at end of file diff --git a/modules/aws-msk-apache-kafka-cluster-master/variables.tf b/modules/aws-msk-apache-kafka-cluster-master/variables.tf new file mode 100644 index 0000000..3b3d892 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/variables.tf @@ -0,0 +1,235 @@ +variable "enabled" { + type = bool + default = true + description = "Set to false to prevent the module from creating any resources" +} + +variable "name" { + type = string + default = null + description = "Name of the MSK cluster" +} + +variable "kafka_version" { + type = string + description = "The desired Kafka software version" +} + +variable "broker_instance_type" { + type = string + description = "The instance type to use for the Kafka brokers" +} + +variable "broker_per_zone" { + type = number + default = 1 + description = "Number of Kafka brokers per zone." + validation { + condition = var.broker_per_zone > 0 + error_message = "The broker_per_zone value must be at atleast 1." + } +} + +variable "broker_volume_size" { + type = number + default = 1000 + description = "The size in GiB of the EBS volume for the data drive on each broker node" +} + +variable "vpc_id" { + type = string + description = "VPC ID where subnets will be created (e.g. `vpc-aceb2723`)" +} + +variable "subnet_ids" { + type = list(string) + description = "Subnet IDs for Client Broker" + validation { + condition = length(var.subnet_ids) > 0 + error_message = "The subnet_ids list must have at atleast 1 value." + } +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +# Intentionally not deprecated via security_group_inputs.tf since it cannot effectively be replaced via var.additional_security_group_rules. +# This is because the logic to create these rules exists within this module, and should not be passed in by the consumer +# of this module. +variable "allowed_cidr_blocks" { + type = list(string) + default = [] + description = "List of CIDR blocks to be allowed to connect to the cluster" +} + +variable "associated_security_group_ids" { + description = "associated id of existing security group whose rules we will manage" + type = string + default = null +} + +variable "client_broker" { + type = string + default = "TLS" + description = "Encryption setting for data in transit between clients and brokers. Valid values: `TLS`, `TLS_PLAINTEXT`, and `PLAINTEXT`" +} + +variable "encryption_in_cluster" { + type = bool + default = true + description = "Whether data communication among broker nodes is encrypted" +} + +variable "encryption_at_rest_kms_key_arn" { + type = string + default = "" + description = "You may specify a KMS key short ID or ARN (it will always output an ARN) to use for encrypting your data at rest" +} + +variable "enhanced_monitoring" { + type = string + default = "DEFAULT" + description = "Specify the desired enhanced MSK CloudWatch monitoring level. Valid values: `DEFAULT`, `PER_BROKER`, and `PER_TOPIC_PER_BROKER`" +} + +variable "certificate_authority_arns" { + type = list(string) + default = [] + description = "List of ACM Certificate Authority Amazon Resource Names (ARNs) to be used for TLS client authentication" +} + +variable "client_allow_unauthenticated" { + type = bool + default = false + description = "Enables unauthenticated access." +} + +variable "client_sasl_scram_enabled" { + type = bool + default = false + description = "Enables SCRAM client authentication via AWS Secrets Manager (cannot be set to `true` at the same time as `client_tls_auth_enabled`)." +} + +variable "client_sasl_scram_secret_association_arns" { + type = list(string) + default = [] + description = "List of AWS Secrets Manager secret ARNs for scram authentication (cannot be set to `true` at the same time as `client_tls_auth_enabled`)." +} + +variable "client_sasl_iam_enabled" { + type = bool + default = false + description = "Enables client authentication via IAM policies (cannot be set to `true` at the same time as `client_sasl_*_enabled`)." +} + +variable "client_tls_auth_enabled" { + type = bool + default = false + description = "Set `true` to enable the Client TLS Authentication" +} + +variable "jmx_exporter_enabled" { + type = bool + default = false + description = "Set `true` to enable the JMX Exporter" +} + +variable "node_exporter_enabled" { + type = bool + default = false + description = "Set `true` to enable the Node Exporter" +} + +variable "cloudwatch_logs_enabled" { + type = bool + default = false + description = "Indicates whether you want to enable or disable streaming broker logs to Cloudwatch Logs" +} + +variable "cloudwatch_logs_log_group" { + type = string + default = null + description = "Name of the Cloudwatch Log Group to deliver logs to" +} + +variable "firehose_logs_enabled" { + type = bool + default = false + description = "Indicates whether you want to enable or disable streaming broker logs to Kinesis Data Firehose" +} + +variable "firehose_delivery_stream" { + type = string + default = "" + description = "Name of the Kinesis Data Firehose delivery stream to deliver logs to" +} + +variable "s3_logs_enabled" { + type = bool + default = false + description = " Indicates whether you want to enable or disable streaming broker logs to S3" +} + +variable "s3_logs_bucket" { + type = string + default = "" + description = "Name of the S3 bucket to deliver logs to" +} + +variable "s3_logs_prefix" { + type = string + default = "" + description = "Prefix to append to the S3 folder name logs are delivered to" +} + +variable "properties" { + type = map(string) + default = {} + description = "Contents of the server.properties file. Supported properties are documented in the [MSK Developer Guide](https://docs.aws.amazon.com/msk/latest/developerguide/msk-configuration-properties.html)" +} + +variable "autoscaling_enabled" { + type = bool + default = true + description = "To automatically expand your cluster's storage in response to increased usage, you can enable this. [More info](https://docs.aws.amazon.com/msk/latest/developerguide/msk-autoexpand.html)" +} + +variable "storage_autoscaling_target_value" { + type = number + default = 60 + description = "Percentage of storage used to trigger autoscaled storage increase" +} + +variable "storage_autoscaling_max_capacity" { + type = number + default = null + description = "Maximum size the autoscaling policy can scale storage. Defaults to `broker_volume_size`" +} + +variable "storage_autoscaling_disable_scale_in" { + type = bool + default = false + description = "If the value is true, scale in is disabled and the target tracking policy won't remove capacity from the scalable resource." +} + +variable "tags" { + type = map(string) + default = {} + description = "tags for the MSK cluster" +} + +variable "public_access_enabled" { + type = bool + default = false + description = "Enable public access to MSK cluster (given that all of the requirements are met)" + nullable = false +} + +variable "secret_name" { + type = string + default = "AmazonMSK_credential" + description = "AWS Secret Manager Secret name for MSK client_sasl_scram auth method." +} \ No newline at end of file diff --git a/modules/aws-msk-apache-kafka-cluster-master/versions.tf b/modules/aws-msk-apache-kafka-cluster-master/versions.tf new file mode 100644 index 0000000..fc6bdc5 --- /dev/null +++ b/modules/aws-msk-apache-kafka-cluster-master/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.14.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} diff --git a/modules/aws-rds-aurora/README.md b/modules/aws-rds-aurora/README.md new file mode 100644 index 0000000..ba53873 --- /dev/null +++ b/modules/aws-rds-aurora/README.md @@ -0,0 +1,400 @@ +# AWS RDS Aurora Terraform module + +Terraform module which creates AWS RDS Aurora resources. + + +## Available Features + +- Autoscaling of read-replicas +- Global cluster +- Enhanced monitoring +- Serverless cluster (v1 and v2) +- Import from S3 +- Fine grained control of individual cluster instances +- Custom endpoints +- RDS multi-AZ support (not Aurora) + +## Usage + +```hcl +module "cluster" { + source = "terraform-modules/rds-aurora/aws" + + name = "test-aurora-db-postgres96" + engine = "aurora-postgresql" + engine_version = "11.12" + instance_class = "db.r6g.large" + instances = { + one = {} + 2 = { + instance_class = "db.r6g.2xlarge" + } + } + + vpc_id = "vpc-12345678" + subnets = ["subnet-12345678", "subnet-87654321"] + + allowed_security_groups = ["sg-12345678"] + allowed_cidr_blocks = ["10.20.0.0/20"] + + storage_encrypted = true + apply_immediately = true + monitoring_interval = 10 + + db_parameter_group_name = "default" + db_cluster_parameter_group_name = "default" + + enabled_cloudwatch_logs_exports = ["postgresql"] + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### Cluster Instance Configuration + +There are a couple different configuration methods that can be used to create instances within the cluster: + +ℹ️ Only the pertinent attributes are shown for brevity + +1. Create homogenous cluster of any number of instances + + - Resources created: + - Writer: 1 + - Reader(s): 2 + +```hcl + instance_class = "db.r6g.large" + instances = { + one = {} + two = {} + three = {} + } +``` + +2. Create homogenous cluster of instances w/ autoscaling enabled. This is redundant and we'll show why in the next example. + + - Resources created: + - Writer: 1 + - Reader(s): + - At least 4 readers (2 created directly, 2 created by appautoscaling) + - At most 7 reader instances (2 created directly, 5 created by appautoscaling) + +ℹ️ Autoscaling uses the instance class specified by `instance_class`. + +```hcl + instance_class = "db.r6g.large" + instances = { + one = {} + two = {} + three = {} + } + + autoscaling_enabled = true + autoscaling_min_capacity = 2 + autoscaling_max_capacity = 5 +``` + +3. Create homogeneous cluster scaled via autoscaling. At least one instance (writer) is required + + - Resources created: + - Writer: 1 + - Reader(s): + - At least 1 reader + - At most 5 readers + +```hcl + instance_class = "db.r6g.large" + instances = { + one = {} + } + + autoscaling_enabled = true + autoscaling_min_capacity = 1 + autoscaling_max_capacity = 5 +``` + +4. Create heterogenous cluster to support mixed-use workloads + + It is common in this configuration to independently control the instance `promotion_tier` paired with `endpoints` to create custom endpoints directed at select instances or instance groups. + + - Resources created: + - Writer: 1 + - Readers: 2 + +```hcl + instance_class = "db.r5.large" + instances = { + one = { + instance_class = "db.r5.2xlarge" + publicly_accessible = true + } + two = { + identifier = "static-member-1" + instance_class = "db.r5.2xlarge" + } + three = { + identifier = "excluded-member-1" + instance_class = "db.r5.large" + promotion_tier = 15 + } + } +``` + +5. Create heterogenous cluster to support mixed-use workloads w/ autoscaling enabled + + - Resources created: + - Writer: 1 + - Reader(s): + - At least 3 readers (2 created directly, 1 created through appautoscaling) + - At most 7 readers (2 created directly, 5 created through appautoscaling) + +ℹ️ Autoscaling uses the instance class specified by `instance_class`. + +```hcl + instance_class = "db.r5.large" + instances = { + one = { + instance_class = "db.r5.2xlarge" + publicly_accessible = true + } + two = { + identifier = "static-member-1" + instance_class = "db.r5.2xlarge" + } + three = { + identifier = "excluded-member-1" + instance_class = "db.r5.large" + promotion_tier = 15 + } + } + + autoscaling_enabled = true + autoscaling_min_capacity = 1 + autoscaling_max_capacity = 5 +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +# This RDS cluster will not be created +module "cluster" { + source = "terraform-modules/rds-aurora/aws" + + # Disable creation of cluster and all resources + create_cluster = false + + # Disable creation of subnet group - provide a subnet group + create_db_subnet_group = false + + # Disable creation of security group - provide a security group + create_security_group = false + + # Disable creation of monitoring IAM role - provide a role ARN + create_monitoring_role = false + + # Disable creation of random password - AWS API provides the password + create_random_password = false + + # ... omitted +} +``` + +## Examples + +- [Autoscaling](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/autoscaling): A PostgreSQL cluster with enhanced monitoring and autoscaling enabled +- [Global Cluster](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/global-cluster): A PostgreSQL global cluster with clusters provisioned in two different region +- [Multi-AZ](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/multi-az): A multi-AZ RDS cluster (not using Aurora engine) +- [MySQL](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/mysql): A simple MySQL cluster +- [PostgreSQL](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/postgresql): A simple PostgreSQL cluster +- [S3 Import](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/s3-import): A MySQL cluster created from a Percona Xtrabackup stored in S3 +- [Serverless](https://github.com/ToggTrumore/terraform-modules/terraform-aws-rds-aurora/tree/main/examples/serverless): Serverless V1 and V2 (PostgreSQL and MySQL) + +## Documentation + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [aws](#requirement\_aws) | >= 4.30 | +| [random](#requirement\_random) | >= 2.2 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.30 | +| [random](#provider\_random) | >= 2.2 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_db_parameter_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_parameter_group) | resource | +| [aws_db_subnet_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource | +| [aws_iam_role.rds_enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.rds_enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_rds_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster) | resource | +| [aws_rds_cluster_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_endpoint) | resource | +| [aws_rds_cluster_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_instance) | resource | +| [aws_rds_cluster_parameter_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource | +| [aws_rds_cluster_role_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_role_association) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.cidr_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.default_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [random_id.snapshot_identifier](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [random_password.master_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_iam_policy_document.monitoring_rds_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allocated\_storage](#input\_allocated\_storage) | The amount of storage in gibibytes (GiB) to allocate to each DB instance in the Multi-AZ DB cluster. (This setting is required to create a Multi-AZ DB cluster) | `number` | `null` | no | +| [allow\_major\_version\_upgrade](#input\_allow\_major\_version\_upgrade) | Enable to allow major engine version upgrades when changing engine versions. Defaults to `false` | `bool` | `false` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | A list of CIDR blocks which are allowed to access the database | `list(string)` | `[]` | no | +| [allowed\_security\_groups](#input\_allowed\_security\_groups) | A list of Security Group ID's to allow access to | `list(string)` | `[]` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any cluster modifications are applied immediately, or during the next maintenance window. Default is `false` | `bool` | `null` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window. Default `true` | `bool` | `null` | no | +| [autoscaling\_enabled](#input\_autoscaling\_enabled) | Determines whether autoscaling of the cluster read replicas is enabled | `bool` | `false` | no | +| [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of read replicas permitted when autoscaling is enabled | `number` | `2` | no | +| [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of read replicas permitted when autoscaling is enabled | `number` | `0` | no | +| [autoscaling\_policy\_name](#input\_autoscaling\_policy\_name) | Autoscaling policy name | `string` | `"target-metric"` | no | +| [autoscaling\_scale\_in\_cooldown](#input\_autoscaling\_scale\_in\_cooldown) | Cooldown in seconds before allowing further scaling operations after a scale in | `number` | `300` | no | +| [autoscaling\_scale\_out\_cooldown](#input\_autoscaling\_scale\_out\_cooldown) | Cooldown in seconds before allowing further scaling operations after a scale out | `number` | `300` | no | +| [autoscaling\_target\_connections](#input\_autoscaling\_target\_connections) | Average number of connections threshold which will initiate autoscaling. Default value is 70% of db.r4/r5/r6g.large's default max\_connections | `number` | `700` | no | +| [autoscaling\_target\_cpu](#input\_autoscaling\_target\_cpu) | CPU threshold which will initiate autoscaling | `number` | `70` | no | +| [availability\_zones](#input\_availability\_zones) | List of EC2 Availability Zones for the DB cluster storage where DB cluster instances can be created. RDS automatically assigns 3 AZs if less than 3 AZs are configured, which will show as a difference requiring resource recreation next Terraform apply | `list(string)` | `null` | no | +| [backtrack\_window](#input\_backtrack\_window) | The target backtrack window, in seconds. Only available for `aurora` engine currently. To disable backtracking, set this value to 0. Must be between 0 and 259200 (72 hours) | `number` | `null` | no | +| [backup\_retention\_period](#input\_backup\_retention\_period) | The days to retain backups for. Default `7` | `number` | `7` | no | +| [ca\_cert\_identifier](#input\_ca\_cert\_identifier) | The identifier of the CA certificate for the DB instance | `string` | `null` | no | +| [cluster\_members](#input\_cluster\_members) | List of RDS Instances that are a part of this cluster | `list(string)` | `null` | no | +| [cluster\_tags](#input\_cluster\_tags) | A map of tags to add to only the cluster. Used for AWS Instance Scheduler tagging | `map(string)` | `{}` | no | +| [cluster\_timeouts](#input\_cluster\_timeouts) | Create, update, and delete timeout configurations for the cluster | `map(string)` | `{}` | no | +| [cluster\_use\_name\_prefix](#input\_cluster\_use\_name\_prefix) | Whether to use `name` as a prefix for the cluster | `bool` | `false` | no | +| [copy\_tags\_to\_snapshot](#input\_copy\_tags\_to\_snapshot) | Copy all Cluster `tags` to snapshots | `bool` | `null` | no | +| [create\_cluster](#input\_create\_cluster) | Whether cluster should be created (affects nearly all resources) | `bool` | `true` | no | +| [create\_db\_cluster\_parameter\_group](#input\_create\_db\_cluster\_parameter\_group) | Determines whether a cluster parameter should be created or use existing | `bool` | `false` | no | +| [create\_db\_parameter\_group](#input\_create\_db\_parameter\_group) | Determines whether a DB parameter should be created or use existing | `bool` | `false` | no | +| [create\_db\_subnet\_group](#input\_create\_db\_subnet\_group) | Determines whether to create the database subnet group or use existing | `bool` | `true` | no | +| [create\_monitoring\_role](#input\_create\_monitoring\_role) | Determines whether to create the IAM role for RDS enhanced monitoring | `bool` | `true` | no | +| [create\_random\_password](#input\_create\_random\_password) | Determines whether to create random password for RDS primary cluster | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines whether to create security group for RDS cluster | `bool` | `true` | no | +| [database\_name](#input\_database\_name) | Name for an automatically created database on cluster creation | `string` | `null` | no | +| [db\_cluster\_db\_instance\_parameter\_group\_name](#input\_db\_cluster\_db\_instance\_parameter\_group\_name) | Instance parameter group to associate with all instances of the DB cluster. The `db_cluster_db_instance_parameter_group_name` is only valid in combination with `allow_major_version_upgrade` | `string` | `null` | no | +| [db\_cluster\_instance\_class](#input\_db\_cluster\_instance\_class) | The compute and memory capacity of each DB instance in the Multi-AZ DB cluster, for example db.m6g.xlarge. Not all DB instance classes are available in all AWS Regions, or for all database engines | `string` | `null` | no | +| [db\_cluster\_parameter\_group\_description](#input\_db\_cluster\_parameter\_group\_description) | The description of the DB cluster parameter group. Defaults to "Managed by Terraform" | `string` | `null` | no | +| [db\_cluster\_parameter\_group\_family](#input\_db\_cluster\_parameter\_group\_family) | The family of the DB cluster parameter group | `string` | `""` | no | +| [db\_cluster\_parameter\_group\_name](#input\_db\_cluster\_parameter\_group\_name) | The name of the DB cluster parameter group | `string` | `null` | no | +| [db\_cluster\_parameter\_group\_parameters](#input\_db\_cluster\_parameter\_group\_parameters) | A list of DB cluster parameters to apply. Note that parameters may differ from a family to an other | `list(map(string))` | `[]` | no | +| [db\_cluster\_parameter\_group\_use\_name\_prefix](#input\_db\_cluster\_parameter\_group\_use\_name\_prefix) | Determines whether the DB cluster parameter group name is used as a prefix | `bool` | `true` | no | +| [db\_parameter\_group\_description](#input\_db\_parameter\_group\_description) | The description of the DB parameter group. Defaults to "Managed by Terraform" | `string` | `null` | no | +| [db\_parameter\_group\_family](#input\_db\_parameter\_group\_family) | The family of the DB parameter group | `string` | `""` | no | +| [db\_parameter\_group\_name](#input\_db\_parameter\_group\_name) | The name of the DB parameter group | `string` | `null` | no | +| [db\_parameter\_group\_parameters](#input\_db\_parameter\_group\_parameters) | A list of DB parameters to apply. Note that parameters may differ from a family to an other | `list(map(string))` | `[]` | no | +| [db\_parameter\_group\_use\_name\_prefix](#input\_db\_parameter\_group\_use\_name\_prefix) | Determines whether the DB parameter group name is used as a prefix | `bool` | `true` | no | +| [db\_subnet\_group\_name](#input\_db\_subnet\_group\_name) | The name of the subnet group name (existing or created) | `string` | `""` | no | +| [deletion\_protection](#input\_deletion\_protection) | If the DB instance should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false` | `bool` | `null` | no | +| [enable\_global\_write\_forwarding](#input\_enable\_global\_write\_forwarding) | Whether cluster should forward writes to an associated global cluster. Applied to secondary clusters to enable them to forward writes to an `aws_rds_global_cluster`'s primary cluster | `bool` | `null` | no | +| [enable\_http\_endpoint](#input\_enable\_http\_endpoint) | Enable HTTP endpoint (data API). Only valid when engine\_mode is set to `serverless` | `bool` | `null` | no | +| [enabled\_cloudwatch\_logs\_exports](#input\_enabled\_cloudwatch\_logs\_exports) | Set of log types to export to cloudwatch. If omitted, no logs will be exported. The following log types are supported: `audit`, `error`, `general`, `slowquery`, `postgresql` | `list(string)` | `[]` | no | +| [endpoints](#input\_endpoints) | Map of additional cluster endpoints and their attributes to be created | `any` | `{}` | no | +| [engine](#input\_engine) | The name of the database engine to be used for this DB cluster. Defaults to `aurora`. Valid Values: `aurora`, `aurora-mysql`, `aurora-postgresql` | `string` | `null` | no | +| [engine\_mode](#input\_engine\_mode) | The database engine mode. Valid values: `global`, `multimaster`, `parallelquery`, `provisioned`, `serverless`. Defaults to: `provisioned` | `string` | `null` | no | +| [engine\_version](#input\_engine\_version) | The database engine version. Updating this argument results in an outage | `string` | `null` | no | +| [final\_snapshot\_identifier\_prefix](#input\_final\_snapshot\_identifier\_prefix) | The prefix name to use when creating a final snapshot on cluster destroy; a 8 random digits are appended to name to ensure it's unique | `string` | `"final"` | no | +| [global\_cluster\_identifier](#input\_global\_cluster\_identifier) | The global cluster identifier specified on `aws_rds_global_cluster` | `string` | `null` | no | +| [iam\_database\_authentication\_enabled](#input\_iam\_database\_authentication\_enabled) | Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled | `bool` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the monitoring role | `string` | `null` | no | +| [iam\_role\_force\_detach\_policies](#input\_iam\_role\_force\_detach\_policies) | Whether to force detaching any policies the monitoring role has before destroying it | `bool` | `null` | no | +| [iam\_role\_managed\_policy\_arns](#input\_iam\_role\_managed\_policy\_arns) | Set of exclusive IAM managed policy ARNs to attach to the monitoring role | `list(string)` | `null` | no | +| [iam\_role\_max\_session\_duration](#input\_iam\_role\_max\_session\_duration) | Maximum session duration (in seconds) that you want to set for the monitoring role | `number` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Friendly name of the monitoring role | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | Path for the monitoring role | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the monitoring role | `string` | `null` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether to use `iam_role_name` as is or create a unique name beginning with the `iam_role_name` as the prefix | `bool` | `false` | no | +| [iam\_roles](#input\_iam\_roles) | Map of IAM roles and supported feature names to associate with the cluster | `map(map(string))` | `{}` | no | +| [instance\_class](#input\_instance\_class) | Instance type to use at master instance. Note: if `autoscaling_enabled` is `true`, this will be the same instance class used on instances created by autoscaling | `string` | `""` | no | +| [instance\_timeouts](#input\_instance\_timeouts) | Create, update, and delete timeout configurations for the cluster instance(s) | `map(string)` | `{}` | no | +| [instances](#input\_instances) | Map of cluster instances and any specific/overriding attributes to be created | `any` | `{}` | no | +| [instances\_use\_identifier\_prefix](#input\_instances\_use\_identifier\_prefix) | Determines whether cluster instance identifiers are used as prefixes | `bool` | `false` | no | +| [iops](#input\_iops) | The amount of Provisioned IOPS (input/output operations per second) to be initially allocated for each DB instance in the Multi-AZ DB cluster | `number` | `null` | no | +| [is\_primary\_cluster](#input\_is\_primary\_cluster) | Determines whether cluster is primary cluster with writer instance (set to `false` for global cluster and replica clusters) | `bool` | `true` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to `true` | `string` | `null` | no | +| [master\_password](#input\_master\_password) | Password for the master DB user. Note - when specifying a value here, 'create\_random\_password' should be set to `false` | `string` | `null` | no | +| [master\_username](#input\_master\_username) | Username for the master DB user | `string` | `"root"` | no | +| [monitoring\_interval](#input\_monitoring\_interval) | The interval, in seconds, between points when Enhanced Monitoring metrics are collected for instances. Set to `0` to disable. Default is `0` | `number` | `0` | no | +| [monitoring\_role\_arn](#input\_monitoring\_role\_arn) | IAM role used by RDS to send enhanced monitoring metrics to CloudWatch | `string` | `""` | no | +| [name](#input\_name) | Name used across resources created | `string` | `""` | no | +| [network\_type](#input\_network\_type) | The type of network stack to use (IPV4 or DUAL) | `string` | `null` | no | +| [performance\_insights\_enabled](#input\_performance\_insights\_enabled) | Specifies whether Performance Insights is enabled or not | `bool` | `null` | no | +| [performance\_insights\_kms\_key\_id](#input\_performance\_insights\_kms\_key\_id) | The ARN for the KMS key to encrypt Performance Insights data | `string` | `null` | no | +| [performance\_insights\_retention\_period](#input\_performance\_insights\_retention\_period) | Amount of time in days to retain Performance Insights data. Either 7 (7 days) or 731 (2 years) | `number` | `null` | no | +| [port](#input\_port) | The port on which the DB accepts connections | `string` | `null` | no | +| [predefined\_metric\_type](#input\_predefined\_metric\_type) | The metric type to scale on. Valid values are `RDSReaderAverageCPUUtilization` and `RDSReaderAverageDatabaseConnections` | `string` | `"RDSReaderAverageCPUUtilization"` | no | +| [preferred\_backup\_window](#input\_preferred\_backup\_window) | The daily time range during which automated backups are created if automated backups are enabled using the `backup_retention_period` parameter. Time in UTC | `string` | `"02:00-03:00"` | no | +| [preferred\_maintenance\_window](#input\_preferred\_maintenance\_window) | The weekly time range during which system maintenance can occur, in (UTC) | `string` | `"sun:05:00-sun:06:00"` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Determines whether instances are publicly accessible. Default false | `bool` | `null` | no | +| [random\_password\_length](#input\_random\_password\_length) | Length of random password to create. Defaults to `10` | `number` | `10` | no | +| [replication\_source\_identifier](#input\_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` | `null` | no | +| [restore\_to\_point\_in\_time](#input\_restore\_to\_point\_in\_time) | Map of nested attributes for cloning Aurora cluster | `map(string)` | `{}` | no | +| [s3\_import](#input\_s3\_import) | Configuration map used to restore from a Percona Xtrabackup in S3 (only MySQL is supported) | `map(string)` | `{}` | no | +| [scaling\_configuration](#input\_scaling\_configuration) | Map of nested attributes with scaling properties. Only valid when `engine_mode` is set to `serverless` | `map(string)` | `{}` | no | +| [security\_group\_description](#input\_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` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | A map of security group egress rule definitions to add to the security group created | `map(any)` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | Additional tags for the security group | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`name`) is used as a prefix | `bool` | `true` | no | +| [serverlessv2\_scaling\_configuration](#input\_serverlessv2\_scaling\_configuration) | Map of nested attributes with serverless v2 scaling properties. Only valid when `engine_mode` is set to `provisioned` | `map(string)` | `{}` | no | +| [skip\_final\_snapshot](#input\_skip\_final\_snapshot) | Determines whether a final snapshot is created before the cluster is deleted. If true is specified, no snapshot is created | `bool` | `null` | no | +| [snapshot\_identifier](#input\_snapshot\_identifier) | Specifies whether or not to create this cluster from a snapshot. You can use either the name or ARN when specifying a DB cluster snapshot, or the ARN when specifying a DB snapshot | `string` | `null` | no | +| [source\_region](#input\_source\_region) | The source region for an encrypted replica DB cluster | `string` | `null` | no | +| [storage\_encrypted](#input\_storage\_encrypted) | Specifies whether the DB cluster is encrypted. The default is `true` | `bool` | `true` | no | +| [storage\_type](#input\_storage\_type) | Specifies the storage type to be associated with the DB cluster. (This setting is required to create a Multi-AZ DB cluster). Valid values: `io1`, Default: `io1` | `string` | `null` | no | +| [subnets](#input\_subnets) | List of subnet IDs used by database subnet group created | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | `""` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC security groups to associate to the cluster in addition to the SG we create in this module | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [additional\_cluster\_endpoints](#output\_additional\_cluster\_endpoints) | A map of additional cluster endpoints and their attributes | +| [cluster\_arn](#output\_cluster\_arn) | Amazon Resource Name (ARN) of cluster | +| [cluster\_database\_name](#output\_cluster\_database\_name) | Name for an automatically created database on cluster creation | +| [cluster\_endpoint](#output\_cluster\_endpoint) | Writer endpoint for the cluster | +| [cluster\_engine\_version\_actual](#output\_cluster\_engine\_version\_actual) | The running version of the cluster database | +| [cluster\_hosted\_zone\_id](#output\_cluster\_hosted\_zone\_id) | The Route53 Hosted Zone ID of the endpoint | +| [cluster\_id](#output\_cluster\_id) | The RDS Cluster Identifier | +| [cluster\_instances](#output\_cluster\_instances) | A map of cluster instances and their attributes | +| [cluster\_master\_password](#output\_cluster\_master\_password) | The database master password | +| [cluster\_master\_username](#output\_cluster\_master\_username) | The database master username | +| [cluster\_members](#output\_cluster\_members) | List of RDS Instances that are a part of this cluster | +| [cluster\_port](#output\_cluster\_port) | The database port | +| [cluster\_reader\_endpoint](#output\_cluster\_reader\_endpoint) | A read-only endpoint for the cluster, automatically load-balanced across replicas | +| [cluster\_resource\_id](#output\_cluster\_resource\_id) | The RDS Cluster Resource ID | +| [cluster\_role\_associations](#output\_cluster\_role\_associations) | A map of IAM roles associated with the cluster and their attributes | +| [db\_cluster\_parameter\_group\_arn](#output\_db\_cluster\_parameter\_group\_arn) | The ARN of the DB cluster parameter group created | +| [db\_cluster\_parameter\_group\_id](#output\_db\_cluster\_parameter\_group\_id) | The ID of the DB cluster parameter group created | +| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the DB parameter group created | +| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The ID of the DB parameter group created | +| [db\_subnet\_group\_name](#output\_db\_subnet\_group\_name) | The db subnet group name | +| [enhanced\_monitoring\_iam\_role\_arn](#output\_enhanced\_monitoring\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the enhanced monitoring role | +| [enhanced\_monitoring\_iam\_role\_name](#output\_enhanced\_monitoring\_iam\_role\_name) | The name of the enhanced monitoring role | +| [enhanced\_monitoring\_iam\_role\_unique\_id](#output\_enhanced\_monitoring\_iam\_role\_unique\_id) | Stable and unique string identifying the enhanced monitoring role | +| [security\_group\_id](#output\_security\_group\_id) | The security group ID of the cluster | + diff --git a/modules/aws-rds-aurora/main.tf b/modules/aws-rds-aurora/main.tf new file mode 100644 index 0000000..567668c --- /dev/null +++ b/modules/aws-rds-aurora/main.tf @@ -0,0 +1,791 @@ +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +locals { + create_cluster = var.create_cluster + + port = coalesce(var.port, (var.engine == "aurora-postgresql" ? 5432 : 3306)) + + internal_db_subnet_group_name = try(coalesce(var.db_subnet_group_name, var.name), "") + db_subnet_group_name = var.create_db_subnet_group ? try(aws_db_subnet_group.this[0].name, null) : local.internal_db_subnet_group_name + + cluster_parameter_group_name = try(coalesce(var.db_cluster_parameter_group_name, var.name), null) + db_parameter_group_name = try(coalesce(var.db_parameter_group_name, var.name), null) + + master_password = var.create_random_password && var.rds_custom ? random_password.master_password[0].result : var.master_password + backtrack_window = (var.engine == "aurora-mysql" || var.engine == "aurora") && var.engine_mode != "serverless" ? var.backtrack_window : 0 + + is_serverless = var.engine_mode == "serverless" +} + +################################################################################ +# Random Password & Snapshot ID +################################################################################ + +resource "random_password" "master_password" { + count = var.create_random_password ? 1 : 0 + + length = var.random_password_length + special = false +} + +resource "random_id" "snapshot_identifier" { + count = local.create_cluster ? 1 : 0 + + keepers = { + id = var.name + } + + byte_length = 4 +} + +################################################################################ +# DB Subnet Group +################################################################################ + +data "aws_subnets" "private_subnets_with_database_tag" { + filter { + name = "vpc-id" + values = [var.vpc_id] + } + filter { + name = "tag:Name" + values = ["${var.subnet_id_names}"] + } +} + +resource "aws_db_subnet_group" "this" { + count = var.create_db_subnet_group ? 1 : 0 + + name = local.internal_db_subnet_group_name + description = "For Aurora cluster ${var.name}" + subnet_ids = try(data.aws_subnets.private_subnets_with_database_tag.ids, null) + + tags = var.tags +} + +################################################################################ +# Cluster +################################################################################ + +resource "aws_rds_cluster" "this" { + count = local.create_cluster ? 1 : 0 + + allocated_storage = var.allocated_storage + allow_major_version_upgrade = var.allow_major_version_upgrade + apply_immediately = var.apply_immediately + availability_zones = var.availability_zones + backup_retention_period = var.backup_retention_period + backtrack_window = local.backtrack_window + cluster_identifier = var.cluster_use_name_prefix ? null : var.name + cluster_identifier_prefix = var.cluster_use_name_prefix ? "${var.name}-" : null + cluster_members = var.cluster_members + copy_tags_to_snapshot = var.copy_tags_to_snapshot + database_name = var.is_primary_cluster ? var.database_name : null + db_cluster_instance_class = var.db_cluster_instance_class + db_cluster_parameter_group_name = var.create_db_cluster_parameter_group ? aws_rds_cluster_parameter_group.this[0].id : var.db_cluster_parameter_group_name + db_instance_parameter_group_name = var.allow_major_version_upgrade ? var.db_cluster_db_instance_parameter_group_name : null + db_subnet_group_name = local.db_subnet_group_name + deletion_protection = var.deletion_protection + enable_global_write_forwarding = var.enable_global_write_forwarding + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports + enable_http_endpoint = var.enable_http_endpoint + engine = var.engine + engine_mode = var.engine_mode + engine_version = var.engine_version + final_snapshot_identifier = "${var.final_snapshot_identifier_prefix}-${var.name}-${try(random_id.snapshot_identifier[0].hex, "")}" + global_cluster_identifier = var.global_cluster_identifier + iam_database_authentication_enabled = var.iam_database_authentication_enabled + # iam_roles has been removed from this resource and instead will be used with aws_rds_cluster_role_association below to avoid conflicts per docs + iops = var.iops + kms_key_id = var.is_primary_cluster ? aws_kms_key.kms.arn : var.kms_key_id + master_password = var.is_primary_cluster ? local.master_password : null + master_username = var.is_primary_cluster ? var.master_username : null + network_type = var.network_type + port = local.port + preferred_backup_window = local.is_serverless ? null : var.preferred_backup_window + preferred_maintenance_window = local.is_serverless ? null : var.preferred_maintenance_window + replication_source_identifier = var.replication_source_identifier + + dynamic "restore_to_point_in_time" { + for_each = length(var.restore_to_point_in_time) > 0 ? [var.restore_to_point_in_time] : [] + + content { + restore_to_time = try(restore_to_point_in_time.value.restore_to_time, null) + restore_type = try(restore_to_point_in_time.value.restore_type, null) + source_cluster_identifier = restore_to_point_in_time.value.source_cluster_identifier + use_latest_restorable_time = try(restore_to_point_in_time.value.use_latest_restorable_time, null) + } + } + + dynamic "s3_import" { + for_each = length(var.s3_import) > 0 && !local.is_serverless ? [var.s3_import] : [] + + content { + bucket_name = s3_import.value.bucket_name + bucket_prefix = try(s3_import.value.bucket_prefix, null) + ingestion_role = s3_import.value.ingestion_role + source_engine = "mysql" + source_engine_version = s3_import.value.source_engine_version + } + } + + dynamic "scaling_configuration" { + for_each = length(var.scaling_configuration) > 0 && local.is_serverless ? [var.scaling_configuration] : [] + + content { + auto_pause = try(scaling_configuration.value.auto_pause, null) + max_capacity = try(scaling_configuration.value.max_capacity, null) + min_capacity = try(scaling_configuration.value.min_capacity, null) + seconds_until_auto_pause = try(scaling_configuration.value.seconds_until_auto_pause, null) + timeout_action = try(scaling_configuration.value.timeout_action, null) + } + } + + dynamic "serverlessv2_scaling_configuration" { + for_each = length(var.serverlessv2_scaling_configuration) > 0 && var.engine_mode == "provisioned" ? [var.serverlessv2_scaling_configuration] : [] + + content { + max_capacity = serverlessv2_scaling_configuration.value.max_capacity + min_capacity = serverlessv2_scaling_configuration.value.min_capacity + } + } + + skip_final_snapshot = var.skip_final_snapshot + snapshot_identifier = var.snapshot_identifier + source_region = var.source_region + storage_encrypted = var.storage_encrypted + storage_type = var.storage_type + tags = merge(var.tags, var.cluster_tags) + vpc_security_group_ids = compact(concat([try(aws_security_group.this[0].id, "")], var.vpc_security_group_ids)) + + timeouts { + create = try(var.cluster_timeouts.create, null) + update = try(var.cluster_timeouts.update, null) + delete = try(var.cluster_timeouts.delete, null) + } + + lifecycle { + ignore_changes = [ + # See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#replication_source_identifier + # Since this is used either in read-replica clusters or global clusters, this should be acceptable to specify + replication_source_identifier, + # See docs here https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_global_cluster#new-global-cluster-from-existing-db-cluster + global_cluster_identifier, + ] + } +} + +################################################################################ +# Cluster Instance(s) +################################################################################ + +resource "aws_rds_cluster_instance" "this" { + for_each = { for k, v in var.instances : k => v if local.create_cluster && !local.is_serverless } + + apply_immediately = try(each.value.apply_immediately, var.apply_immediately) + auto_minor_version_upgrade = try(each.value.auto_minor_version_upgrade, var.auto_minor_version_upgrade) + availability_zone = try(each.value.availability_zone, null) + ca_cert_identifier = var.ca_cert_identifier + cluster_identifier = aws_rds_cluster.this[0].id + copy_tags_to_snapshot = try(each.value.copy_tags_to_snapshot, var.copy_tags_to_snapshot) + db_parameter_group_name = var.create_db_parameter_group ? aws_db_parameter_group.this[0].id : var.db_parameter_group_name + db_subnet_group_name = local.db_subnet_group_name + engine = var.engine + engine_version = var.engine_version + identifier = var.instances_use_identifier_prefix ? null : try(each.value.identifier, "${var.name}-${each.key}") + identifier_prefix = var.instances_use_identifier_prefix ? try(each.value.identifier_prefix, "${var.name}-${each.key}-") : null + instance_class = try(each.value.instance_class, var.instance_class) + monitoring_interval = try(each.value.monitoring_interval, var.monitoring_interval) + monitoring_role_arn = var.create_monitoring_role ? try(aws_iam_role.rds_enhanced_monitoring[0].arn, null) : var.monitoring_role_arn + performance_insights_enabled = try(each.value.performance_insights_enabled, var.performance_insights_enabled) + performance_insights_kms_key_id = try(each.value.performance_insights_kms_key_id, var.performance_insights_kms_key_id) + performance_insights_retention_period = try(each.value.performance_insights_retention_period, var.performance_insights_retention_period) + # preferred_backup_window - is set at the cluster level and will error if provided here + preferred_maintenance_window = try(each.value.preferred_maintenance_window, var.preferred_maintenance_window) + promotion_tier = try(each.value.promotion_tier, null) + publicly_accessible = try(each.value.publicly_accessible, var.publicly_accessible) + tags = merge(var.tags, try(each.value.tags, {})) + + timeouts { + create = try(var.instance_timeouts.create, null) + update = try(var.instance_timeouts.update, null) + delete = try(var.instance_timeouts.delete, null) + } +} + +################################################################################ +# Cluster Endpoint(s) +################################################################################ + +resource "aws_rds_cluster_endpoint" "this" { + for_each = { for k, v in var.endpoints : k => v if local.create_cluster && !local.is_serverless } + + cluster_endpoint_identifier = each.value.identifier + cluster_identifier = aws_rds_cluster.this[0].id + custom_endpoint_type = each.value.type + excluded_members = try(each.value.excluded_members, null) + static_members = try(each.value.static_members, null) + tags = merge(var.tags, try(each.value.tags, {})) + + depends_on = [ + aws_rds_cluster_instance.this + ] +} + +################################################################################ +# Cluster IAM Roles +################################################################################ + +resource "aws_rds_cluster_role_association" "this" { + for_each = { for k, v in var.iam_roles : k => v if local.create_cluster } + + db_cluster_identifier = aws_rds_cluster.this[0].id + feature_name = each.value.feature_name + role_arn = each.value.role_arn +} + +################################################################################ +# Enhanced Monitoring +################################################################################ + +data "aws_iam_policy_document" "monitoring_rds_assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["monitoring.rds.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "rds_enhanced_monitoring" { + count = var.create_monitoring_role && var.monitoring_interval > 0 ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : var.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${var.iam_role_name}-" : null + description = var.iam_role_description + path = var.iam_role_path + + assume_role_policy = data.aws_iam_policy_document.monitoring_rds_assume_role.json + managed_policy_arns = var.iam_role_managed_policy_arns + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = var.iam_role_force_detach_policies + max_session_duration = var.iam_role_max_session_duration + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "rds_enhanced_monitoring" { + count = var.create_monitoring_role && var.monitoring_interval > 0 ? 1 : 0 + + role = aws_iam_role.rds_enhanced_monitoring[0].name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" +} + +################################################################################ +# Autoscaling +################################################################################ + +resource "aws_appautoscaling_target" "this" { + count = local.create_cluster && var.autoscaling_enabled && !local.is_serverless ? 1 : 0 + + max_capacity = var.autoscaling_max_capacity + min_capacity = var.autoscaling_min_capacity + resource_id = "cluster:${aws_rds_cluster.this[0].cluster_identifier}" + scalable_dimension = "rds:cluster:ReadReplicaCount" + service_namespace = "rds" +} + +resource "aws_appautoscaling_policy" "this" { + count = local.create_cluster && var.autoscaling_enabled && !local.is_serverless ? 1 : 0 + + name = var.autoscaling_policy_name + policy_type = "TargetTrackingScaling" + resource_id = "cluster:${aws_rds_cluster.this[0].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.autoscaling_scale_in_cooldown + scale_out_cooldown = var.autoscaling_scale_out_cooldown + target_value = var.predefined_metric_type == "RDSReaderAverageCPUUtilization" ? var.autoscaling_target_cpu : var.autoscaling_target_connections + } + + depends_on = [ + aws_appautoscaling_target.this + ] +} + +################################################################################ +# Security Group +################################################################################ + +resource "aws_security_group" "this" { + count = var.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : var.name + name_prefix = var.security_group_use_name_prefix ? "${var.name}-" : null + vpc_id = var.vpc_id + description = coalesce(var.security_group_description, "Control traffic to/from RDS Aurora ${var.name}") + + tags = merge(var.tags, var.security_group_tags, { Name = var.name }) + + lifecycle { + create_before_destroy = true + } +} + +# TODO - change to map of ingress rules under one resource at next breaking change +resource "aws_security_group_rule" "default_ingress" { + count = var.create_security_group ? length(var.allowed_security_groups) : 0 + + description = "From allowed SGs" + + type = "ingress" + from_port = local.port + to_port = local.port + protocol = "tcp" + source_security_group_id = element(var.allowed_security_groups, count.index) + security_group_id = aws_security_group.this[0].id +} + +# TODO - change to map of ingress rules under one resource at next breaking change +resource "aws_security_group_rule" "cidr_ingress" { + count = var.create_security_group && length(var.allowed_cidr_blocks) > 0 ? 1 : 0 + + description = "From allowed CIDRs" + + type = "ingress" + from_port = local.port + to_port = local.port + protocol = "tcp" + cidr_blocks = var.allowed_cidr_blocks + security_group_id = aws_security_group.this[0].id +} + +resource "aws_security_group_rule" "egress" { + for_each = var.create_security_group ? var.security_group_egress_rules : {} + + # required + type = "egress" + from_port = try(each.value.from_port, local.port) + to_port = try(each.value.to_port, local.port) + protocol = "tcp" + security_group_id = aws_security_group.this[0].id + + # optional + cidr_blocks = try(each.value.cidr_blocks, null) + description = try(each.value.description, null) + ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null) + prefix_list_ids = try(each.value.prefix_list_ids, null) + source_security_group_id = try(each.value.source_security_group_id, null) +} + +################################################################################ +# Cluster Parameter Group +################################################################################ + +resource "aws_rds_cluster_parameter_group" "this" { + count = local.create_cluster && var.create_db_cluster_parameter_group ? 1 : 0 + + name = var.db_cluster_parameter_group_use_name_prefix ? null : local.cluster_parameter_group_name + name_prefix = var.db_cluster_parameter_group_use_name_prefix ? "${local.cluster_parameter_group_name}-" : null + description = var.db_cluster_parameter_group_description + family = var.db_cluster_parameter_group_family + + dynamic "parameter" { + for_each = var.db_cluster_parameter_group_parameters + + content { + name = parameter.value.name + value = parameter.value.value + apply_method = try(parameter.value.apply_method, "immediate") + } + } + + tags = var.tags +} + +################################################################################ +# DB Parameter Group +################################################################################ + +resource "aws_db_parameter_group" "this" { + count = local.create_cluster && var.create_db_parameter_group && var.rds_custom ? 1 : 0 + + name = var.db_parameter_group_use_name_prefix ? null : local.db_parameter_group_name + name_prefix = var.db_parameter_group_use_name_prefix ? "${local.db_parameter_group_name}-" : null + description = var.db_parameter_group_description + family = var.db_parameter_group_family + + dynamic "parameter" { + for_each = var.db_parameter_group_parameters + + content { + name = parameter.value.name + value = parameter.value.value + apply_method = try(parameter.value.apply_method, "immediate") + } + } + + tags = var.tags +} + +data "aws_caller_identity" "current" {} +data "aws_iam_policy_document" "rds" { + statement { + sid = "Enable IAM User Permissions" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", + data.aws_caller_identity.current.arn, + ] + } + } + + statement { + sid = "Allow use of the key" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = ["*"] + + principals { + type = "Service" + identifiers = [ + "monitoring.rds.amazonaws.com", + "rds.amazonaws.com", + ] + } + } +} + +resource "aws_kms_key" "kms" { + policy = data.aws_iam_policy_document.rds.json + multi_region = var.kms_multi_region +} + + +############################# +# Amazon RDS for SQL Server # +############################# + +resource "aws_iam_policy" "policy" { + count = var.rds_custom ? 1 : 0 + name = "AWSRDSCustomSQLServerIamRolePolicy" + path = "/" + description = "AWS RDS Custom SQL Server Policy" + + # Terraform's "jsonencode" function converts a + # Terraform expression result to valid JSON syntax. + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "ssmAgent1", + "Effect" : "Allow", + "Action" : [ + "ssm:GetDeployablePatchSnapshotForInstance", + "ssm:ListAssociations", + "ssm:PutInventory", + "ssm:PutConfigurePackageResult", + "ssm:UpdateInstanceInformation", + "ssm:GetManifest" + ], + "Resource" : "*" + }, + { + "Sid" : "ssmAgent2", + "Effect" : "Allow", + "Action" : [ + "ssm:ListInstanceAssociations", + "ssm:PutComplianceItems", + "ssm:UpdateAssociationStatus", + "ssm:DescribeAssociation", + "ssm:UpdateInstanceAssociationStatus" + ], + "Resource" : "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:instance/*" + }, + { + "Sid" : "ssmAgent3", + "Effect" : "Allow", + "Action" : [ + "ssm:UpdateAssociationStatus", + "ssm:DescribeAssociation", + "ssm:GetDocument", + "ssm:DescribeDocument" + ], + "Resource" : "arn:aws:ssm:*:*:document/*" + }, + { + "Sid" : "ssmAgent4", + "Effect" : "Allow", + "Action" : [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Resource" : "*" + }, + { + "Sid" : "ssmAgent5", + "Effect" : "Allow", + "Action" : [ + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply" + ], + "Resource" : "*" + }, + { + "Sid" : "ssmAgent6", + "Effect" : "Allow", + "Action" : [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Resource" : "arn:aws:ssm:*:*:parameter/*" + }, + { + "Sid" : "ssmAgent7", + "Effect" : "Allow", + "Action" : [ + "ssm:UpdateInstanceAssociationStatus", + "ssm:DescribeAssociation" + ], + "Resource" : "arn:aws:ssm:*:*:association/*" + }, + { + "Sid" : "eccSnapshot1", + "Effect" : "Allow", + "Action" : "ec2:CreateSnapshot", + "Resource" : [ + "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:volume/*" + ], + }, + { + "Sid" : "eccSnapshot2", + "Effect" : "Allow", + "Action" : "ec2:CreateSnapshot", + "Resource" : [ + "arn:aws:ec2:${data.aws_region.current.name}::snapshot/*" + ] + }, + { + "Sid" : "eccCreateTag", + "Effect" : "Allow", + "Action" : "ec2:CreateTags", + "Resource" : "*", + }, + { + "Sid" : "s3BucketAccess", + "Effect" : "Allow", + "Action" : [ + "s3:putObject", + "s3:getObject", + "s3:getObjectVersion", + "s3:AbortMultipartUpload" + ], + "Resource" : [ + "arn:aws:s3:::do-not-delete-rds-custom-*/*" + ] + }, + { + "Sid" : "customerKMSEncryption", + "Effect" : "Allow", + "Action" : [ + "kms:Decrypt", + "kms:GenerateDataKey*" + ], + "Resource" : [ + "${var.kms_key_id}" + ] + }, + { + "Sid" : "readSecretsFromCP", + "Effect" : "Allow", + "Action" : [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + "Resource" : [ + "arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:do-not-delete-rds-custom-*" + ] + }, + { + "Sid" : "publishCWMetrics", + "Effect" : "Allow", + "Action" : "cloudwatch:PutMetricData", + "Resource" : "*" + }, + { + "Sid" : "putEventsToEventBus", + "Effect" : "Allow", + "Action" : "events:PutEvents", + "Resource" : "arn:aws:events:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:event-bus/default" + }, + { + "Sid" : "cwlOperations1", + "Effect" : "Allow", + "Action" : [ + "logs:PutRetentionPolicy", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "logs:CreateLogStream", + "logs:CreateLogGroup" + ], + "Resource" : "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:rds-custom-instance-*" + }, + { + "Action" : [ + "SQS:SendMessage", + "SQS:ReceiveMessage", + "SQS:DeleteMessage", + "SQS:GetQueueUrl" + ], + "Resource" : [ + "arn:aws:sqs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:do-not-delete-rds-custom-*" + ], + "Effect" : "Allow", + "Sid" : "SendMessageToSQSQueue" + } + ] + }) +} + + +resource "aws_iam_role" "rds_custom_role" { + count = var.rds_custom ? 1 : 0 + name = "AWSRDSCustomSQLServerInstanceRole" + path = "/" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_policy_attachment" "custom_attach" { + count = var.rds_custom ? 1 : 0 + name = "rds-custom-attachment" + roles = [element(aws_iam_role.rds_custom_role[*].name, 0)] + policy_arn = element(aws_iam_policy.policy[*].arn, 0) + +} + +resource "aws_iam_instance_profile" "rds_custom_profile" { + count = var.rds_custom ? 1 : 0 + name = "AWSRDSCustomSQLServerInstanceProfile" + role = element(aws_iam_role.rds_custom_role[*].name, 0) + +} + +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_db_instance" "rds_sql_server" { + + count = var.rds_sql ? 1 : 0 + + engine = var.engine + engine_version = var.engine_version + port = 1433 + + identifier = var.instances_use_identifier_prefix ? null : var.name + + allow_major_version_upgrade = var.allow_major_version_upgrade + apply_immediately = var.apply_immediately + + custom_iam_instance_profile = var.rds_custom ? element(aws_iam_instance_profile.rds_custom_profile[*].name, 0) : null # Instance profile is required for Custom for SQL Server + + backup_window = var.preferred_backup_window + backup_retention_period = var.backup_retention_period + maintenance_window = var.preferred_maintenance_window + skip_final_snapshot = var.skip_final_snapshot + deletion_protection = var.deletion_protection + + db_subnet_group_name = local.db_subnet_group_name + instance_class = var.instance_class + kms_key_id = var.kms_key_id + parameter_group_name = var.custom_db_paramater_group_name + + allocated_storage = var.allocated_storage + storage_type = var.storage_type + storage_encrypted = var.storage_encrypted + license_model = "license-included" + + username = var.master_username + manage_master_user_password = true + + multi_az = var.multi_az # Custom RDS does support multi AZ + vpc_security_group_ids = var.allowed_security_groups + + timeouts { + create = "80m" + } + tags = var.tags + + depends_on = [aws_iam_policy_attachment.custom_attach] + +} + +resource "aws_db_instance" "rds_sql_server_read_replica" { + + count = var.read_replica ? 1 : 0 + + engine = var.engine + engine_version = var.engine_version + port = 1433 + + identifier = var.instances_use_identifier_prefix ? null : "${var.name}-read-replica" + + allow_major_version_upgrade = var.allow_major_version_upgrade + apply_immediately = var.apply_immediately + + + maintenance_window = var.preferred_maintenance_window + deletion_protection = var.deletion_protection + + instance_class = var.instance_class + kms_key_id = var.kms_key_id + parameter_group_name = var.custom_db_paramater_group_name + + storage_type = var.storage_type + storage_encrypted = var.storage_encrypted + + vpc_security_group_ids = var.allowed_security_groups + + replicate_source_db = element(aws_db_instance.rds_sql_server[*].identifier, 0) + + timeouts { + create = "80m" + } + + tags = var.tags + depends_on = [aws_iam_policy_attachment.custom_attach] +} \ No newline at end of file diff --git a/modules/aws-rds-aurora/outputs.tf b/modules/aws-rds-aurora/outputs.tf new file mode 100644 index 0000000..df8f56a --- /dev/null +++ b/modules/aws-rds-aurora/outputs.tf @@ -0,0 +1,162 @@ +################################################################################ +# DB Subnet Group +################################################################################ + +output "db_subnet_group_name" { + description = "The db subnet group name" + value = local.db_subnet_group_name +} + +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "Amazon Resource Name (ARN) of cluster" + value = try(aws_rds_cluster.this[0].arn, "") +} + +output "cluster_id" { + description = "The RDS Cluster Identifier" + value = try(aws_rds_cluster.this[0].id, "") +} + +output "cluster_resource_id" { + description = "The RDS Cluster Resource ID" + value = try(aws_rds_cluster.this[0].cluster_resource_id, "") +} + +output "cluster_members" { + description = "List of RDS Instances that are a part of this cluster" + value = try(aws_rds_cluster.this[0].cluster_members, "") +} + +output "cluster_endpoint" { + description = "Writer endpoint for the cluster" + value = try(aws_rds_cluster.this[0].endpoint, "") +} + +output "cluster_reader_endpoint" { + description = "A read-only endpoint for the cluster, automatically load-balanced across replicas" + value = try(aws_rds_cluster.this[0].reader_endpoint, "") +} + +output "cluster_engine_version_actual" { + description = "The running version of the cluster database" + value = try(aws_rds_cluster.this[0].engine_version_actual, "") +} + +# database_name is not set on `aws_rds_cluster` resource if it was not specified, so can't be used in output +output "cluster_database_name" { + description = "Name for an automatically created database on cluster creation" + value = var.database_name +} + +output "cluster_port" { + description = "The database port" + value = try(aws_rds_cluster.this[0].port, "") +} + +output "cluster_master_password" { + description = "The database master password" + value = try(aws_rds_cluster.this[0].master_password, "") + sensitive = true +} + +output "cluster_master_username" { + description = "The database master username" + value = try(aws_rds_cluster.this[0].master_username, "") + sensitive = true +} + +output "cluster_hosted_zone_id" { + description = "The Route53 Hosted Zone ID of the endpoint" + value = try(aws_rds_cluster.this[0].hosted_zone_id, "") +} + +################################################################################ +# Cluster Instance(s) +################################################################################ + +output "cluster_instances" { + description = "A map of cluster instances and their attributes" + value = aws_rds_cluster_instance.this +} + +################################################################################ +# Cluster Endpoint(s) +################################################################################ + +output "additional_cluster_endpoints" { + description = "A map of additional cluster endpoints and their attributes" + value = aws_rds_cluster_endpoint.this +} + +################################################################################ +# Cluster IAM Roles +################################################################################ + +output "cluster_role_associations" { + description = "A map of IAM roles associated with the cluster and their attributes" + value = aws_rds_cluster_role_association.this +} + +################################################################################ +# Enhanced Monitoring +################################################################################ + +output "enhanced_monitoring_iam_role_name" { + description = "The name of the enhanced monitoring role" + value = try(aws_iam_role.rds_enhanced_monitoring[0].name, "") +} + +output "enhanced_monitoring_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the enhanced monitoring role" + value = try(aws_iam_role.rds_enhanced_monitoring[0].arn, "") +} + +output "enhanced_monitoring_iam_role_unique_id" { + description = "Stable and unique string identifying the enhanced monitoring role" + value = try(aws_iam_role.rds_enhanced_monitoring[0].unique_id, "") +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_id" { + description = "The security group ID of the cluster" + value = try(aws_security_group.this[0].id, "") +} + +################################################################################ +# Cluster Parameter Group +################################################################################ + +output "db_cluster_parameter_group_arn" { + description = "The ARN of the DB cluster parameter group created" + value = try(aws_rds_cluster_parameter_group.this[0].arn, "") +} + +output "db_cluster_parameter_group_id" { + description = "The ID of the DB cluster parameter group created" + value = try(aws_rds_cluster_parameter_group.this[0].id, "") +} + +################################################################################ +# DB Parameter Group +################################################################################ + +output "db_parameter_group_arn" { + description = "The ARN of the DB parameter group created" + value = try(aws_db_parameter_group.this[0].arn, "") +} + +output "db_parameter_group_id" { + description = "The ID of the DB parameter group created" + value = try(aws_db_parameter_group.this[0].id, "") +} + +output "kms_key_arn" { + value = aws_kms_key.kms.arn +} \ No newline at end of file diff --git a/modules/aws-rds-aurora/variables.tf b/modules/aws-rds-aurora/variables.tf new file mode 100644 index 0000000..7101172 --- /dev/null +++ b/modules/aws-rds-aurora/variables.tf @@ -0,0 +1,713 @@ +variable "name" { + description = "Name used across resources created" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Random Password & Snapshot ID +################################################################################ + +variable "create_random_password" { + description = "Determines whether to create random password for RDS primary cluster" + type = bool + default = false +} + +variable "random_password_length" { + description = "Length of random password to create. Defaults to `10`" + type = number + default = 10 +} + +################################################################################ +# DB Subnet Group +################################################################################ + +variable "create_db_subnet_group" { + description = "Determines whether to create the database subnet group or use existing" + type = bool + default = true +} + +variable "db_subnet_group_name" { + description = "The name of the subnet group name (existing or created)" + type = string + default = "" +} + +variable "subnets" { + description = "List of subnet IDs used by database subnet group created" + type = list(string) + default = [] +} + +variable "network_type" { + description = "The type of network stack to use (IPV4 or DUAL)" + type = string + default = null +} + +variable "subnet_id_names" { + description = "name of subnet ID's" + type = string + default = "*" +} + +################################################################################ +# Cluster +################################################################################ + +variable "create_cluster" { + description = "Whether cluster should be created (affects nearly all resources)" + type = bool + default = true +} + +variable "is_primary_cluster" { + description = "Determines whether cluster is primary cluster with writer instance (set to `false` for global cluster and replica clusters)" + type = bool + default = true +} + +variable "cluster_use_name_prefix" { + description = "Whether to use `name` as a prefix for the cluster" + type = bool + default = false +} + +variable "allocated_storage" { + description = "The amount of storage in gibibytes (GiB) to allocate to each DB instance in the Multi-AZ DB cluster. (This setting is required to create a Multi-AZ DB cluster)" + type = number + default = null +} + +variable "allow_major_version_upgrade" { + description = "Enable to allow major engine version upgrades when changing engine versions. Defaults to `false`" + type = bool + default = false +} + +variable "apply_immediately" { + description = "Specifies whether any cluster modifications are applied immediately, or during the next maintenance window. Default is `false`" + type = bool + default = null +} + +variable "availability_zones" { + description = "List of EC2 Availability Zones for the DB cluster storage where DB cluster instances can be created. RDS automatically assigns 3 AZs if less than 3 AZs are configured, which will show as a difference requiring resource recreation next Terraform apply" + type = list(string) + default = null +} + +variable "backup_retention_period" { + description = "The days to retain backups for. Default `7`" + type = number + default = 7 +} + +variable "backtrack_window" { + description = "The target backtrack window, in seconds. Only available for `aurora` engine currently. To disable backtracking, set this value to 0. Must be between 0 and 259200 (72 hours)" + type = number + default = null +} + +variable "cluster_members" { + description = "List of RDS Instances that are a part of this cluster" + type = list(string) + default = null +} + +variable "copy_tags_to_snapshot" { + description = "Copy all Cluster `tags` to snapshots" + type = bool + default = null +} + +variable "database_name" { + description = "Name for an automatically created database on cluster creation" + type = string + default = null +} + +variable "db_cluster_instance_class" { + description = "The compute and memory capacity of each DB instance in the Multi-AZ DB cluster, for example db.m6g.xlarge. Not all DB instance classes are available in all AWS Regions, or for all database engines" + type = string + default = null +} + +variable "db_cluster_db_instance_parameter_group_name" { + description = "Instance parameter group to associate with all instances of the DB cluster. The `db_cluster_db_instance_parameter_group_name` is only valid in combination with `allow_major_version_upgrade`" + type = string + default = null +} + +variable "deletion_protection" { + description = "If the DB instance should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`" + type = bool + default = null +} + +variable "enable_global_write_forwarding" { + description = "Whether cluster should forward writes to an associated global cluster. Applied to secondary clusters to enable them to forward writes to an `aws_rds_global_cluster`'s primary cluster" + type = bool + default = null +} + +variable "enabled_cloudwatch_logs_exports" { + description = "Set of log types to export to cloudwatch. If omitted, no logs will be exported. The following log types are supported: `audit`, `error`, `general`, `slowquery`, `postgresql`" + type = list(string) + default = [] +} + +variable "enable_http_endpoint" { + description = "Enable HTTP endpoint (data API). Only valid when engine_mode is set to `serverless`" + type = bool + default = null +} + +variable "engine" { + description = "The name of the database engine to be used for this DB cluster. Defaults to `aurora`. Valid Values: `aurora`, `aurora-mysql`, `aurora-postgresql`" + type = string + default = null +} + +variable "engine_mode" { + description = "The database engine mode. Valid values: `global`, `multimaster`, `parallelquery`, `provisioned`, `serverless`. Defaults to: `provisioned`" + type = string + default = null +} + +variable "engine_version" { + description = "The database engine version. Updating this argument results in an outage" + type = string + default = null +} + +variable "final_snapshot_identifier_prefix" { + description = "The prefix name to use when creating a final snapshot on cluster destroy; a 8 random digits are appended to name to ensure it's unique" + type = string + default = "final" +} + +variable "global_cluster_identifier" { + description = "The global cluster identifier specified on `aws_rds_global_cluster`" + type = string + default = null +} + +variable "iam_database_authentication_enabled" { + description = "Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled" + type = bool + default = null +} + +variable "iops" { + description = "The amount of Provisioned IOPS (input/output operations per second) to be initially allocated for each DB instance in the Multi-AZ DB cluster" + type = number + default = null +} + + +variable "master_password" { + description = "Password for the master DB user. Note - when specifying a value here, 'create_random_password' should be set to `false`" + type = string + default = null +} + +variable "master_username" { + description = "Username for the master DB user" + type = string + default = "root" +} + +variable "port" { + description = "The port on which the DB accepts connections" + type = string + default = null +} + +variable "preferred_backup_window" { + description = "The daily time range during which automated backups are created if automated backups are enabled using the `backup_retention_period` parameter. Time in UTC" + type = string + default = "02:00-03:00" +} + +variable "preferred_maintenance_window" { + description = "The weekly time range during which system maintenance can occur, in (UTC)" + type = string + default = "sun:05:00-sun:06:00" +} + +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" + type = string + default = null +} + +variable "restore_to_point_in_time" { + description = "Map of nested attributes for cloning Aurora cluster" + type = map(string) + default = {} +} + +variable "s3_import" { + description = "Configuration map used to restore from a Percona Xtrabackup in S3 (only MySQL is supported)" + type = map(string) + default = {} +} + +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 "serverlessv2_scaling_configuration" { + description = "Map of nested attributes with serverless v2 scaling properties. Only valid when `engine_mode` is set to `provisioned`" + type = map(string) + default = {} +} + +variable "skip_final_snapshot" { + description = "Determines whether a final snapshot is created before the cluster is deleted. If true is specified, no snapshot is created" + type = bool + default = null +} + +variable "snapshot_identifier" { + description = "Specifies whether or not to create this cluster from a snapshot. You can use either the name or ARN when specifying a DB cluster snapshot, or the ARN when specifying a DB snapshot" + type = string + default = null +} + +variable "source_region" { + description = "The source region for an encrypted replica DB cluster" + type = string + default = null +} + +variable "storage_encrypted" { + description = "Specifies whether the DB cluster is encrypted. The default is `true`" + type = bool + default = true +} + +variable "storage_type" { + description = "Specifies the storage type to be associated with the DB cluster. (This setting is required to create a Multi-AZ DB cluster). Valid values: `io1`, Default: `io1`" + type = string + default = null +} + +variable "cluster_tags" { + description = "A map of tags to add to only the cluster. Used for AWS Instance Scheduler tagging" + type = map(string) + default = {} +} + +variable "vpc_security_group_ids" { + description = "List of VPC security groups to associate to the cluster in addition to the SG we create in this module" + type = list(string) + default = [] +} + +variable "cluster_timeouts" { + description = "Create, update, and delete timeout configurations for the cluster" + type = map(string) + default = {} +} + +################################################################################ +# Cluster Instance(s) +################################################################################ + +variable "instances" { + description = "Map of cluster instances and any specific/overriding attributes to be created" + type = any + default = {} +} + +variable "auto_minor_version_upgrade" { + description = "Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window. Default `true`" + type = bool + default = null +} + +variable "ca_cert_identifier" { + description = "The identifier of the CA certificate for the DB instance" + type = string + default = null +} + +variable "db_parameter_group_name" { + description = "The name of the DB parameter group" + type = string + default = null +} + +variable "instances_use_identifier_prefix" { + description = "Determines whether cluster instance identifiers are used as prefixes" + type = bool + default = false +} + +variable "instance_class" { + description = "Instance type to use at master instance. Note: if `autoscaling_enabled` is `true`, this will be the same instance class used on instances created by autoscaling" + type = string + default = "" +} + +variable "monitoring_interval" { + description = "The interval, in seconds, between points when Enhanced Monitoring metrics are collected for instances. Set to `0` to disable. Default is `0`" + type = number + default = 0 +} + +variable "performance_insights_enabled" { + description = "Specifies whether Performance Insights is enabled or not" + type = bool + default = null +} + +variable "performance_insights_kms_key_id" { + description = "The ARN for the KMS key to encrypt Performance Insights data" + type = string + default = null +} + +variable "performance_insights_retention_period" { + description = "Amount of time in days to retain Performance Insights data. Either 7 (7 days) or 731 (2 years)" + type = number + default = null +} + +variable "publicly_accessible" { + description = "Determines whether instances are publicly accessible. Default false" + type = bool + default = null +} + +variable "instance_timeouts" { + description = "Create, update, and delete timeout configurations for the cluster instance(s)" + type = map(string) + default = {} +} + +################################################################################ +# Cluster Endpoint(s) +################################################################################ + +variable "endpoints" { + description = "Map of additional cluster endpoints and their attributes to be created" + type = any + default = {} +} + +################################################################################ +# Cluster IAM Roles +################################################################################ + +variable "iam_roles" { + description = "Map of IAM roles and supported feature names to associate with the cluster" + type = map(map(string)) + default = {} +} + +################################################################################ +# Enhanced Monitoring +################################################################################ + +variable "create_monitoring_role" { + description = "Determines whether to create the IAM role for RDS enhanced monitoring" + type = bool + default = true +} + +variable "monitoring_role_arn" { + description = "IAM role used by RDS to send enhanced monitoring metrics to CloudWatch" + type = string + default = "" +} + +variable "iam_role_name" { + description = "Friendly name of the monitoring role" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether to use `iam_role_name` as is or create a unique name beginning with the `iam_role_name` as the prefix" + type = bool + default = false +} + +variable "iam_role_description" { + description = "Description of the monitoring role" + type = string + default = null +} + +variable "iam_role_path" { + description = "Path for the monitoring role" + type = string + default = null +} + +variable "iam_role_managed_policy_arns" { + description = "Set of exclusive IAM managed policy ARNs to attach to the monitoring role" + type = list(string) + default = null +} + +variable "iam_role_permissions_boundary" { + description = "The ARN of the policy that is used to set the permissions boundary for the monitoring role" + type = string + default = null +} + +variable "iam_role_force_detach_policies" { + description = "Whether to force detaching any policies the monitoring role has before destroying it" + type = bool + default = null +} + +variable "iam_role_max_session_duration" { + description = "Maximum session duration (in seconds) that you want to set for the monitoring role" + type = number + default = null +} + +############################# +# Amazon RDS for SQL Server # +############################# +variable "rds_custom" { + type = bool + description = "this is value to enable RDS custom" + default = false +} + +variable "rds_sql" { + type = bool + description = "this is value to enable RDS SQL" + default = false +} +variable "custom_db_paramater_group_name" { + type = string + description = "the paramater group name" + default = "" +} + +variable "multi_az" { + type = bool + default = false + description = "Multi Availibity Zone" +} + +variable "read_replica" { + type = bool + default = false + description = "Read replica for RDS" +} + +################################################################################ +# Autoscaling +################################################################################ + +variable "autoscaling_enabled" { + description = "Determines whether autoscaling of the cluster read replicas is enabled" + type = bool + default = false +} + +variable "autoscaling_max_capacity" { + description = "Maximum number of read replicas permitted when autoscaling is enabled" + type = number + default = 2 +} + +variable "autoscaling_min_capacity" { + description = "Minimum number of read replicas permitted when autoscaling is enabled" + type = number + default = 0 +} + +variable "autoscaling_policy_name" { + description = "Autoscaling policy name" + type = string + default = "target-metric" +} + +variable "predefined_metric_type" { + description = "The metric type to scale on. Valid values are `RDSReaderAverageCPUUtilization` and `RDSReaderAverageDatabaseConnections`" + type = string + default = "RDSReaderAverageCPUUtilization" +} + +variable "autoscaling_scale_in_cooldown" { + description = "Cooldown in seconds before allowing further scaling operations after a scale in" + type = number + default = 300 +} + +variable "autoscaling_scale_out_cooldown" { + description = "Cooldown in seconds before allowing further scaling operations after a scale out" + type = number + default = 300 +} + +variable "autoscaling_target_cpu" { + description = "CPU threshold which will initiate autoscaling" + type = number + default = 70 +} + +variable "autoscaling_target_connections" { + description = "Average number of connections threshold which will initiate autoscaling. Default value is 70% of db.r4/r5/r6g.large's default max_connections" + type = number + default = 700 +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines whether to create security group for RDS cluster" + type = bool + default = true +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`name`) is used as a prefix" + type = bool + default = true +} + +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 = null +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string + default = "" +} + +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 "security_group_egress_rules" { + description = "A map of security group egress rule definitions to add to the security group created" + type = map(any) + default = {} +} + +variable "security_group_tags" { + description = "Additional tags for the security group" + type = map(string) + default = {} +} + +################################################################################ +# Cluster Parameter Group +################################################################################ + +variable "create_db_cluster_parameter_group" { + description = "Determines whether a cluster parameter should be created or use existing" + type = bool + default = false +} + +variable "db_cluster_parameter_group_name" { + description = "The name of the DB cluster parameter group" + type = string + default = null +} + +variable "db_cluster_parameter_group_use_name_prefix" { + description = "Determines whether the DB cluster parameter group name is used as a prefix" + type = bool + default = true +} + +variable "db_cluster_parameter_group_description" { + description = "The description of the DB cluster parameter group. Defaults to \"Managed by Terraform\"" + type = string + default = null +} + +variable "db_cluster_parameter_group_family" { + description = "The family of the DB cluster parameter group" + type = string + default = "" +} + +variable "db_cluster_parameter_group_parameters" { + description = "A list of DB cluster parameters to apply. Note that parameters may differ from a family to an other" + type = list(map(string)) + default = [] +} + +################################################################################ +# DB Parameter Group +################################################################################ + + +variable "create_db_parameter_group" { + description = "Determines whether a DB parameter should be created or use existing" + type = bool + default = false +} + +variable "db_parameter_group_use_name_prefix" { + description = "Determines whether the DB parameter group name is used as a prefix" + type = bool + default = true +} + +variable "db_parameter_group_description" { + description = "The description of the DB parameter group. Defaults to \"Managed by Terraform\"" + type = string + default = null +} + +variable "db_parameter_group_family" { + description = "The family of the DB parameter group" + type = string + default = "" +} + +variable "db_parameter_group_parameters" { + description = "A list of DB parameters to apply. Note that parameters may differ from a family to an other" + type = list(map(string)) + default = [] +} + + +variable "kms_key_id" { + description = "The key ID of the KMS key" + default = null +} + +variable "kms_multi_region" { + description = "Determine whether the KMS key is multi region support" + type = bool + default = false +} \ No newline at end of file diff --git a/modules/aws-rds-aurora/versions.tf b/modules/aws-rds-aurora/versions.tf new file mode 100644 index 0000000..297a491 --- /dev/null +++ b/modules/aws-rds-aurora/versions.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.30" + } + + random = { + source = "hashicorp/random" + version = ">= 2.2" + } + } +} diff --git a/modules/aws-rds-global-cluster/README.md b/modules/aws-rds-global-cluster/README.md new file mode 100644 index 0000000..d109d64 --- /dev/null +++ b/modules/aws-rds-global-cluster/README.md @@ -0,0 +1,59 @@ +# terraform-aws-rds-global-cluster + +This module creates RDS Global Cluster. + +## Usage + +```hcl2 +module "rds-global-cluster" { + source = "../terraform-aws-rds-global-cluster" + + global_cluster_identifier = "global-cluster-example" + global_cluster_engine = "aurora-postgresql" + global_cluster_engine_version = "14.3" + global_cluster_database_name = "global_cluster_database" + global_cluster_storage_encrypted = true +} +``` + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_rds_global_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_global_cluster) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [global\_cluster\_database\_name](#input\_global\_cluster\_database\_name) | Global cluster database name | `string` | `""` | no | +| [global\_cluster\_delete\_protection](#input\_global\_cluster\_delete\_protection) | Global cluster delete protection flag | `bool` | `true` | no | +| [global\_cluster\_engine](#input\_global\_cluster\_engine) | Global cluster db engine | `string` | `""` | no | +| [global\_cluster\_engine\_version](#input\_global\_cluster\_engine\_version) | Global cluster db engine version | `string` | `""` | no | +| [global\_cluster\_identifier](#input\_global\_cluster\_identifier) | Global cluster identifier | `string` | `""` | no | +| [global\_cluster\_storage\_encrypted](#input\_global\_cluster\_storage\_encrypted) | Global cluster storage encrypted flag | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [database\_name](#output\_database\_name) | Global cluster database name | +| [engine](#output\_engine) | Global cluster database engine | +| [engine\_version](#output\_engine\_version) | Global cluster database engine version | +| [global\_cluster\_identifier](#output\_global\_cluster\_identifier) | Global cluster ID | + diff --git a/modules/aws-rds-global-cluster/main.tf b/modules/aws-rds-global-cluster/main.tf new file mode 100644 index 0000000..2b3ddb0 --- /dev/null +++ b/modules/aws-rds-global-cluster/main.tf @@ -0,0 +1,10 @@ +################################################################################ +# Global Cluster +################################################################################ +resource "aws_rds_global_cluster" "this" { + global_cluster_identifier = var.global_cluster_identifier + engine = var.global_cluster_engine + engine_version = var.global_cluster_engine_version + database_name = var.global_cluster_database_name + storage_encrypted = var.global_cluster_storage_encrypted +} \ No newline at end of file diff --git a/modules/aws-rds-global-cluster/outputs.tf b/modules/aws-rds-global-cluster/outputs.tf new file mode 100644 index 0000000..7265ee8 --- /dev/null +++ b/modules/aws-rds-global-cluster/outputs.tf @@ -0,0 +1,16 @@ +output "database_name" { + value = aws_rds_global_cluster.this.database_name + description = "Global cluster database name" +} +output "engine" { + value = aws_rds_global_cluster.this.engine + description = "Global cluster database engine" +} +output "engine_version" { + value = aws_rds_global_cluster.this.engine_version + description = "Global cluster database engine version" +} +output "global_cluster_identifier" { + value = aws_rds_global_cluster.this.id + description = "Global cluster ID" +} diff --git a/modules/aws-rds-global-cluster/variables.tf b/modules/aws-rds-global-cluster/variables.tf new file mode 100644 index 0000000..1e7f01c --- /dev/null +++ b/modules/aws-rds-global-cluster/variables.tf @@ -0,0 +1,32 @@ +variable "global_cluster_identifier" { + type = string + default = "" + description = "Global cluster identifier" +} +variable "global_cluster_engine" { + type = string + default = "" + description = "Global cluster db engine" +} +variable "global_cluster_engine_version" { + type = string + default = "" + description = "Global cluster db engine version" +} +variable "global_cluster_database_name" { + type = string + default = "" + description = "Global cluster database name" +} +variable "global_cluster_storage_encrypted" { + type = bool + default = true + description = "Global cluster storage encrypted flag" +} + +variable "global_cluster_delete_protection" { + type = bool + default = true + description = "Global cluster delete protection flag" +} + diff --git a/modules/aws-rds/README.md b/modules/aws-rds/README.md new file mode 100644 index 0000000..989c4a5 --- /dev/null +++ b/modules/aws-rds/README.md @@ -0,0 +1,373 @@ +# AWS RDS Terraform module + +Terraform module which creates RDS resources on AWS. +Root module calls these modules which can also be used separately to create independent resources: + +- [db_instance](./db_instance) - creates RDS DB instance +- [db_subnet_group](./db_subnet_group) - creates RDS DB subnet group +- [db_parameter_group](./db_parameter_group) - creates RDS DB parameter group +- [db_option_group](./db_option_group) - creates RDS DB option group + +## Usage + +```hcl +module "db" { + source = "terraform-aws-modules/rds/aws" + + identifier = "demodb" + + engine = "mysql" + engine_version = "5.7" + instance_class = "db.t3a.large" + allocated_storage = 5 + + db_name = "demodb" + username = "user" + port = "3306" + + iam_database_authentication_enabled = true + + vpc_security_group_ids = ["sg-12345678"] + + maintenance_window = "Mon:00:00-Mon:03:00" + backup_window = "03:00-06:00" + + # Enhanced Monitoring - see example for details on how to create the role + # by yourself, in case you don't want to create it automatically + monitoring_interval = "30" + monitoring_role_name = "MyRDSMonitoringRole" + create_monitoring_role = true + + tags = { + Owner = "user" + Environment = "dev" + } + + # DB subnet group + create_db_subnet_group = true + subnet_ids = ["subnet-12345678", "subnet-87654321"] + + # DB parameter group + family = "mysql5.7" + + # DB option group + major_engine_version = "5.7" + + # Database Deletion Protection + deletion_protection = true + + parameters = [ + { + name = "character_set_client" + value = "utf8mb4" + }, + { + name = "character_set_server" + value = "utf8mb4" + } + ] + + options = [ + { + option_name = "MARIADB_AUDIT_PLUGIN" + + option_settings = [ + { + name = "SERVER_AUDIT_EVENTS" + value = "CONNECT" + }, + { + name = "SERVER_AUDIT_FILE_ROTATIONS" + value = "37" + }, + ] + }, + ] +} +``` + +## Conditional creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "db" { + source = "terraform-aws-modules/rds/aws" + + # Disable creation of RDS instance(s) + create_db_instance = false + + # Disable creation of option group - provide an option group or default AWS default + create_db_option_group = false + + # Disable creation of parameter group - provide a parameter group or default to AWS default + create_db_parameter_group = false + + # Enable creation of subnet group (disabled by default) + create_db_subnet_group = true + + # Enable creation of monitoring IAM role + create_monitoring_role = true + + # ... omitted +} +``` + +## Option Groups + +[Reference](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithOptionGroups.html) + +Users have the ability to: + +- Create an option group with the name provided: + +```hcl + option_group_name = "prod-instance-mysql-8.0" + option_group_use_name_prefix = false +``` + +- Create an option group using a unique prefix beginning with the name provided: + +```hcl + option_group_name = "prod-instance-mysql-8.0" +``` + +- Pass the name of an option group to use that has been created outside of the module: + +```hcl + create_db_option_group = false + option_group_name = "prod-instance-mysql-8.0" # must already exist in AWS +``` + +- Skip creating an option group for PostgreSQL entirely as that is not supported + +```hcl + engine = "postgres" + option_group_name = "prod-instance-postgresql-11.0" # this will be ignored, no option group created +``` + +- Use a default option group provided by AWS + +```hcl + create_db_option_group = false +``` + +## Parameter Groups + +[Reference](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html) + +Users have the ability to: + +- Create a parameter group with the name provided: + +```hcl + parameter_group_name = "prod-instance-mysql-8.0" + parameter_group_use_name_prefix = false +``` + +- Create a parameter group using a unique prefix beginning with the name provided: + +```hcl + parameter_group_name = "prod-instance-mysql-8.0" +``` + +- Pass the name of a parameter group to use that has been created outside of the module: + +```hcl + create_db_parameter_group = false + parameter_group_name = "prod-instance-mysql-8.0" # must already exist in AWS +``` + +- Use a default parameter group provided by AWS + +```hcl + create_db_parameter_group = false +``` + +## Examples + +- [Complete RDS example for MSSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/complete-mssql) +- [Complete RDS example for MySQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/complete-mysql) +- [Complete RDS example for Oracle](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/complete-oracle) +- [Complete RDS example for PostgreSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/complete-postgres) +- [Enhanced monitoring example](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/enhanced-monitoring) +- [Replica RDS example for MySQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/replica-mysql) +- [Replica RDS example for PostgreSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/replica-postgres) +- [S3 import example for MySQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/s3-import-mysql) +- [Blue/Green Deployment example for MySQL and PostgreSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/blue-green-deployment) + +## Notes + +1. This module does not create RDS security group. Use [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group) module for this. +2. For an RDS instance with `storage_type` using `gp3`, be aware that `iops` and `storage_throughput` cannot be specified if the `allocated_storage` value is below a per-`engine` threshold. See the [RDS User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#gp3-storage) for details. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [db\_instance](#module\_db\_instance) | ./db_instance | n/a | +| [db\_instance\_role\_association](#module\_db\_instance\_role\_association) | ./db_instance_role_association | n/a | +| [db\_option\_group](#module\_db\_option\_group) | ./db_option_group | n/a | +| [db\_parameter\_group](#module\_db\_parameter\_group) | ./db_parameter_group | n/a | +| [db\_subnet\_group](#module\_db\_subnet\_group) | ./db_subnet_group | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allocated\_storage](#input\_allocated\_storage) | The allocated storage in gigabytes | `number` | `null` | no | +| [allow\_major\_version\_upgrade](#input\_allow\_major\_version\_upgrade) | Indicates that major version upgrades are allowed. Changing this parameter does not result in an outage and the change is asynchronously applied as soon as possible | `bool` | `false` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any database modifications are applied immediately, or during the next maintenance window | `bool` | `false` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window | `bool` | `true` | no | +| [availability\_zone](#input\_availability\_zone) | The Availability Zone of the RDS instance | `string` | `null` | no | +| [backup\_retention\_period](#input\_backup\_retention\_period) | The days to retain backups for | `number` | `null` | no | +| [backup\_window](#input\_backup\_window) | The daily time range (in UTC) during which automated backups are created if they are enabled. Example: '09:46-10:16'. Must not overlap with maintenance\_window | `string` | `null` | no | +| [blue\_green\_update](#input\_blue\_green\_update) | Enables low-downtime updates using RDS Blue/Green deployments. | `map(string)` | `{}` | no | +| [ca\_cert\_identifier](#input\_ca\_cert\_identifier) | Specifies the identifier of the CA certificate for the DB instance | `string` | `null` | no | +| [character\_set\_name](#input\_character\_set\_name) | The character set name to use for DB encoding in Oracle instances. This can't be changed. See Oracle Character Sets Supported in Amazon RDS and Collations and Character Sets for Microsoft SQL Server for more information. This can only be set on creation | `string` | `null` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | The number of days to retain CloudWatch logs for the DB instance | `number` | `7` | no | +| [copy\_tags\_to\_snapshot](#input\_copy\_tags\_to\_snapshot) | On delete, copy all Instance tags to the final snapshot | `bool` | `false` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a CloudWatch log group is created for each `enabled_cloudwatch_logs_exports` | `bool` | `false` | no | +| [create\_db\_instance](#input\_create\_db\_instance) | Whether to create a database instance | `bool` | `true` | no | +| [create\_db\_option\_group](#input\_create\_db\_option\_group) | Create a database option group | `bool` | `true` | no | +| [create\_db\_parameter\_group](#input\_create\_db\_parameter\_group) | Whether to create a database parameter group | `bool` | `true` | no | +| [create\_db\_subnet\_group](#input\_create\_db\_subnet\_group) | Whether to create a database subnet group | `bool` | `false` | no | +| [create\_monitoring\_role](#input\_create\_monitoring\_role) | Create IAM role with a defined name that permits RDS to send enhanced monitoring metrics to CloudWatch Logs | `bool` | `false` | no | +| [custom\_iam\_instance\_profile](#input\_custom\_iam\_instance\_profile) | RDS custom iam instance profile | `string` | `null` | no | +| [db\_instance\_role\_associations](#input\_db\_instance\_role\_associations) | A map of DB instance supported feature name to role association ARNs. | `map(any)` | `{}` | no | +| [db\_instance\_tags](#input\_db\_instance\_tags) | Additional tags for the DB instance | `map(string)` | `{}` | no | +| [db\_name](#input\_db\_name) | The DB name to create. If omitted, no database is created initially | `string` | `null` | no | +| [db\_option\_group\_tags](#input\_db\_option\_group\_tags) | Additional tags for the DB option group | `map(string)` | `{}` | no | +| [db\_parameter\_group\_tags](#input\_db\_parameter\_group\_tags) | Additional tags for the DB parameter group | `map(string)` | `{}` | no | +| [db\_subnet\_group\_description](#input\_db\_subnet\_group\_description) | Description of the DB subnet group to create | `string` | `null` | no | +| [db\_subnet\_group\_name](#input\_db\_subnet\_group\_name) | Name of DB subnet group. DB instance will be created in the VPC associated with the DB subnet group. If unspecified, will be created in the default VPC | `string` | `null` | no | +| [db\_subnet\_group\_tags](#input\_db\_subnet\_group\_tags) | Additional tags for the DB subnet group | `map(string)` | `{}` | no | +| [db\_subnet\_group\_use\_name\_prefix](#input\_db\_subnet\_group\_use\_name\_prefix) | Determines whether to use `subnet_group_name` as is or create a unique name beginning with the `subnet_group_name` as the prefix | `bool` | `true` | no | +| [delete\_automated\_backups](#input\_delete\_automated\_backups) | Specifies whether to remove automated backups immediately after the DB instance is deleted | `bool` | `true` | no | +| [deletion\_protection](#input\_deletion\_protection) | The database can't be deleted when this value is set to true | `bool` | `false` | no | +| [domain](#input\_domain) | The ID of the Directory Service Active Directory domain to create the instance in | `string` | `null` | no | +| [domain\_auth\_secret\_arn](#input\_domain\_auth\_secret\_arn) | (Optional, but required if domain\_fqdn is provided) The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [domain\_dns\_ips](#input\_domain\_dns\_ips) | (Optional, but required if domain\_fqdn is provided) The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers. Two IP addresses must be provided. If there isn't a secondary domain controller, use the IP address of the primary domain controller for both entries in the list. Conflicts with domain and domain\_iam\_role\_name. | `list(string)` | `null` | no | +| [domain\_fqdn](#input\_domain\_fqdn) | The fully qualified domain name (FQDN) of the self managed Active Directory domain. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [domain\_iam\_role\_name](#input\_domain\_iam\_role\_name) | (Required if domain is provided) The name of the IAM role to be used when making API calls to the Directory Service | `string` | `null` | no | +| [domain\_ou](#input\_domain\_ou) | (Optional, but required if domain\_fqdn is provided) The self managed Active Directory organizational unit for your DB instance to join. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [enabled\_cloudwatch\_logs\_exports](#input\_enabled\_cloudwatch\_logs\_exports) | List of log types to enable for exporting to CloudWatch logs. If omitted, no logs will be exported. Valid values (depending on engine): alert, audit, error, general, listener, slowquery, trace, postgresql (PostgreSQL), upgrade (PostgreSQL) | `list(string)` | `[]` | no | +| [engine](#input\_engine) | The database engine to use | `string` | `null` | no | +| [engine\_version](#input\_engine\_version) | The engine version to use | `string` | `null` | no | +| [family](#input\_family) | The family of the DB parameter group | `string` | `null` | no | +| [final\_snapshot\_identifier\_prefix](#input\_final\_snapshot\_identifier\_prefix) | The name which is prefixed to the final snapshot on cluster destroy | `string` | `"final"` | no | +| [iam\_database\_authentication\_enabled](#input\_iam\_database\_authentication\_enabled) | Specifies whether or not the mappings of AWS Identity and Access Management (IAM) accounts to database accounts are enabled | `bool` | `false` | no | +| [identifier](#input\_identifier) | The name of the RDS instance | `string` | n/a | yes | +| [instance\_class](#input\_instance\_class) | The instance type of the RDS instance | `string` | `null` | no | +| [instance\_use\_identifier\_prefix](#input\_instance\_use\_identifier\_prefix) | Determines whether to use `identifier` as is or create a unique identifier beginning with `identifier` as the specified prefix | `bool` | `false` | no | +| [iops](#input\_iops) | The amount of provisioned IOPS. Setting this implies a storage\_type of 'io1' or `gp3`. See `notes` for limitations regarding this variable for `gp3` | `number` | `null` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN for the KMS encryption key. If creating an encrypted replica, set this to the destination KMS ARN. If storage\_encrypted is set to true and kms\_key\_id is not specified the default KMS key created in your account will be used. Be sure to use the full ARN, not a key alias. | `string` | `null` | no | +| [license\_model](#input\_license\_model) | License model information for this DB instance. Optional, but required for some DB engines, i.e. Oracle SE1 | `string` | `null` | no | +| [maintenance\_window](#input\_maintenance\_window) | The window to perform maintenance in. Syntax: 'ddd:hh24:mi-ddd:hh24:mi'. Eg: 'Mon:00:00-Mon:03:00' | `string` | `null` | no | +| [major\_engine\_version](#input\_major\_engine\_version) | Specifies the major version of the engine that this option group should be associated with | `string` | `null` | no | +| [manage\_master\_user\_password](#input\_manage\_master\_user\_password) | Set to true to allow RDS to manage the master user password in Secrets Manager | `bool` | `true` | no | +| [manage\_master\_user\_password\_rotation](#input\_manage\_master\_user\_password\_rotation) | Whether to manage the master user password rotation. Setting this value to false after previously having been set to true will disable automatic rotation. | `bool` | `false` | no | +| [master\_user\_password\_rotate\_immediately](#input\_master\_user\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no | +| [master\_user\_password\_rotation\_automatically\_after\_days](#input\_master\_user\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either automatically\_after\_days or schedule\_expression must be specified. | `number` | `null` | no | +| [master\_user\_password\_rotation\_duration](#input\_master\_user\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no | +| [master\_user\_password\_rotation\_schedule\_expression](#input\_master\_user\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically\_after\_days or schedule\_expression must be specified. | `string` | `null` | no | +| [master\_user\_secret\_kms\_key\_id](#input\_master\_user\_secret\_kms\_key\_id) | The key ARN, key ID, alias ARN or alias name for the KMS key to encrypt the master user password secret in Secrets Manager.
If not specified, the default KMS key for your Amazon Web Services account is used. | `string` | `null` | no | +| [max\_allocated\_storage](#input\_max\_allocated\_storage) | Specifies the value for Storage Autoscaling | `number` | `0` | no | +| [monitoring\_interval](#input\_monitoring\_interval) | The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance. To disable collecting Enhanced Monitoring metrics, specify 0. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60 | `number` | `0` | no | +| [monitoring\_role\_arn](#input\_monitoring\_role\_arn) | The ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch Logs. Must be specified if monitoring\_interval is non-zero | `string` | `null` | no | +| [monitoring\_role\_description](#input\_monitoring\_role\_description) | Description of the monitoring IAM role | `string` | `null` | no | +| [monitoring\_role\_name](#input\_monitoring\_role\_name) | Name of the IAM role which will be created when create\_monitoring\_role is enabled | `string` | `"rds-monitoring-role"` | no | +| [monitoring\_role\_permissions\_boundary](#input\_monitoring\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the monitoring IAM role | `string` | `null` | no | +| [monitoring\_role\_use\_name\_prefix](#input\_monitoring\_role\_use\_name\_prefix) | Determines whether to use `monitoring_role_name` as is or create a unique identifier beginning with `monitoring_role_name` as the specified prefix | `bool` | `false` | no | +| [multi\_az](#input\_multi\_az) | Specifies if the RDS instance is multi-AZ | `bool` | `false` | no | +| [nchar\_character\_set\_name](#input\_nchar\_character\_set\_name) | The national character set is used in the NCHAR, NVARCHAR2, and NCLOB data types for Oracle instances. This can't be changed. | `string` | `null` | no | +| [network\_type](#input\_network\_type) | The type of network stack to use | `string` | `null` | no | +| [option\_group\_description](#input\_option\_group\_description) | The description of the option group | `string` | `null` | no | +| [option\_group\_name](#input\_option\_group\_name) | Name of the option group | `string` | `null` | no | +| [option\_group\_timeouts](#input\_option\_group\_timeouts) | Define maximum timeout for deletion of `aws_db_option_group` resource | `map(string)` | `{}` | no | +| [option\_group\_use\_name\_prefix](#input\_option\_group\_use\_name\_prefix) | Determines whether to use `option_group_name` as is or create a unique name beginning with the `option_group_name` as the prefix | `bool` | `true` | no | +| [options](#input\_options) | A list of Options to apply | `any` | `[]` | no | +| [parameter\_group\_description](#input\_parameter\_group\_description) | Description of the DB parameter group to create | `string` | `null` | no | +| [parameter\_group\_name](#input\_parameter\_group\_name) | Name of the DB parameter group to associate or create | `string` | `null` | no | +| [parameter\_group\_use\_name\_prefix](#input\_parameter\_group\_use\_name\_prefix) | Determines whether to use `parameter_group_name` as is or create a unique name beginning with the `parameter_group_name` as the prefix | `bool` | `true` | no | +| [parameters](#input\_parameters) | A list of DB parameters (map) to apply | `list(map(string))` | `[]` | no | +| [password](#input\_password) | Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file.
The password provided will not be used if `manage_master_user_password` is set to true. | `string` | `null` | no | +| [performance\_insights\_enabled](#input\_performance\_insights\_enabled) | Specifies whether Performance Insights are enabled | `bool` | `false` | no | +| [performance\_insights\_kms\_key\_id](#input\_performance\_insights\_kms\_key\_id) | The ARN for the KMS key to encrypt Performance Insights data | `string` | `null` | no | +| [performance\_insights\_retention\_period](#input\_performance\_insights\_retention\_period) | The amount of time in days to retain Performance Insights data. Valid values are `7`, `731` (2 years) or a multiple of `31` | `number` | `7` | no | +| [port](#input\_port) | The port on which the DB accepts connections | `string` | `null` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Bool to control if instance is publicly accessible | `bool` | `false` | no | +| [replica\_mode](#input\_replica\_mode) | Specifies whether the replica is in either mounted or open-read-only mode. This attribute is only supported by Oracle instances. Oracle replicas operate in open-read-only mode unless otherwise specified | `string` | `null` | no | +| [replicate\_source\_db](#input\_replicate\_source\_db) | Specifies that this resource is a Replicate database, and to use this value as the source database. This correlates to the identifier of another Amazon RDS Database to replicate | `string` | `null` | no | +| [restore\_to\_point\_in\_time](#input\_restore\_to\_point\_in\_time) | Restore to a point in time (MySQL is NOT supported) | `map(string)` | `null` | no | +| [s3\_import](#input\_s3\_import) | Restore from a Percona Xtrabackup in S3 (only MySQL is supported) | `map(string)` | `null` | no | +| [skip\_final\_snapshot](#input\_skip\_final\_snapshot) | Determines whether a final DB snapshot is created before the DB instance is deleted. If true is specified, no DBSnapshot is created. If false is specified, a DB snapshot is created before the DB instance is deleted | `bool` | `false` | no | +| [snapshot\_identifier](#input\_snapshot\_identifier) | Specifies whether or not to create this database from a snapshot. This correlates to the snapshot ID you'd find in the RDS console, e.g: rds:production-2015-06-26-06-05 | `string` | `null` | no | +| [storage\_encrypted](#input\_storage\_encrypted) | Specifies whether the DB instance is encrypted | `bool` | `true` | no | +| [storage\_throughput](#input\_storage\_throughput) | Storage throughput value for the DB instance. See `notes` for limitations regarding this variable for `gp3` | `number` | `null` | no | +| [storage\_type](#input\_storage\_type) | One of 'standard' (magnetic), 'gp2' (general purpose SSD), 'gp3' (new generation of general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not. If you specify 'io1' or 'gp3' , you must also include a value for the 'iops' parameter | `string` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of VPC subnet IDs | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Updated Terraform resource management timeouts. Applies to `aws_db_instance` in particular to permit resource management times | `map(string)` | `{}` | no | +| [timezone](#input\_timezone) | Time zone of the DB instance. timezone is currently only supported by Microsoft SQL Server. The timezone can only be set on creation. See MSSQL User Guide for more information | `string` | `null` | no | +| [username](#input\_username) | Username for the master DB user | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC security groups to associate | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_instance\_address](#output\_db\_instance\_address) | The address of the RDS instance | +| [db\_instance\_arn](#output\_db\_instance\_arn) | The ARN of the RDS instance | +| [db\_instance\_availability\_zone](#output\_db\_instance\_availability\_zone) | The availability zone of the RDS instance | +| [db\_instance\_ca\_cert\_identifier](#output\_db\_instance\_ca\_cert\_identifier) | Specifies the identifier of the CA certificate for the DB instance | +| [db\_instance\_cloudwatch\_log\_groups](#output\_db\_instance\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes | +| [db\_instance\_domain](#output\_db\_instance\_domain) | The ID of the Directory Service Active Directory domain the instance is joined to | +| [db\_instance\_domain\_auth\_secret\_arn](#output\_db\_instance\_domain\_auth\_secret\_arn) | The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain | +| [db\_instance\_domain\_dns\_ips](#output\_db\_instance\_domain\_dns\_ips) | The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers | +| [db\_instance\_domain\_fqdn](#output\_db\_instance\_domain\_fqdn) | The fully qualified domain name (FQDN) of an self managed Active Directory domain | +| [db\_instance\_domain\_iam\_role\_name](#output\_db\_instance\_domain\_iam\_role\_name) | The name of the IAM role to be used when making API calls to the Directory Service | +| [db\_instance\_domain\_ou](#output\_db\_instance\_domain\_ou) | The self managed Active Directory organizational unit for your DB instance to join | +| [db\_instance\_endpoint](#output\_db\_instance\_endpoint) | The connection endpoint | +| [db\_instance\_engine](#output\_db\_instance\_engine) | The database engine | +| [db\_instance\_engine\_version\_actual](#output\_db\_instance\_engine\_version\_actual) | The running version of the database | +| [db\_instance\_hosted\_zone\_id](#output\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record) | +| [db\_instance\_identifier](#output\_db\_instance\_identifier) | The RDS instance identifier | +| [db\_instance\_master\_user\_secret\_arn](#output\_db\_instance\_master\_user\_secret\_arn) | The ARN of the master user secret (Only available when manage\_master\_user\_password is set to true) | +| [db\_instance\_name](#output\_db\_instance\_name) | The database name | +| [db\_instance\_port](#output\_db\_instance\_port) | The database port | +| [db\_instance\_resource\_id](#output\_db\_instance\_resource\_id) | The RDS Resource ID of this instance | +| [db\_instance\_role\_associations](#output\_db\_instance\_role\_associations) | A map of DB Instance Identifiers and IAM Role ARNs separated by a comma | +| [db\_instance\_secretsmanager\_secret\_rotation\_enabled](#output\_db\_instance\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret | +| [db\_instance\_status](#output\_db\_instance\_status) | The RDS instance status | +| [db\_instance\_username](#output\_db\_instance\_username) | The master username for the database | +| [db\_listener\_endpoint](#output\_db\_listener\_endpoint) | Specifies the listener connection endpoint for SQL Server Always On | +| [db\_option\_group\_arn](#output\_db\_option\_group\_arn) | The ARN of the db option group | +| [db\_option\_group\_id](#output\_db\_option\_group\_id) | The db option group id | +| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the db parameter group | +| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The db parameter group id | +| [db\_subnet\_group\_arn](#output\_db\_subnet\_group\_arn) | The ARN of the db subnet group | +| [db\_subnet\_group\_id](#output\_db\_subnet\_group\_id) | The db subnet group name | +| [enhanced\_monitoring\_iam\_role\_arn](#output\_enhanced\_monitoring\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the monitoring role | +| [enhanced\_monitoring\_iam\_role\_name](#output\_enhanced\_monitoring\_iam\_role\_name) | The name of the monitoring role | + + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/LICENSE) for full details. diff --git a/modules/aws-rds/db_instance/README.md b/modules/aws-rds/db_instance/README.md new file mode 100644 index 0000000..0edf130 --- /dev/null +++ b/modules/aws-rds/db_instance/README.md @@ -0,0 +1,149 @@ +# aws_db_instance + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.36 | +| [random](#requirement\_random) | >= 3.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.36 | +| [random](#provider\_random) | >= 3.1 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_db_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance) | resource | +| [aws_iam_role.enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_secretsmanager_secret_rotation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation) | resource | +| [random_id.snapshot_identifier](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [aws_iam_policy_document.enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allocated\_storage](#input\_allocated\_storage) | The allocated storage in gigabytes | `number` | `null` | no | +| [allow\_major\_version\_upgrade](#input\_allow\_major\_version\_upgrade) | Indicates that major version upgrades are allowed. Changing this parameter does not result in an outage and the change is asynchronously applied as soon as possible | `bool` | `false` | no | +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any database modifications are applied immediately, or during the next maintenance window | `bool` | `false` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window | `bool` | `true` | no | +| [availability\_zone](#input\_availability\_zone) | The Availability Zone of the RDS instance | `string` | `null` | no | +| [backup\_retention\_period](#input\_backup\_retention\_period) | The days to retain backups for | `number` | `null` | no | +| [backup\_window](#input\_backup\_window) | The daily time range (in UTC) during which automated backups are created if they are enabled. Example: '09:46-10:16'. Must not overlap with maintenance\_window | `string` | `null` | no | +| [blue\_green\_update](#input\_blue\_green\_update) | Enables low-downtime updates using RDS Blue/Green deployments. | `map(string)` | `{}` | no | +| [ca\_cert\_identifier](#input\_ca\_cert\_identifier) | Specifies the identifier of the CA certificate for the DB instance | `string` | `null` | no | +| [character\_set\_name](#input\_character\_set\_name) | The character set name to use for DB encoding in Oracle instances. This can't be changed. See Oracle Character Sets Supported in Amazon RDS and Collations and Character Sets for Microsoft SQL Server for more information. This can only be set on creation. | `string` | `null` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | The number of days to retain CloudWatch logs for the DB instance | `number` | `7` | no | +| [copy\_tags\_to\_snapshot](#input\_copy\_tags\_to\_snapshot) | On delete, copy all Instance tags to the final snapshot | `bool` | `false` | no | +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a CloudWatch log group is created for each `enabled_cloudwatch_logs_exports` | `bool` | `false` | no | +| [create\_monitoring\_role](#input\_create\_monitoring\_role) | Create IAM role with a defined name that permits RDS to send enhanced monitoring metrics to CloudWatch Logs. | `bool` | `false` | no | +| [custom\_iam\_instance\_profile](#input\_custom\_iam\_instance\_profile) | RDS custom iam instance profile | `string` | `null` | no | +| [db\_name](#input\_db\_name) | The DB name to create. If omitted, no database is created initially | `string` | `null` | no | +| [db\_subnet\_group\_name](#input\_db\_subnet\_group\_name) | Name of DB subnet group. DB instance will be created in the VPC associated with the DB subnet group. If unspecified, will be created in the default VPC | `string` | `null` | no | +| [delete\_automated\_backups](#input\_delete\_automated\_backups) | Specifies whether to remove automated backups immediately after the DB instance is deleted | `bool` | `true` | no | +| [deletion\_protection](#input\_deletion\_protection) | The database can't be deleted when this value is set to true. | `bool` | `false` | no | +| [domain](#input\_domain) | The ID of the Directory Service Active Directory domain to create the instance in | `string` | `null` | no | +| [domain\_auth\_secret\_arn](#input\_domain\_auth\_secret\_arn) | (Optional, but required if domain\_fqdn is provided) The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [domain\_dns\_ips](#input\_domain\_dns\_ips) | (Optional, but required if domain\_fqdn is provided) The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers. Two IP addresses must be provided. If there isn't a secondary domain controller, use the IP address of the primary domain controller for both entries in the list. Conflicts with domain and domain\_iam\_role\_name. | `list(string)` | `null` | no | +| [domain\_fqdn](#input\_domain\_fqdn) | The fully qualified domain name (FQDN) of the self managed Active Directory domain. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [domain\_iam\_role\_name](#input\_domain\_iam\_role\_name) | (Required if domain is provided) The name of the IAM role to be used when making API calls to the Directory Service | `string` | `null` | no | +| [domain\_ou](#input\_domain\_ou) | (Optional, but required if domain\_fqdn is provided) The self managed Active Directory organizational unit for your DB instance to join. Conflicts with domain and domain\_iam\_role\_name. | `string` | `null` | no | +| [enabled\_cloudwatch\_logs\_exports](#input\_enabled\_cloudwatch\_logs\_exports) | List of log types to enable for exporting to CloudWatch logs. If omitted, no logs will be exported. Valid values (depending on engine): alert, audit, error, general, listener, slowquery, trace, postgresql (PostgreSQL), upgrade (PostgreSQL). | `list(string)` | `[]` | no | +| [engine](#input\_engine) | The database engine to use | `string` | `null` | no | +| [engine\_version](#input\_engine\_version) | The engine version to use | `string` | `null` | no | +| [final\_snapshot\_identifier\_prefix](#input\_final\_snapshot\_identifier\_prefix) | The name which is prefixed to the final snapshot on cluster destroy | `string` | `"final"` | no | +| [iam\_database\_authentication\_enabled](#input\_iam\_database\_authentication\_enabled) | Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled | `bool` | `false` | no | +| [identifier](#input\_identifier) | The name of the RDS instance | `string` | n/a | yes | +| [instance\_class](#input\_instance\_class) | The instance type of the RDS instance | `string` | `null` | no | +| [iops](#input\_iops) | The amount of provisioned IOPS. Setting this implies a storage\_type of 'io1' or `gp3`. See `notes` for limitations regarding this variable for `gp3` | `number` | `null` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN for the KMS encryption key. If creating an encrypted replica, set this to the destination KMS ARN. If storage\_encrypted is set to true and kms\_key\_id is not specified the default KMS key created in your account will be used | `string` | `null` | no | +| [license\_model](#input\_license\_model) | License model information for this DB instance. Optional, but required for some DB engines, i.e. Oracle SE1 | `string` | `null` | no | +| [maintenance\_window](#input\_maintenance\_window) | The window to perform maintenance in. Syntax: 'ddd:hh24:mi-ddd:hh24:mi'. Eg: 'Mon:00:00-Mon:03:00' | `string` | `null` | no | +| [manage\_master\_user\_password](#input\_manage\_master\_user\_password) | Set to true to allow RDS to manage the master user password in Secrets Manager. Cannot be set if password is provided | `bool` | `true` | no | +| [manage\_master\_user\_password\_rotation](#input\_manage\_master\_user\_password\_rotation) | Whether to manage the master user password rotation. Setting this value to false after previously having been set to true will disable automatic rotation. | `bool` | `false` | no | +| [master\_user\_password\_rotate\_immediately](#input\_master\_user\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no | +| [master\_user\_password\_rotation\_automatically\_after\_days](#input\_master\_user\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either automatically\_after\_days or schedule\_expression must be specified. | `number` | `null` | no | +| [master\_user\_password\_rotation\_duration](#input\_master\_user\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no | +| [master\_user\_password\_rotation\_schedule\_expression](#input\_master\_user\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically\_after\_days or schedule\_expression must be specified. | `string` | `null` | no | +| [master\_user\_secret\_kms\_key\_id](#input\_master\_user\_secret\_kms\_key\_id) | The key ARN, key ID, alias ARN or alias name for the KMS key to encrypt the master user password secret in Secrets Manager.
If not specified, the default KMS key for your Amazon Web Services account is used. | `string` | `null` | no | +| [max\_allocated\_storage](#input\_max\_allocated\_storage) | Specifies the value for Storage Autoscaling | `number` | `0` | no | +| [monitoring\_interval](#input\_monitoring\_interval) | The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance. To disable collecting Enhanced Monitoring metrics, specify 0. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60. | `number` | `0` | no | +| [monitoring\_role\_arn](#input\_monitoring\_role\_arn) | The ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch Logs. Must be specified if monitoring\_interval is non-zero. | `string` | `null` | no | +| [monitoring\_role\_description](#input\_monitoring\_role\_description) | Description of the monitoring IAM role | `string` | `null` | no | +| [monitoring\_role\_name](#input\_monitoring\_role\_name) | Name of the IAM role which will be created when create\_monitoring\_role is enabled. | `string` | `"rds-monitoring-role"` | no | +| [monitoring\_role\_permissions\_boundary](#input\_monitoring\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the monitoring IAM role | `string` | `null` | no | +| [monitoring\_role\_use\_name\_prefix](#input\_monitoring\_role\_use\_name\_prefix) | Determines whether to use `monitoring_role_name` as is or create a unique identifier beginning with `monitoring_role_name` as the specified prefix | `bool` | `false` | no | +| [multi\_az](#input\_multi\_az) | Specifies if the RDS instance is multi-AZ | `bool` | `false` | no | +| [nchar\_character\_set\_name](#input\_nchar\_character\_set\_name) | The national character set is used in the NCHAR, NVARCHAR2, and NCLOB data types for Oracle instances. This can't be changed. | `string` | `null` | no | +| [network\_type](#input\_network\_type) | The type of network stack | `string` | `null` | no | +| [option\_group\_name](#input\_option\_group\_name) | Name of the DB option group to associate. | `string` | `null` | no | +| [parameter\_group\_name](#input\_parameter\_group\_name) | Name of the DB parameter group to associate | `string` | `null` | no | +| [password](#input\_password) | Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file | `string` | `null` | no | +| [performance\_insights\_enabled](#input\_performance\_insights\_enabled) | Specifies whether Performance Insights are enabled | `bool` | `false` | no | +| [performance\_insights\_kms\_key\_id](#input\_performance\_insights\_kms\_key\_id) | The ARN for the KMS key to encrypt Performance Insights data. | `string` | `null` | no | +| [performance\_insights\_retention\_period](#input\_performance\_insights\_retention\_period) | The amount of time in days to retain Performance Insights data. Either 7 (7 days) or 731 (2 years). | `number` | `7` | no | +| [port](#input\_port) | The port on which the DB accepts connections | `string` | `null` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Bool to control if instance is publicly accessible | `bool` | `false` | no | +| [replica\_mode](#input\_replica\_mode) | Specifies whether the replica is in either mounted or open-read-only mode. This attribute is only supported by Oracle instances. Oracle replicas operate in open-read-only mode unless otherwise specified | `string` | `null` | no | +| [replicate\_source\_db](#input\_replicate\_source\_db) | Specifies that this resource is a Replicate database, and to use this value as the source database. This correlates to the identifier of another Amazon RDS Database to replicate. | `string` | `null` | no | +| [restore\_to\_point\_in\_time](#input\_restore\_to\_point\_in\_time) | Restore to a point in time (MySQL is NOT supported) | `map(string)` | `null` | no | +| [s3\_import](#input\_s3\_import) | Restore from a Percona Xtrabackup in S3 (only MySQL is supported) | `map(string)` | `null` | no | +| [skip\_final\_snapshot](#input\_skip\_final\_snapshot) | Determines whether a final DB snapshot is created before the DB instance is deleted. If true is specified, no DBSnapshot is created. If false is specified, a DB snapshot is created before the DB instance is deleted | `bool` | `false` | no | +| [snapshot\_identifier](#input\_snapshot\_identifier) | Specifies whether or not to create this database from a snapshot. This correlates to the snapshot ID you'd find in the RDS console, e.g: rds:production-2015-06-26-06-05. | `string` | `null` | no | +| [storage\_encrypted](#input\_storage\_encrypted) | Specifies whether the DB instance is encrypted | `bool` | `true` | no | +| [storage\_throughput](#input\_storage\_throughput) | Storage throughput value for the DB instance. This setting applies only to the `gp3` storage type. See `notes` for limitations regarding this variable for `gp3` | `number` | `null` | no | +| [storage\_type](#input\_storage\_type) | One of 'standard' (magnetic), 'gp2' (general purpose SSD), 'gp3' (new generation of general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not. If you specify 'io1' or 'gp3' , you must also include a value for the 'iops' parameter | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Updated Terraform resource management timeouts. Applies to `aws_db_instance` in particular to permit resource management times | `map(string)` | `{}` | no | +| [timezone](#input\_timezone) | Time zone of the DB instance. timezone is currently only supported by Microsoft SQL Server. The timezone can only be set on creation. See MSSQL User Guide for more information. | `string` | `null` | no | +| [use\_identifier\_prefix](#input\_use\_identifier\_prefix) | Determines whether to use `identifier` as is or create a unique identifier beginning with `identifier` as the specified prefix | `bool` | `false` | no | +| [username](#input\_username) | Username for the master DB user | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC security groups to associate | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_instance\_address](#output\_db\_instance\_address) | The address of the RDS instance | +| [db\_instance\_arn](#output\_db\_instance\_arn) | The ARN of the RDS instance | +| [db\_instance\_availability\_zone](#output\_db\_instance\_availability\_zone) | The availability zone of the RDS instance | +| [db\_instance\_ca\_cert\_identifier](#output\_db\_instance\_ca\_cert\_identifier) | Specifies the identifier of the CA certificate for the DB instance | +| [db\_instance\_cloudwatch\_log\_groups](#output\_db\_instance\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes | +| [db\_instance\_domain](#output\_db\_instance\_domain) | The ID of the Directory Service Active Directory domain the instance is joined to | +| [db\_instance\_domain\_auth\_secret\_arn](#output\_db\_instance\_domain\_auth\_secret\_arn) | The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain | +| [db\_instance\_domain\_dns\_ips](#output\_db\_instance\_domain\_dns\_ips) | The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers | +| [db\_instance\_domain\_fqdn](#output\_db\_instance\_domain\_fqdn) | The fully qualified domain name (FQDN) of an self managed Active Directory domain | +| [db\_instance\_domain\_iam\_role\_name](#output\_db\_instance\_domain\_iam\_role\_name) | The name of the IAM role to be used when making API calls to the Directory Service | +| [db\_instance\_domain\_ou](#output\_db\_instance\_domain\_ou) | The self managed Active Directory organizational unit for your DB instance to join | +| [db\_instance\_endpoint](#output\_db\_instance\_endpoint) | The connection endpoint | +| [db\_instance\_engine](#output\_db\_instance\_engine) | The database engine | +| [db\_instance\_engine\_version\_actual](#output\_db\_instance\_engine\_version\_actual) | The running version of the database | +| [db\_instance\_hosted\_zone\_id](#output\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record) | +| [db\_instance\_identifier](#output\_db\_instance\_identifier) | The RDS instance identifier | +| [db\_instance\_master\_user\_secret\_arn](#output\_db\_instance\_master\_user\_secret\_arn) | The ARN of the master user secret (Only available when manage\_master\_user\_password is set to true) | +| [db\_instance\_name](#output\_db\_instance\_name) | The database name | +| [db\_instance\_port](#output\_db\_instance\_port) | The database port | +| [db\_instance\_resource\_id](#output\_db\_instance\_resource\_id) | The RDS Resource ID of this instance | +| [db\_instance\_secretsmanager\_secret\_rotation\_enabled](#output\_db\_instance\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret | +| [db\_instance\_status](#output\_db\_instance\_status) | The RDS instance status | +| [db\_instance\_username](#output\_db\_instance\_username) | The master username for the database | +| [db\_listener\_endpoint](#output\_db\_listener\_endpoint) | Specifies the listener connection endpoint for SQL Server Always On | +| [enhanced\_monitoring\_iam\_role\_arn](#output\_enhanced\_monitoring\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the monitoring role | +| [enhanced\_monitoring\_iam\_role\_name](#output\_enhanced\_monitoring\_iam\_role\_name) | The name of the monitoring role | + diff --git a/modules/aws-rds/db_instance/main.tf b/modules/aws-rds/db_instance/main.tf new file mode 100644 index 0000000..325d82c --- /dev/null +++ b/modules/aws-rds/db_instance/main.tf @@ -0,0 +1,220 @@ +locals { + monitoring_role_arn = var.create_monitoring_role ? aws_iam_role.enhanced_monitoring[0].arn : var.monitoring_role_arn + + final_snapshot_identifier = var.skip_final_snapshot ? null : "${var.final_snapshot_identifier_prefix}-${var.identifier}-${try(random_id.snapshot_identifier[0].hex, "")}" + + identifier = var.use_identifier_prefix ? null : var.identifier + identifier_prefix = var.use_identifier_prefix ? "${var.identifier}-" : null + + monitoring_role_name = var.monitoring_role_use_name_prefix ? null : var.monitoring_role_name + monitoring_role_name_prefix = var.monitoring_role_use_name_prefix ? "${var.monitoring_role_name}-" : null + + # Replicas will use source metadata + is_replica = var.replicate_source_db != null +} + +# Ref. https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces +data "aws_partition" "current" {} + +resource "random_id" "snapshot_identifier" { + count = var.create && !var.skip_final_snapshot ? 1 : 0 + + keepers = { + id = var.identifier + } + + byte_length = 4 +} + +resource "aws_db_instance" "this" { + count = var.create ? 1 : 0 + + identifier = local.identifier + identifier_prefix = local.identifier_prefix + + engine = local.is_replica ? null : var.engine + engine_version = var.engine_version + instance_class = var.instance_class + allocated_storage = var.allocated_storage + storage_type = var.storage_type + storage_encrypted = var.storage_encrypted + kms_key_id = var.kms_key_id + license_model = var.license_model + + db_name = var.db_name + username = !local.is_replica ? var.username : null + password = !local.is_replica && var.manage_master_user_password ? null : var.password + port = var.port + domain = var.domain + domain_auth_secret_arn = var.domain_auth_secret_arn + domain_dns_ips = var.domain_dns_ips + domain_fqdn = var.domain_fqdn + domain_iam_role_name = var.domain_iam_role_name + domain_ou = var.domain_ou + iam_database_authentication_enabled = var.iam_database_authentication_enabled + custom_iam_instance_profile = var.custom_iam_instance_profile + manage_master_user_password = !local.is_replica && var.manage_master_user_password ? var.manage_master_user_password : null + master_user_secret_kms_key_id = !local.is_replica && var.manage_master_user_password ? var.master_user_secret_kms_key_id : null + + vpc_security_group_ids = var.vpc_security_group_ids + db_subnet_group_name = var.db_subnet_group_name + parameter_group_name = var.parameter_group_name + option_group_name = var.option_group_name + network_type = var.network_type + + availability_zone = var.availability_zone + multi_az = var.multi_az + iops = var.iops + storage_throughput = var.storage_throughput + publicly_accessible = var.publicly_accessible + ca_cert_identifier = var.ca_cert_identifier + + allow_major_version_upgrade = var.allow_major_version_upgrade + auto_minor_version_upgrade = var.auto_minor_version_upgrade + apply_immediately = var.apply_immediately + maintenance_window = var.maintenance_window + + # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-green-deployments.html + dynamic "blue_green_update" { + for_each = length(var.blue_green_update) > 0 ? [var.blue_green_update] : [] + + content { + enabled = try(blue_green_update.value.enabled, null) + } + } + + snapshot_identifier = var.snapshot_identifier + copy_tags_to_snapshot = var.copy_tags_to_snapshot + skip_final_snapshot = var.skip_final_snapshot + final_snapshot_identifier = local.final_snapshot_identifier + + performance_insights_enabled = var.performance_insights_enabled + performance_insights_retention_period = var.performance_insights_enabled ? var.performance_insights_retention_period : null + performance_insights_kms_key_id = var.performance_insights_enabled ? var.performance_insights_kms_key_id : null + + replicate_source_db = var.replicate_source_db + replica_mode = var.replica_mode + backup_retention_period = length(var.blue_green_update) > 0 ? coalesce(var.backup_retention_period, 1) : var.backup_retention_period + backup_window = var.backup_window + max_allocated_storage = var.max_allocated_storage + monitoring_interval = var.monitoring_interval + monitoring_role_arn = var.monitoring_interval > 0 ? local.monitoring_role_arn : null + + character_set_name = var.character_set_name + nchar_character_set_name = var.nchar_character_set_name + timezone = var.timezone + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports + + deletion_protection = var.deletion_protection + delete_automated_backups = var.delete_automated_backups + + dynamic "restore_to_point_in_time" { + for_each = var.restore_to_point_in_time != null ? [var.restore_to_point_in_time] : [] + + content { + restore_time = lookup(restore_to_point_in_time.value, "restore_time", null) + source_db_instance_automated_backups_arn = lookup(restore_to_point_in_time.value, "source_db_instance_automated_backups_arn", null) + source_db_instance_identifier = lookup(restore_to_point_in_time.value, "source_db_instance_identifier", null) + source_dbi_resource_id = lookup(restore_to_point_in_time.value, "source_dbi_resource_id", null) + use_latest_restorable_time = lookup(restore_to_point_in_time.value, "use_latest_restorable_time", null) + } + } + + dynamic "s3_import" { + for_each = var.s3_import != null ? [var.s3_import] : [] + + content { + source_engine = "mysql" + source_engine_version = s3_import.value.source_engine_version + bucket_name = s3_import.value.bucket_name + bucket_prefix = lookup(s3_import.value, "bucket_prefix", null) + ingestion_role = s3_import.value.ingestion_role + } + } + + tags = var.tags + + depends_on = [aws_cloudwatch_log_group.this] + + timeouts { + create = lookup(var.timeouts, "create", null) + delete = lookup(var.timeouts, "delete", null) + update = lookup(var.timeouts, "update", null) + } + + # Note: do not add `latest_restorable_time` to `ignore_changes` + # https://github.com/terraform-aws-modules/terraform-aws-rds/issues/478 +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +# Log groups will not be created if using an identifier prefix +resource "aws_cloudwatch_log_group" "this" { + for_each = toset([for log in var.enabled_cloudwatch_logs_exports : log if var.create && var.create_cloudwatch_log_group && !var.use_identifier_prefix]) + + name = "/aws/rds/instance/${var.identifier}/${each.value}" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = var.tags +} + +################################################################################ +# Enhanced monitoring +################################################################################ + +data "aws_iam_policy_document" "enhanced_monitoring" { + statement { + actions = [ + "sts:AssumeRole", + ] + + principals { + type = "Service" + identifiers = ["monitoring.rds.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "enhanced_monitoring" { + count = var.create_monitoring_role ? 1 : 0 + + name = local.monitoring_role_name + name_prefix = local.monitoring_role_name_prefix + assume_role_policy = data.aws_iam_policy_document.enhanced_monitoring.json + description = var.monitoring_role_description + permissions_boundary = var.monitoring_role_permissions_boundary + + tags = merge( + { + "Name" = format("%s", var.monitoring_role_name) + }, + var.tags, + ) +} + +resource "aws_iam_role_policy_attachment" "enhanced_monitoring" { + count = var.create_monitoring_role ? 1 : 0 + + role = aws_iam_role.enhanced_monitoring[0].name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" +} + +################################################################################ +# Managed Secret Rotation +################################################################################ + +resource "aws_secretsmanager_secret_rotation" "this" { + count = var.create && var.manage_master_user_password && var.manage_master_user_password_rotation ? 1 : 0 + + secret_id = aws_db_instance.this[0].master_user_secret[0].secret_arn + rotate_immediately = var.master_user_password_rotate_immediately + + rotation_rules { + automatically_after_days = var.master_user_password_rotation_automatically_after_days + duration = var.master_user_password_rotation_duration + schedule_expression = var.master_user_password_rotation_schedule_expression + } +} diff --git a/modules/aws-rds/db_instance/outputs.tf b/modules/aws-rds/db_instance/outputs.tf new file mode 100644 index 0000000..26c4cdb --- /dev/null +++ b/modules/aws-rds/db_instance/outputs.tf @@ -0,0 +1,138 @@ +output "enhanced_monitoring_iam_role_name" { + description = "The name of the monitoring role" + value = try(aws_iam_role.enhanced_monitoring[0].name, null) +} + +output "enhanced_monitoring_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the monitoring role" + value = try(aws_iam_role.enhanced_monitoring[0].arn, null) +} + +output "db_instance_address" { + description = "The address of the RDS instance" + value = try(aws_db_instance.this[0].address, null) +} + +output "db_instance_arn" { + description = "The ARN of the RDS instance" + value = try(aws_db_instance.this[0].arn, null) +} + +output "db_instance_availability_zone" { + description = "The availability zone of the RDS instance" + value = try(aws_db_instance.this[0].availability_zone, null) +} + +output "db_instance_endpoint" { + description = "The connection endpoint" + value = try(aws_db_instance.this[0].endpoint, null) +} + +output "db_listener_endpoint" { + description = "Specifies the listener connection endpoint for SQL Server Always On" + value = try(aws_db_instance.this[0].listener_endpoint, null) +} + +output "db_instance_engine" { + description = "The database engine" + value = try(aws_db_instance.this[0].engine, null) +} + +output "db_instance_engine_version_actual" { + description = "The running version of the database" + value = try(aws_db_instance.this[0].engine_version_actual, null) +} + +output "db_instance_hosted_zone_id" { + description = "The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record)" + value = try(aws_db_instance.this[0].hosted_zone_id, null) +} + +output "db_instance_identifier" { + description = "The RDS instance identifier" + value = try(aws_db_instance.this[0].identifier, null) +} + +output "db_instance_resource_id" { + description = "The RDS Resource ID of this instance" + value = try(aws_db_instance.this[0].resource_id, null) +} + +output "db_instance_status" { + description = "The RDS instance status" + value = try(aws_db_instance.this[0].status, null) +} + +output "db_instance_name" { + description = "The database name" + value = try(aws_db_instance.this[0].db_name, null) +} + +output "db_instance_username" { + description = "The master username for the database" + value = try(aws_db_instance.this[0].username, null) + sensitive = true +} + +output "db_instance_port" { + description = "The database port" + value = try(aws_db_instance.this[0].port, null) +} + +output "db_instance_ca_cert_identifier" { + description = "Specifies the identifier of the CA certificate for the DB instance" + value = try(aws_db_instance.this[0].ca_cert_identifier, null) +} + +output "db_instance_domain" { + description = "The ID of the Directory Service Active Directory domain the instance is joined to" + value = try(aws_db_instance.this[0].domain, null) +} + +output "db_instance_domain_auth_secret_arn" { + description = "The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain" + value = try(aws_db_instance.this[0].domain_auth_secret_arn, null) +} + +output "db_instance_domain_dns_ips" { + description = "The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers" + value = try(aws_db_instance.this[0].domain_dns_ips, null) +} + +output "db_instance_domain_fqdn" { + description = "The fully qualified domain name (FQDN) of an self managed Active Directory domain" + value = try(aws_db_instance.this[0].domain_fqdn, null) +} + +output "db_instance_domain_iam_role_name" { + description = "The name of the IAM role to be used when making API calls to the Directory Service" + value = try(aws_db_instance.this[0].domain_iam_role_name, null) +} + +output "db_instance_domain_ou" { + description = "The self managed Active Directory organizational unit for your DB instance to join" + value = try(aws_db_instance.this[0].domain_ou, null) +} + +output "db_instance_master_user_secret_arn" { + description = "The ARN of the master user secret (Only available when manage_master_user_password is set to true)" + value = try(aws_db_instance.this[0].master_user_secret[0].secret_arn, null) +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "db_instance_cloudwatch_log_groups" { + description = "Map of CloudWatch log groups created and their attributes" + value = aws_cloudwatch_log_group.this +} + +################################################################################ +# Managed Secret Rotation +################################################################################ + +output "db_instance_secretsmanager_secret_rotation_enabled" { + description = "Specifies whether automatic rotation is enabled for the secret" + value = try(aws_secretsmanager_secret_rotation.this[0].rotation_enabled, null) +} diff --git a/modules/aws-rds/db_instance/variables.tf b/modules/aws-rds/db_instance/variables.tf new file mode 100644 index 0000000..372d452 --- /dev/null +++ b/modules/aws-rds/db_instance/variables.tf @@ -0,0 +1,477 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "identifier" { + description = "The name of the RDS instance" + type = string +} +variable "custom_iam_instance_profile" { + description = "RDS custom iam instance profile" + type = string + default = null +} + +variable "use_identifier_prefix" { + description = "Determines whether to use `identifier` as is or create a unique identifier beginning with `identifier` as the specified prefix" + type = bool + default = false +} + +variable "allocated_storage" { + description = "The allocated storage in gigabytes" + type = number + default = null +} + +variable "storage_type" { + description = "One of 'standard' (magnetic), 'gp2' (general purpose SSD), 'gp3' (new generation of general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not. If you specify 'io1' or 'gp3' , you must also include a value for the 'iops' parameter" + type = string + default = null +} + +variable "storage_throughput" { + description = "Storage throughput value for the DB instance. This setting applies only to the `gp3` storage type. See `notes` for limitations regarding this variable for `gp3`" + type = number + default = null +} + +variable "storage_encrypted" { + description = "Specifies whether the DB instance is encrypted" + type = bool + default = true +} + +variable "kms_key_id" { + description = "The ARN for the KMS encryption key. If creating an encrypted replica, set this to the destination KMS ARN. If storage_encrypted is set to true and kms_key_id is not specified the default KMS key created in your account will be used" + type = string + default = null +} + +variable "replicate_source_db" { + description = "Specifies that this resource is a Replicate database, and to use this value as the source database. This correlates to the identifier of another Amazon RDS Database to replicate." + type = string + default = null +} + +variable "license_model" { + description = "License model information for this DB instance. Optional, but required for some DB engines, i.e. Oracle SE1" + type = string + default = null +} + +variable "replica_mode" { + description = "Specifies whether the replica is in either mounted or open-read-only mode. This attribute is only supported by Oracle instances. Oracle replicas operate in open-read-only mode unless otherwise specified" + type = string + default = null +} + +variable "iam_database_authentication_enabled" { + description = "Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled" + type = bool + default = false +} + +variable "domain" { + description = "The ID of the Directory Service Active Directory domain to create the instance in" + type = string + default = null +} + +variable "domain_auth_secret_arn" { + description = "(Optional, but required if domain_fqdn is provided) The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "domain_dns_ips" { + description = "(Optional, but required if domain_fqdn is provided) The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers. Two IP addresses must be provided. If there isn't a secondary domain controller, use the IP address of the primary domain controller for both entries in the list. Conflicts with domain and domain_iam_role_name." + type = list(string) + default = null +} + +variable "domain_fqdn" { + description = "The fully qualified domain name (FQDN) of the self managed Active Directory domain. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "domain_iam_role_name" { + description = "(Required if domain is provided) The name of the IAM role to be used when making API calls to the Directory Service" + type = string + default = null +} + +variable "domain_ou" { + description = "(Optional, but required if domain_fqdn is provided) The self managed Active Directory organizational unit for your DB instance to join. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "engine" { + description = "The database engine to use" + type = string + default = null +} + +variable "engine_version" { + description = "The engine version to use" + type = string + default = null +} + +variable "instance_class" { + description = "The instance type of the RDS instance" + type = string + default = null +} + +variable "db_name" { + description = "The DB name to create. If omitted, no database is created initially" + type = string + default = null +} + +variable "username" { + description = "Username for the master DB user" + type = string + default = null +} + +variable "password" { + description = "Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file" + type = string + default = null +} + +variable "manage_master_user_password" { + description = "Set to true to allow RDS to manage the master user password in Secrets Manager. Cannot be set if password is provided" + type = bool + default = true +} + +variable "master_user_secret_kms_key_id" { + description = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.36 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_instance_role_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance_role_association) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Determines whether to create a DB instance role association | `bool` | `true` | no | +| [db\_instance\_identifier](#input\_db\_instance\_identifier) | The database instance identifier to associate the role | `string` | `null` | no | +| [feature\_name](#input\_feature\_name) | Name of the feature for association | `string` | `null` | no | +| [role\_arn](#input\_role\_arn) | Amazon Resource Name (ARN) of the IAM Role to associate with the DB Instance | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_instance\_role\_association\_id](#output\_db\_instance\_role\_association\_id) | DB Instance Identifier and IAM Role ARN separated by a comma | + diff --git a/modules/aws-rds/db_instance_role_association/main.tf b/modules/aws-rds/db_instance_role_association/main.tf new file mode 100644 index 0000000..0a11a4c --- /dev/null +++ b/modules/aws-rds/db_instance_role_association/main.tf @@ -0,0 +1,7 @@ +resource "aws_db_instance_role_association" "this" { + count = var.create ? 1 : 0 + + db_instance_identifier = var.db_instance_identifier + feature_name = var.feature_name + role_arn = var.role_arn +} diff --git a/modules/aws-rds/db_instance_role_association/outputs.tf b/modules/aws-rds/db_instance_role_association/outputs.tf new file mode 100644 index 0000000..9152a0c --- /dev/null +++ b/modules/aws-rds/db_instance_role_association/outputs.tf @@ -0,0 +1,4 @@ +output "db_instance_role_association_id" { + description = "DB Instance Identifier and IAM Role ARN separated by a comma" + value = try(aws_db_instance_role_association.this[0].id, "") +} diff --git a/modules/aws-rds/db_instance_role_association/variables.tf b/modules/aws-rds/db_instance_role_association/variables.tf new file mode 100644 index 0000000..d548d7f --- /dev/null +++ b/modules/aws-rds/db_instance_role_association/variables.tf @@ -0,0 +1,23 @@ +variable "create" { + description = "Determines whether to create a DB instance role association" + type = bool + default = true +} + +variable "feature_name" { + description = "Name of the feature for association" + type = string + default = null +} + +variable "role_arn" { + description = "Amazon Resource Name (ARN) of the IAM Role to associate with the DB Instance" + type = string + default = null +} + +variable "db_instance_identifier" { + description = "The database instance identifier to associate the role" + type = string + default = null +} diff --git a/modules/aws-rds/db_instance_role_association/versions.tf b/modules/aws-rds/db_instance_role_association/versions.tf new file mode 100644 index 0000000..3b3b588 --- /dev/null +++ b/modules/aws-rds/db_instance_role_association/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.36" + } + } +} diff --git a/modules/aws-rds/db_option_group/README.md b/modules/aws-rds/db_option_group/README.md new file mode 100644 index 0000000..caf96fb --- /dev/null +++ b/modules/aws-rds/db_option_group/README.md @@ -0,0 +1,47 @@ +# aws_db_option_group + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.36 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_option_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_option_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [engine\_name](#input\_engine\_name) | Specifies the name of the engine that this option group should be associated with | `string` | `null` | no | +| [major\_engine\_version](#input\_major\_engine\_version) | Specifies the major version of the engine that this option group should be associated with | `string` | `null` | no | +| [name](#input\_name) | The name of the option group | `string` | `""` | no | +| [option\_group\_description](#input\_option\_group\_description) | The description of the option group | `string` | `null` | no | +| [options](#input\_options) | A list of Options to apply | `any` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Define maximum timeout for deletion of `aws_db_option_group` resource | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_option\_group\_arn](#output\_db\_option\_group\_arn) | The ARN of the db option group | +| [db\_option\_group\_id](#output\_db\_option\_group\_id) | The db option group id | + diff --git a/modules/aws-rds/db_option_group/main.tf b/modules/aws-rds/db_option_group/main.tf new file mode 100644 index 0000000..6ae3584 --- /dev/null +++ b/modules/aws-rds/db_option_group/main.tf @@ -0,0 +1,50 @@ +locals { + name = var.use_name_prefix ? null : var.name + name_prefix = var.use_name_prefix ? "${var.name}-" : null + + description = coalesce(var.option_group_description, format("%s option group", var.name)) +} + +resource "aws_db_option_group" "this" { + count = var.create ? 1 : 0 + + name = local.name + name_prefix = local.name_prefix + option_group_description = local.description + engine_name = var.engine_name + major_engine_version = var.major_engine_version + + dynamic "option" { + for_each = var.options + content { + option_name = option.value.option_name + port = lookup(option.value, "port", null) + version = lookup(option.value, "version", null) + db_security_group_memberships = lookup(option.value, "db_security_group_memberships", null) + vpc_security_group_memberships = lookup(option.value, "vpc_security_group_memberships", null) + + dynamic "option_settings" { + for_each = lookup(option.value, "option_settings", []) + content { + name = lookup(option_settings.value, "name", null) + value = lookup(option_settings.value, "value", null) + } + } + } + } + + tags = merge( + var.tags, + { + "Name" = var.name + }, + ) + + timeouts { + delete = lookup(var.timeouts, "delete", null) + } + + lifecycle { + create_before_destroy = true + } +} diff --git a/modules/aws-rds/db_option_group/outputs.tf b/modules/aws-rds/db_option_group/outputs.tf new file mode 100644 index 0000000..377e169 --- /dev/null +++ b/modules/aws-rds/db_option_group/outputs.tf @@ -0,0 +1,9 @@ +output "db_option_group_id" { + description = "The db option group id" + value = try(aws_db_option_group.this[0].id, null) +} + +output "db_option_group_arn" { + description = "The ARN of the db option group" + value = try(aws_db_option_group.this[0].arn, null) +} diff --git a/modules/aws-rds/db_option_group/variables.tf b/modules/aws-rds/db_option_group/variables.tf new file mode 100644 index 0000000..de4be19 --- /dev/null +++ b/modules/aws-rds/db_option_group/variables.tf @@ -0,0 +1,53 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "name" { + description = "The name of the option group" + type = string + default = "" +} + +variable "use_name_prefix" { + description = "Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix" + type = bool + default = true +} + +variable "option_group_description" { + description = "The description of the option group" + type = string + default = null +} + +variable "engine_name" { + description = "Specifies the name of the engine that this option group should be associated with" + type = string + default = null +} + +variable "major_engine_version" { + description = "Specifies the major version of the engine that this option group should be associated with" + type = string + default = null +} + +variable "options" { + description = "A list of Options to apply" + type = any + default = [] +} + +variable "timeouts" { + description = "Define maximum timeout for deletion of `aws_db_option_group` resource" + type = map(string) + default = {} +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} diff --git a/modules/aws-rds/db_option_group/versions.tf b/modules/aws-rds/db_option_group/versions.tf new file mode 100644 index 0000000..3b3b588 --- /dev/null +++ b/modules/aws-rds/db_option_group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.36" + } + } +} diff --git a/modules/aws-rds/db_parameter_group/README.md b/modules/aws-rds/db_parameter_group/README.md new file mode 100644 index 0000000..4b4faab --- /dev/null +++ b/modules/aws-rds/db_parameter_group/README.md @@ -0,0 +1,45 @@ +# aws_db_parameter_group + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.36 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_parameter_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_parameter_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [description](#input\_description) | The description of the DB parameter group | `string` | `null` | no | +| [family](#input\_family) | The family of the DB parameter group | `string` | `null` | no | +| [name](#input\_name) | The name of the DB parameter group | `string` | `""` | no | +| [parameters](#input\_parameters) | A list of DB parameter maps to apply | `list(map(string))` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the db parameter group | +| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The db parameter group id | + diff --git a/modules/aws-rds/db_parameter_group/main.tf b/modules/aws-rds/db_parameter_group/main.tf new file mode 100644 index 0000000..94acbe5 --- /dev/null +++ b/modules/aws-rds/db_parameter_group/main.tf @@ -0,0 +1,35 @@ +locals { + name = var.use_name_prefix ? null : var.name + name_prefix = var.use_name_prefix ? "${var.name}-" : null + + description = coalesce(var.description, format("%s parameter group", var.name)) +} + +resource "aws_db_parameter_group" "this" { + count = var.create ? 1 : 0 + + name = local.name + name_prefix = local.name_prefix + description = local.description + family = var.family + + dynamic "parameter" { + for_each = var.parameters + content { + name = parameter.value.name + value = parameter.value.value + apply_method = lookup(parameter.value, "apply_method", null) + } + } + + tags = merge( + var.tags, + { + "Name" = var.name + }, + ) + + lifecycle { + create_before_destroy = true + } +} diff --git a/modules/aws-rds/db_parameter_group/outputs.tf b/modules/aws-rds/db_parameter_group/outputs.tf new file mode 100644 index 0000000..0ea4641 --- /dev/null +++ b/modules/aws-rds/db_parameter_group/outputs.tf @@ -0,0 +1,9 @@ +output "db_parameter_group_id" { + description = "The db parameter group id" + value = try(aws_db_parameter_group.this[0].id, null) +} + +output "db_parameter_group_arn" { + description = "The ARN of the db parameter group" + value = try(aws_db_parameter_group.this[0].arn, null) +} diff --git a/modules/aws-rds/db_parameter_group/variables.tf b/modules/aws-rds/db_parameter_group/variables.tf new file mode 100644 index 0000000..b7b7af9 --- /dev/null +++ b/modules/aws-rds/db_parameter_group/variables.tf @@ -0,0 +1,41 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "name" { + description = "The name of the DB parameter group" + type = string + default = "" +} + +variable "use_name_prefix" { + description = "Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix" + type = bool + default = true +} + +variable "description" { + description = "The description of the DB parameter group" + type = string + default = null +} + +variable "family" { + description = "The family of the DB parameter group" + type = string + default = null +} + +variable "parameters" { + description = "A list of DB parameter maps to apply" + type = list(map(string)) + default = [] +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} diff --git a/modules/aws-rds/db_parameter_group/versions.tf b/modules/aws-rds/db_parameter_group/versions.tf new file mode 100644 index 0000000..3b3b588 --- /dev/null +++ b/modules/aws-rds/db_parameter_group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.36" + } + } +} diff --git a/modules/aws-rds/db_subnet_group/README.md b/modules/aws-rds/db_subnet_group/README.md new file mode 100644 index 0000000..6a79449 --- /dev/null +++ b/modules/aws-rds/db_subnet_group/README.md @@ -0,0 +1,44 @@ +# aws_db_subnet_group + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.36 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_subnet_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [description](#input\_description) | The description of the DB subnet group | `string` | `null` | no | +| [name](#input\_name) | The name of the DB subnet group | `string` | `""` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of VPC subnet IDs | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_subnet\_group\_arn](#output\_db\_subnet\_group\_arn) | The ARN of the db subnet group | +| [db\_subnet\_group\_id](#output\_db\_subnet\_group\_id) | The db subnet group name | + diff --git a/modules/aws-rds/db_subnet_group/main.tf b/modules/aws-rds/db_subnet_group/main.tf new file mode 100644 index 0000000..6eecd88 --- /dev/null +++ b/modules/aws-rds/db_subnet_group/main.tf @@ -0,0 +1,22 @@ +locals { + name = var.use_name_prefix ? null : var.name + name_prefix = var.use_name_prefix ? "${var.name}-" : null + + description = coalesce(var.description, format("%s subnet group", var.name)) +} + +resource "aws_db_subnet_group" "this" { + count = var.create ? 1 : 0 + + name = local.name + name_prefix = local.name_prefix + description = local.description + subnet_ids = var.subnet_ids + + tags = merge( + var.tags, + { + "Name" = var.name + }, + ) +} diff --git a/modules/aws-rds/db_subnet_group/outputs.tf b/modules/aws-rds/db_subnet_group/outputs.tf new file mode 100644 index 0000000..dd92fe8 --- /dev/null +++ b/modules/aws-rds/db_subnet_group/outputs.tf @@ -0,0 +1,9 @@ +output "db_subnet_group_id" { + description = "The db subnet group name" + value = try(aws_db_subnet_group.this[0].id, null) +} + +output "db_subnet_group_arn" { + description = "The ARN of the db subnet group" + value = try(aws_db_subnet_group.this[0].arn, null) +} diff --git a/modules/aws-rds/db_subnet_group/variables.tf b/modules/aws-rds/db_subnet_group/variables.tf new file mode 100644 index 0000000..48185ab --- /dev/null +++ b/modules/aws-rds/db_subnet_group/variables.tf @@ -0,0 +1,35 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "name" { + description = "The name of the DB subnet group" + type = string + default = "" +} + +variable "use_name_prefix" { + description = "Determines whether to use `name` as is or create a unique name beginning with `name` as the specified prefix" + type = bool + default = true +} + +variable "description" { + description = "The description of the DB subnet group" + type = string + default = null +} + +variable "subnet_ids" { + description = "A list of VPC subnet IDs" + type = list(string) + default = [] +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} diff --git a/modules/aws-rds/db_subnet_group/versions.tf b/modules/aws-rds/db_subnet_group/versions.tf new file mode 100644 index 0000000..3b3b588 --- /dev/null +++ b/modules/aws-rds/db_subnet_group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.36" + } + } +} diff --git a/modules/aws-rds/main.tf b/modules/aws-rds/main.tf new file mode 100644 index 0000000..bf35f3c --- /dev/null +++ b/modules/aws-rds/main.tf @@ -0,0 +1,165 @@ +locals { + create_db_subnet_group = var.create_db_subnet_group + create_db_parameter_group = var.create_db_parameter_group + create_db_instance = var.create_db_instance + + db_subnet_group_name = var.create_db_subnet_group ? module.db_subnet_group.db_subnet_group_id : var.db_subnet_group_name + parameter_group_name_id = var.create_db_parameter_group ? module.db_parameter_group.db_parameter_group_id : var.parameter_group_name + + create_db_option_group = var.create_db_option_group && var.engine != "postgres" + option_group = local.create_db_option_group ? module.db_option_group.db_option_group_id : var.option_group_name +} + +module "db_subnet_group" { + source = "./db_subnet_group" + + create = local.create_db_subnet_group + + name = coalesce(var.db_subnet_group_name, var.identifier) + use_name_prefix = var.db_subnet_group_use_name_prefix + description = var.db_subnet_group_description + subnet_ids = var.subnet_ids + + tags = merge(var.tags, var.db_subnet_group_tags) +} + +module "db_parameter_group" { + source = "./db_parameter_group" + + create = local.create_db_parameter_group + + name = coalesce(var.parameter_group_name, var.identifier) + use_name_prefix = var.parameter_group_use_name_prefix + description = var.parameter_group_description + family = var.family + + parameters = var.parameters + + tags = merge(var.tags, var.db_parameter_group_tags) +} + +module "db_option_group" { + source = "./db_option_group" + + create = local.create_db_option_group + + name = coalesce(var.option_group_name, var.identifier) + use_name_prefix = var.option_group_use_name_prefix + option_group_description = var.option_group_description + engine_name = var.engine + major_engine_version = var.major_engine_version + + options = var.options + + timeouts = var.option_group_timeouts + + tags = merge(var.tags, var.db_option_group_tags) +} + +module "db_instance" { + source = "./db_instance" + + create = local.create_db_instance + identifier = var.identifier + use_identifier_prefix = var.instance_use_identifier_prefix + + engine = var.engine + engine_version = var.engine_version + instance_class = var.instance_class + allocated_storage = var.allocated_storage + storage_type = var.storage_type + storage_encrypted = var.storage_encrypted + kms_key_id = var.kms_key_id + license_model = var.license_model + + db_name = var.db_name + username = var.username + password = var.manage_master_user_password ? null : var.password + port = var.port + domain = var.domain + domain_auth_secret_arn = var.domain_auth_secret_arn + domain_dns_ips = var.domain_dns_ips + domain_fqdn = var.domain_fqdn + domain_iam_role_name = var.domain_iam_role_name + domain_ou = var.domain_ou + iam_database_authentication_enabled = var.iam_database_authentication_enabled + custom_iam_instance_profile = var.custom_iam_instance_profile + manage_master_user_password = var.manage_master_user_password + master_user_secret_kms_key_id = var.master_user_secret_kms_key_id + + manage_master_user_password_rotation = var.manage_master_user_password_rotation + master_user_password_rotate_immediately = var.master_user_password_rotate_immediately + master_user_password_rotation_automatically_after_days = var.master_user_password_rotation_automatically_after_days + master_user_password_rotation_duration = var.master_user_password_rotation_duration + master_user_password_rotation_schedule_expression = var.master_user_password_rotation_schedule_expression + + vpc_security_group_ids = var.vpc_security_group_ids + db_subnet_group_name = local.db_subnet_group_name + parameter_group_name = local.parameter_group_name_id + option_group_name = var.engine != "postgres" ? local.option_group : null + network_type = var.network_type + + availability_zone = var.availability_zone + multi_az = var.multi_az + iops = var.iops + storage_throughput = var.storage_throughput + publicly_accessible = var.publicly_accessible + ca_cert_identifier = var.ca_cert_identifier + + allow_major_version_upgrade = var.allow_major_version_upgrade + auto_minor_version_upgrade = var.auto_minor_version_upgrade + apply_immediately = var.apply_immediately + maintenance_window = var.maintenance_window + blue_green_update = var.blue_green_update + + snapshot_identifier = var.snapshot_identifier + copy_tags_to_snapshot = var.copy_tags_to_snapshot + skip_final_snapshot = var.skip_final_snapshot + final_snapshot_identifier_prefix = var.final_snapshot_identifier_prefix + + performance_insights_enabled = var.performance_insights_enabled + performance_insights_retention_period = var.performance_insights_retention_period + performance_insights_kms_key_id = var.performance_insights_enabled ? var.performance_insights_kms_key_id : null + + replicate_source_db = var.replicate_source_db + replica_mode = var.replica_mode + backup_retention_period = var.backup_retention_period + backup_window = var.backup_window + max_allocated_storage = var.max_allocated_storage + monitoring_interval = var.monitoring_interval + monitoring_role_arn = var.monitoring_role_arn + monitoring_role_name = var.monitoring_role_name + monitoring_role_use_name_prefix = var.monitoring_role_use_name_prefix + monitoring_role_description = var.monitoring_role_description + create_monitoring_role = var.create_monitoring_role + monitoring_role_permissions_boundary = var.monitoring_role_permissions_boundary + + character_set_name = var.character_set_name + nchar_character_set_name = var.nchar_character_set_name + timezone = var.timezone + + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports + create_cloudwatch_log_group = var.create_cloudwatch_log_group + cloudwatch_log_group_retention_in_days = var.cloudwatch_log_group_retention_in_days + cloudwatch_log_group_kms_key_id = var.cloudwatch_log_group_kms_key_id + + timeouts = var.timeouts + + deletion_protection = var.deletion_protection + delete_automated_backups = var.delete_automated_backups + + restore_to_point_in_time = var.restore_to_point_in_time + s3_import = var.s3_import + + tags = merge(var.tags, var.db_instance_tags) +} + +module "db_instance_role_association" { + source = "./db_instance_role_association" + + for_each = { for k, v in var.db_instance_role_associations : k => v if var.create_db_instance } + + feature_name = each.key + role_arn = each.value + db_instance_identifier = module.db_instance.db_instance_identifier +} diff --git a/modules/aws-rds/outputs.tf b/modules/aws-rds/outputs.tf new file mode 100644 index 0000000..b0fb26b --- /dev/null +++ b/modules/aws-rds/outputs.tf @@ -0,0 +1,178 @@ +output "enhanced_monitoring_iam_role_name" { + description = "The name of the monitoring role" + value = module.db_instance.enhanced_monitoring_iam_role_name +} + +output "enhanced_monitoring_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the monitoring role" + value = module.db_instance.enhanced_monitoring_iam_role_arn +} + +output "db_instance_address" { + description = "The address of the RDS instance" + value = module.db_instance.db_instance_address +} + +output "db_instance_arn" { + description = "The ARN of the RDS instance" + value = module.db_instance.db_instance_arn +} + +output "db_instance_availability_zone" { + description = "The availability zone of the RDS instance" + value = module.db_instance.db_instance_availability_zone +} + +output "db_instance_endpoint" { + description = "The connection endpoint" + value = module.db_instance.db_instance_endpoint +} + +output "db_listener_endpoint" { + description = "Specifies the listener connection endpoint for SQL Server Always On" + value = module.db_instance.db_listener_endpoint +} + +output "db_instance_engine" { + description = "The database engine" + value = module.db_instance.db_instance_engine +} + +output "db_instance_engine_version_actual" { + description = "The running version of the database" + value = module.db_instance.db_instance_engine_version_actual +} + +output "db_instance_hosted_zone_id" { + description = "The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record)" + value = module.db_instance.db_instance_hosted_zone_id +} + +output "db_instance_identifier" { + description = "The RDS instance identifier" + value = module.db_instance.db_instance_identifier +} + +output "db_instance_resource_id" { + description = "The RDS Resource ID of this instance" + value = module.db_instance.db_instance_resource_id +} + +output "db_instance_status" { + description = "The RDS instance status" + value = module.db_instance.db_instance_status +} + +output "db_instance_name" { + description = "The database name" + value = module.db_instance.db_instance_name +} + +output "db_instance_username" { + description = "The master username for the database" + value = module.db_instance.db_instance_username + sensitive = true +} + +output "db_instance_domain" { + description = "The ID of the Directory Service Active Directory domain the instance is joined to" + value = module.db_instance.db_instance_domain +} + +output "db_instance_domain_auth_secret_arn" { + description = "The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain" + value = module.db_instance.db_instance_domain_auth_secret_arn +} + +output "db_instance_domain_dns_ips" { + description = "The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers" + value = module.db_instance.db_instance_domain_dns_ips +} + +output "db_instance_domain_fqdn" { + description = "The fully qualified domain name (FQDN) of an self managed Active Directory domain" + value = module.db_instance.db_instance_domain_fqdn +} + +output "db_instance_domain_iam_role_name" { + description = "The name of the IAM role to be used when making API calls to the Directory Service" + value = module.db_instance.db_instance_domain_iam_role_name +} + +output "db_instance_domain_ou" { + description = "The self managed Active Directory organizational unit for your DB instance to join" + value = module.db_instance.db_instance_domain_ou +} + +output "db_instance_port" { + description = "The database port" + value = module.db_instance.db_instance_port +} + +output "db_instance_ca_cert_identifier" { + description = "Specifies the identifier of the CA certificate for the DB instance" + value = module.db_instance.db_instance_ca_cert_identifier +} + +output "db_instance_master_user_secret_arn" { + description = "The ARN of the master user secret (Only available when manage_master_user_password is set to true)" + value = module.db_instance.db_instance_master_user_secret_arn +} + +output "db_subnet_group_id" { + description = "The db subnet group name" + value = module.db_subnet_group.db_subnet_group_id +} + +output "db_subnet_group_arn" { + description = "The ARN of the db subnet group" + value = module.db_subnet_group.db_subnet_group_arn +} + +output "db_parameter_group_id" { + description = "The db parameter group id" + value = module.db_parameter_group.db_parameter_group_id +} + +output "db_parameter_group_arn" { + description = "The ARN of the db parameter group" + value = module.db_parameter_group.db_parameter_group_arn +} + +# DB option group +output "db_option_group_id" { + description = "The db option group id" + value = module.db_option_group.db_option_group_id +} + +output "db_option_group_arn" { + description = "The ARN of the db option group" + value = module.db_option_group.db_option_group_arn +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "db_instance_cloudwatch_log_groups" { + description = "Map of CloudWatch log groups created and their attributes" + value = module.db_instance.db_instance_cloudwatch_log_groups +} + +################################################################################ +# DB Instance Role Association +################################################################################ + +output "db_instance_role_associations" { + description = "A map of DB Instance Identifiers and IAM Role ARNs separated by a comma" + value = module.db_instance_role_association +} + +################################################################################ +# Managed Secret Rotation +################################################################################ + +output "db_instance_secretsmanager_secret_rotation_enabled" { + description = "Specifies whether automatic rotation is enabled for the secret" + value = module.db_instance.db_instance_secretsmanager_secret_rotation_enabled +} diff --git a/modules/aws-rds/variables.tf b/modules/aws-rds/variables.tf new file mode 100644 index 0000000..2d2cde5 --- /dev/null +++ b/modules/aws-rds/variables.tf @@ -0,0 +1,609 @@ +variable "identifier" { + description = "The name of the RDS instance" + type = string +} + +variable "instance_use_identifier_prefix" { + description = "Determines whether to use `identifier` as is or create a unique identifier beginning with `identifier` as the specified prefix" + type = bool + default = false +} + +variable "custom_iam_instance_profile" { + description = "RDS custom iam instance profile" + type = string + default = null +} + +variable "allocated_storage" { + description = "The allocated storage in gigabytes" + type = number + default = null +} + +variable "storage_type" { + description = "One of 'standard' (magnetic), 'gp2' (general purpose SSD), 'gp3' (new generation of general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not. If you specify 'io1' or 'gp3' , you must also include a value for the 'iops' parameter" + type = string + default = null +} + +variable "storage_throughput" { + description = "Storage throughput value for the DB instance. See `notes` for limitations regarding this variable for `gp3`" + type = number + default = null +} + +variable "storage_encrypted" { + description = "Specifies whether the DB instance is encrypted" + type = bool + default = true +} + +variable "kms_key_id" { + description = "The ARN for the KMS encryption key. If creating an encrypted replica, set this to the destination KMS ARN. If storage_encrypted is set to true and kms_key_id is not specified the default KMS key created in your account will be used. Be sure to use the full ARN, not a key alias." + type = string + default = null +} + +variable "replicate_source_db" { + description = "Specifies that this resource is a Replicate database, and to use this value as the source database. This correlates to the identifier of another Amazon RDS Database to replicate" + type = string + default = null +} + +variable "license_model" { + description = "License model information for this DB instance. Optional, but required for some DB engines, i.e. Oracle SE1" + type = string + default = null +} + +variable "replica_mode" { + description = "Specifies whether the replica is in either mounted or open-read-only mode. This attribute is only supported by Oracle instances. Oracle replicas operate in open-read-only mode unless otherwise specified" + type = string + default = null +} + +variable "iam_database_authentication_enabled" { + description = "Specifies whether or not the mappings of AWS Identity and Access Management (IAM) accounts to database accounts are enabled" + type = bool + default = false +} + +variable "domain" { + description = "The ID of the Directory Service Active Directory domain to create the instance in" + type = string + default = null +} + +variable "domain_auth_secret_arn" { + description = "(Optional, but required if domain_fqdn is provided) The ARN for the Secrets Manager secret with the self managed Active Directory credentials for the user joining the domain. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "domain_dns_ips" { + description = "(Optional, but required if domain_fqdn is provided) The IPv4 DNS IP addresses of your primary and secondary self managed Active Directory domain controllers. Two IP addresses must be provided. If there isn't a secondary domain controller, use the IP address of the primary domain controller for both entries in the list. Conflicts with domain and domain_iam_role_name." + type = list(string) + default = null +} + +variable "domain_fqdn" { + description = "The fully qualified domain name (FQDN) of the self managed Active Directory domain. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "domain_iam_role_name" { + description = "(Required if domain is provided) The name of the IAM role to be used when making API calls to the Directory Service" + type = string + default = null +} + +variable "domain_ou" { + description = "(Optional, but required if domain_fqdn is provided) The self managed Active Directory organizational unit for your DB instance to join. Conflicts with domain and domain_iam_role_name." + type = string + default = null +} + +variable "engine" { + description = "The database engine to use" + type = string + default = null +} + +variable "engine_version" { + description = "The engine version to use" + type = string + default = null +} + +variable "skip_final_snapshot" { + description = "Determines whether a final DB snapshot is created before the DB instance is deleted. If true is specified, no DBSnapshot is created. If false is specified, a DB snapshot is created before the DB instance is deleted" + type = bool + default = false +} + +variable "snapshot_identifier" { + description = "Specifies whether or not to create this database from a snapshot. This correlates to the snapshot ID you'd find in the RDS console, e.g: rds:production-2015-06-26-06-05" + type = string + default = null +} + +variable "copy_tags_to_snapshot" { + description = "On delete, copy all Instance tags to the final snapshot" + type = bool + default = false +} + +variable "final_snapshot_identifier_prefix" { + description = "The name which is prefixed to the final snapshot on cluster destroy" + type = string + default = "final" +} + +variable "instance_class" { + description = "The instance type of the RDS instance" + type = string + default = null +} + +variable "db_name" { + description = "The DB name to create. If omitted, no database is created initially" + type = string + default = null +} + +variable "username" { + description = "Username for the master DB user" + type = string + default = null +} + +variable "password" { + description = < 2.X. Submit pull-requests to master branch. + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | +| [aws.us-east-1](#provider\_aws.us-east-1) | >= 3.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_metric_alarm.http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [aws_cloudwatch_metric_alarm.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [aws_route53_health_check.http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_health_check) | resource | +| [aws_route53_health_check.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_health_check) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarm\_actions](#input\_alarm\_actions) | Actions to execute when this alarm transitions. | `list(string)` | n/a | yes | +| [alarm\_name\_suffix](#input\_alarm\_name\_suffix) | Suffix for cloudwatch alarm name to ensure uniqueness. | `string` | `""` | no | +| [disable](#input\_disable) | Disable health checks | `bool` | `false` | no | +| [dns\_name](#input\_dns\_name) | Fully-qualified domain name (FQDN) to create. | `string` | n/a | yes | +| [environment](#input\_environment) | Environment tag (e.g. prod). | `string` | n/a | yes | +| [failure\_threshold](#input\_failure\_threshold) | Failure Threshold (must be less than or equal to 10) | `string` | `"3"` | no | +| [health\_check\_path](#input\_health\_check\_path) | Resource Path to check | `string` | `""` | no | +| [health\_check\_regions](#input\_health\_check\_regions) | AWS Regions for health check | `list(string)` |
[
"us-east-1",
"us-west-1",
"us-west-2"
]
| no | +| [request\_interval](#input\_request\_interval) | Request Interval (must be 10 or 30) | `string` | `"30"` | no | + +## Outputs + +No outputs. + diff --git a/modules/aws-route53-health-check-main/main.tf b/modules/aws-route53-health-check-main/main.tf new file mode 100644 index 0000000..5e48c65 --- /dev/null +++ b/modules/aws-route53-health-check-main/main.tf @@ -0,0 +1,140 @@ +locals { + http_enabled = !var.disable && contains(var.health_check_type, "HTTP") + https_enabled = !var.disable && contains(var.health_check_type, "HTTPS") + tcp_enabled = !var.disable && contains(var.health_check_type, "TCP") +} + + +resource "aws_route53_health_check" "http" { + fqdn = var.dns_name + port = 80 + type = "HTTP" + resource_path = var.health_check_path + failure_threshold = var.failure_threshold + request_interval = var.request_interval + regions = var.health_check_regions + measure_latency = true + count = local.http_enabled ? 1 : 0 + + tags = { + Name = "${var.dns_name}-http" + Environment = var.environment + Automation = "Terraform" + } +} + +resource "aws_route53_health_check" "https" { + fqdn = var.dns_name + port = 443 + type = "HTTPS" + resource_path = var.health_check_path + failure_threshold = var.failure_threshold + request_interval = var.request_interval + regions = var.health_check_regions + measure_latency = true + count = local.https_enabled ? 1 : 0 + + tags = { + Name = "${var.dns_name}-https" + Environment = var.environment + Automation = "Terraform" + } +} + +resource "aws_route53_health_check" "tcp" { + fqdn = var.dns_name + port = var.port + type = "TCP" + resource_path = var.health_check_path + failure_threshold = var.failure_threshold + request_interval = var.request_interval + regions = var.health_check_regions + measure_latency = true + count = local.tcp_enabled ? 1 : 0 + + tags = { + Name = "${var.dns_name}-tcp" + Environment = var.environment + Automation = "Terraform" + } +} + +resource "aws_cloudwatch_metric_alarm" "http" { + provider = aws.us-east-1 + + alarm_name = "${var.dns_name}-status-http${var.alarm_name_suffix}" + alarm_description = "Route53 health check status for ${var.dns_name}" + count = (local.http_enabled && var.enable_cloudwatch_alarm) ? 1 : 0 + + + namespace = "AWS/Route53" + metric_name = "HealthCheckStatus" + + dimensions = { + HealthCheckId = aws_route53_health_check.http[0].id + } + + comparison_operator = "LessThanThreshold" + statistic = "Minimum" + threshold = "1" + + evaluation_periods = "1" + period = "60" + + alarm_actions = var.alarm_actions + ok_actions = var.alarm_actions + insufficient_data_actions = var.alarm_actions +} + +resource "aws_cloudwatch_metric_alarm" "https" { + provider = aws.us-east-1 + + alarm_name = "${var.dns_name}-status-https${var.alarm_name_suffix}" + alarm_description = "Route53 health check status for ${var.dns_name}" + count = (local.https_enabled && var.enable_cloudwatch_alarm) ? 1 : 0 + + namespace = "AWS/Route53" + metric_name = "HealthCheckStatus" + + dimensions = { + HealthCheckId = aws_route53_health_check.https[0].id + } + + comparison_operator = "LessThanThreshold" + statistic = "Minimum" + threshold = "1" + + evaluation_periods = "1" + period = "60" + + alarm_actions = var.alarm_actions + ok_actions = var.alarm_actions + insufficient_data_actions = var.alarm_actions +} + +resource "aws_cloudwatch_metric_alarm" "tcp" { + provider = aws.us-east-1 + + alarm_name = "${var.dns_name}-status-tcp${var.alarm_name_suffix}" + alarm_description = "Route53 health check status for ${var.dns_name}" + count = (local.tcp_enabled && var.enable_cloudwatch_alarm) ? 1 : 0 + + namespace = "AWS/Route53" + metric_name = "HealthCheckStatus" + + dimensions = { + HealthCheckId = aws_route53_health_check.tcp[0].id + } + + comparison_operator = "LessThanThreshold" + statistic = "Minimum" + threshold = "1" + + evaluation_periods = "1" + period = "60" + + alarm_actions = var.alarm_actions + ok_actions = var.alarm_actions + insufficient_data_actions = var.alarm_actions +} + diff --git a/modules/aws-route53-health-check-main/outputs.tf b/modules/aws-route53-health-check-main/outputs.tf new file mode 100644 index 0000000..bf29f90 --- /dev/null +++ b/modules/aws-route53-health-check-main/outputs.tf @@ -0,0 +1,17 @@ +output "health_check_ids" { + description = "The health check ID" + value = compact([ + try(aws_route53_health_check.http[0].id, ""), + try(aws_route53_health_check.https[0].id, ""), + try(aws_route53_health_check.tcp[0].id, ""), + ]) +} + +output "health_check_arns" { + description = "The health check ARN" + value = compact([ + try(aws_route53_health_check.http[0].arn, ""), + try(aws_route53_health_check.https[0].arn, ""), + try(aws_route53_health_check.tcp[0].arn, ""), + ]) +} diff --git a/modules/aws-route53-health-check-main/providers.tf b/modules/aws-route53-health-check-main/providers.tf new file mode 100644 index 0000000..edafcb7 --- /dev/null +++ b/modules/aws-route53-health-check-main/providers.tf @@ -0,0 +1,4 @@ +provider "aws" { + alias = "us-east-1" +} + diff --git a/modules/aws-route53-health-check-main/variables.tf b/modules/aws-route53-health-check-main/variables.tf new file mode 100644 index 0000000..9a217f6 --- /dev/null +++ b/modules/aws-route53-health-check-main/variables.tf @@ -0,0 +1,79 @@ +variable "environment" { + description = "Environment tag (e.g. prod)." + type = string +} + +variable "dns_name" { + description = "Fully-qualified domain name (FQDN) to create." + type = string +} + +variable "alarm_name_suffix" { + description = "Suffix for cloudwatch alarm name to ensure uniqueness." + type = string + default = "" +} + +variable "alarm_actions" { + description = "Actions to execute when this alarm transitions." + type = list(string) + default = [] +} + +variable "health_check_regions" { + description = "AWS Regions for health check" + type = list(string) + default = ["us-east-1", "eu-west-1", "ap-southeast-1"] +} + +variable "health_check_path" { + description = "Resource Path to check" + type = string + default = "" +} + +variable "failure_threshold" { + description = "Failure Threshold (must be less than or equal to 10)" + type = string + default = "3" +} + +variable "request_interval" { + description = "Request Interval (must be 10 or 30)" + type = string + default = "30" +} + +variable "disable" { + description = "Disable health checks" + type = bool + default = false +} + +variable "port" { + description = "Port for TCP (between 0 to 65535)" + type = number + default = 443 +} + +variable "enable_cloudwatch_alarm" { + description = "Enable cloudwatch alarm" + type = bool + default = true +} + +variable "health_check_type" { + description = <<-EOT + The health check type to use. + The value specified can either be `HTTP`, `HTTPS`, + and/or `TCP`. + + Defaults to `HTTP` and `HTTPS`. + EOT + type = set(string) + default = ["HTTP", "HTTPS"] + validation { + condition = length(setintersection(["HTTP", "HTTPS", "TCP"], var.health_check_type)) > 0 + error_message = "Health check type must be one be one of: HTTP, HTTPS, TCP." + } +} diff --git a/modules/aws-route53-health-check-main/versions.tf b/modules/aws-route53-health-check-main/versions.tf new file mode 100644 index 0000000..9fdbc79 --- /dev/null +++ b/modules/aws-route53-health-check-main/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = ">= 3.0" + } +} diff --git a/modules/aws-route53-records/README.md b/modules/aws-route53-records/README.md new file mode 100644 index 0000000..d76a21e --- /dev/null +++ b/modules/aws-route53-records/README.md @@ -0,0 +1,105 @@ +# Route53 Records + +This module creates DNS records in Route53 zone. + +## Usage + +```hcl +module "records" { + source = "../terraform-aws-route53-records" + version = "~> 2.0" + + zone_name = "" + private_zone = #Set true if zone is private + + records = [ + { + name = "apigateway1" + type = "A" + alias = { + name = "d-10qxlbvagl.execute-api.eu-west-1.amazonaws.com" + zone_id = "ZLY8HYME6SFAD" + } + }, + { + name = "" + type = "A" + ttl = 3600 + records = [ + "10.10.10.10", + ] + }, + ] + +} +``` + +## Terragrunt issue + +Due to a bug in Terragrunt (https://github.com/gruntwork-io/terragrunt/issues/1211), users should specify records using `records_jsonencoded` argument as jsonencode()'d string, like this: + +```hcl +records_jsonencoded = jsonencode([ + { + name = "tg-list1" + type = "A" + ttl = 3600 + records = [ + "10.10.10.10", + ] + }, + { + name = "tg-list2" + type = "A" + ttl = 3600 + records = [ + "20.10.10.10", + "30.10.10.10", + ] + } +]) +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 2.49 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.49 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create DNS records | `bool` | `true` | no | +| [private\_zone](#input\_private\_zone) | Whether Route53 zone is private or public | `bool` | `false` | no | +| [records](#input\_records) | List of objects of DNS records | `any` | `[]` | no | +| [records\_jsonencoded](#input\_records\_jsonencoded) | List of map of DNS records (stored as jsonencoded string, for terragrunt) | `string` | `null` | no | +| [zone\_id](#input\_zone\_id) | ID of DNS zone | `string` | `null` | no | +| [zone\_name](#input\_zone\_name) | Name of DNS zone | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [route53\_record\_fqdn](#output\_route53\_record\_fqdn) | FQDN built using the zone domain and name | +| [route53\_record\_name](#output\_route53\_record\_name) | The name of the record | + diff --git a/modules/aws-route53-records/main.tf b/modules/aws-route53-records/main.tf new file mode 100644 index 0000000..b22cd30 --- /dev/null +++ b/modules/aws-route53-records/main.tf @@ -0,0 +1,75 @@ +locals { + # Terragrunt users have to provide `records_jsonencoded` as jsonencode()'d string. + # See details: https://github.com/gruntwork-io/terragrunt/issues/1211 + records = concat(var.records, try(jsondecode(var.records_jsonencoded), [])) + + # Convert `records` from list to map with unique keys + recordsets = { for rs in local.records : try(rs.key, join(" ", compact(["${rs.name} ${rs.type}", try(rs.set_identifier, "")]))) => rs } +} + +data "aws_route53_zone" "this" { + count = var.create && (var.zone_id != null || var.zone_name != "null") ? 1 : 0 + + zone_id = var.zone_id + name = var.zone_name + private_zone = var.private_zone +} + +resource "aws_route53_record" "this" { + for_each = { for k, v in local.recordsets : k => v if var.create && (var.zone_id != null || var.zone_name != "null") } + + zone_id = data.aws_route53_zone.this[0].zone_id + + name = each.value.name != "" ? (lookup(each.value, "full_name_override", false) ? each.value.name : "${each.value.name}.${data.aws_route53_zone.this[0].name}") : data.aws_route53_zone.this[0].name + type = each.value.type + ttl = lookup(each.value, "ttl", null) + records = try(each.value.records, null) + set_identifier = lookup(each.value, "set_identifier", null) + health_check_id = lookup(each.value, "health_check_id", null) + multivalue_answer_routing_policy = lookup(each.value, "multivalue_answer_routing_policy", null) + allow_overwrite = lookup(each.value, "allow_overwrite", false) + + dynamic "alias" { + for_each = length(keys(lookup(each.value, "alias", {}))) == 0 ? [] : [true] + + content { + name = each.value.alias.name + zone_id = try(each.value.alias.zone_id, data.aws_route53_zone.this[0].zone_id) + evaluate_target_health = lookup(each.value.alias, "evaluate_target_health", false) + } + } + + dynamic "failover_routing_policy" { + for_each = length(keys(lookup(each.value, "failover_routing_policy", {}))) == 0 ? [] : [true] + + content { + type = each.value.failover_routing_policy.type + } + } + + dynamic "latency_routing_policy" { + for_each = length(keys(lookup(each.value, "latency_routing_policy", {}))) == 0 ? [] : [true] + + content { + region = each.value.latency_routing_policy.region + } + } + + dynamic "weighted_routing_policy" { + for_each = length(keys(lookup(each.value, "weighted_routing_policy", {}))) == 0 ? [] : [true] + + content { + weight = each.value.weighted_routing_policy.weight + } + } + + dynamic "geolocation_routing_policy" { + for_each = length(keys(lookup(each.value, "geolocation_routing_policy", {}))) == 0 ? [] : [true] + + content { + continent = lookup(each.value.geolocation_routing_policy, "continent", null) + country = lookup(each.value.geolocation_routing_policy, "country", null) + subdivision = lookup(each.value.geolocation_routing_policy, "subdivision", null) + } + } +} diff --git a/modules/aws-route53-records/outputs.tf b/modules/aws-route53-records/outputs.tf new file mode 100644 index 0000000..6d3b7ff --- /dev/null +++ b/modules/aws-route53-records/outputs.tf @@ -0,0 +1,9 @@ +output "route53_record_name" { + description = "The name of the record" + value = { for k, v in aws_route53_record.this : k => v.name } +} + +output "route53_record_fqdn" { + description = "FQDN built using the zone domain and name" + value = { for k, v in aws_route53_record.this : k => v.fqdn } +} diff --git a/modules/aws-route53-records/variables.tf b/modules/aws-route53-records/variables.tf new file mode 100644 index 0000000..7c9e841 --- /dev/null +++ b/modules/aws-route53-records/variables.tf @@ -0,0 +1,36 @@ +variable "create" { + description = "Whether to create DNS records" + type = bool + default = true +} + +variable "zone_id" { + description = "ID of DNS zone" + type = string + default = null +} + +# we especially specified zone_name as "null" (string null) +variable "zone_name" { + description = "Name of DNS zone" + type = string + default = "null" +} + +variable "private_zone" { + description = "Whether Route53 zone is private or public" + type = bool + default = false +} + +variable "records" { + description = "List of objects of DNS records" + type = any + default = [] +} + +variable "records_jsonencoded" { + description = "List of map of DNS records (stored as jsonencoded string, for terragrunt)" + type = string + default = null +} diff --git a/modules/aws-route53-records/versions.tf b/modules/aws-route53-records/versions.tf new file mode 100644 index 0000000..c7a7c16 --- /dev/null +++ b/modules/aws-route53-records/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = ">= 2.49" + } +} diff --git a/modules/aws-route53-zones/README.md b/modules/aws-route53-zones/README.md new file mode 100644 index 0000000..68ce25c --- /dev/null +++ b/modules/aws-route53-zones/README.md @@ -0,0 +1,103 @@ +# Route53 Zones + +This module creates Route53 zones. + +## Usage + +```hcl +module "zones" { + source = "../terraform-aws-route53-zones" + version = "~> 2.0" + + zones = { + "public.route53-zone.com" = { + domain_name = "route53-zone.com" + create_zone = true + comment = "route53-zone.com (production)" + tags = { + env = "production" + } + } + "private.route53-zone.com" = { + domain_name = "route53-zone.com" + create_zone = true + comment = "route53-zone.com (production)" + tags = { + env = "production" + } + vpc = [ + { + vpc_id = + }, + { + vpc_id = + }, + ] + } + + } +} +``` + +## Use module with existing zone + +If you want to get output values of existing route 53 zone, dont set set `create_zone` variable or set `false`. + +```hcl +module "zones" { + source = "../terraform-aws-route53-zones" + version = "~> 2.0" + + zones = { + "data.route53-zone.com" = { + domain_name = "route53-zone.com" + comment = "route53-zone.com (production)" + tags = { + env = "production" + } + } + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 2.49 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.49 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create Route53 zone | `bool` | `true` | no | +| [tags](#input\_tags) | Tags added to all zones. Will take precedence over tags from the 'zones' variable | `map(any)` | `{}` | no | +| [zones](#input\_zones) | Map of Route53 zone parameters | `any` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [route53\_zone\_name](#output\_route53\_zone\_name) | Name of Route53 zone | +| [route53\_zone\_name\_servers](#output\_route53\_zone\_name\_servers) | Name servers of Route53 zone | +| [route53\_zone\_zone\_id](#output\_route53\_zone\_zone\_id) | Zone ID of Route53 zone | + \ No newline at end of file diff --git a/modules/aws-route53-zones/main.tf b/modules/aws-route53-zones/main.tf new file mode 100644 index 0000000..7afc577 --- /dev/null +++ b/modules/aws-route53-zones/main.tf @@ -0,0 +1,55 @@ +resource "aws_route53_zone" "this" { + for_each = { for k, v in var.zones : k => v if lookup(v, "create_zone", false) } + + name = lookup(each.value, "domain_name", each.key) + comment = lookup(each.value, "comment", null) + force_destroy = lookup(each.value, "force_destroy", false) + + delegation_set_id = lookup(each.value, "delegation_set_id", null) + + dynamic "vpc" { + for_each = try(tolist(lookup(each.value, "vpc", [])), [lookup(each.value, "vpc", {})]) + + content { + vpc_id = vpc.value.vpc_id + vpc_region = lookup(vpc.value, "vpc_region", null) + } + } + + tags = merge( + lookup(each.value, "tags", {}), + var.tags + ) +} + +resource "aws_route53_zone" "cross_account_this" { + provider = aws.shared_infra + for_each = { for k, v in var.zones : k => v if lookup(v, "create_cross_account", false) } + + name = lookup(each.value, "domain_name", each.key) + comment = lookup(each.value, "comment", null) + force_destroy = lookup(each.value, "force_destroy", false) + + delegation_set_id = lookup(each.value, "delegation_set_id", null) + + dynamic "vpc" { + for_each = try(tolist(lookup(each.value, "vpc", [])), [lookup(each.value, "vpc", {})]) + + content { + vpc_id = vpc.value.vpc_id + vpc_region = lookup(vpc.value, "vpc_region", null) + } + } + + tags = merge( + lookup(each.value, "tags", {}), + var.tags + ) +} + +data "aws_route53_zone" "this" { + for_each = { for k, v in var.zones : k => v if lookup(v, "create_data_zone", false) } + + name = lookup(each.value, "domain_name", each.key) + private_zone = lookup(each.value, "private_zone", true) +} diff --git a/modules/aws-route53-zones/outputs.tf b/modules/aws-route53-zones/outputs.tf new file mode 100644 index 0000000..13e6434 --- /dev/null +++ b/modules/aws-route53-zones/outputs.tf @@ -0,0 +1,19 @@ +output "route53_zone_zone_id" { + description = "Zone ID of Route53 zone" + value = merge({ for k, v in aws_route53_zone.this : k => v.zone_id }, { for k, v in data.aws_route53_zone.this : k => v.zone_id }, { for k, v in aws_route53_zone.cross_account_this : k => v.zone_id }, ) +} + +output "route53_zone_zone_arn" { + description = "Zone ID of Route53 zone" + value = merge({ for k, v in aws_route53_zone.this : k => v.arn }, { for k, v in data.aws_route53_zone.this : k => v.arn }, { for k, v in aws_route53_zone.cross_account_this : k => v.arn }) +} + +output "route53_zone_name_servers" { + description = "Name servers of Route53 zone" + value = merge({ for k, v in aws_route53_zone.this : k => v.name_servers }, { for k, v in data.aws_route53_zone.this : k => v.name_servers }, { for k, v in aws_route53_zone.cross_account_this : k => v.name_servers }) +} + +output "route53_zone_name" { + description = "Name of Route53 zone" + value = merge({ for k, v in aws_route53_zone.this : k => v.name }, { for k, v in data.aws_route53_zone.this : k => v.name }, { for k, v in aws_route53_zone.cross_account_this : k => v.name }) +} diff --git a/modules/aws-route53-zones/variables.tf b/modules/aws-route53-zones/variables.tf new file mode 100644 index 0000000..f2e0eff --- /dev/null +++ b/modules/aws-route53-zones/variables.tf @@ -0,0 +1,17 @@ +variable "create" { + description = "Whether to create Route53 zone" + type = bool + default = true +} + +variable "zones" { + description = "Map of Route53 zone parameters" + type = any + default = {} +} + +variable "tags" { + description = "Tags added to all zones. Will take precedence over tags from the 'zones' variable" + type = map(any) + default = {} +} diff --git a/modules/aws-route53-zones/versions.tf b/modules/aws-route53-zones/versions.tf new file mode 100644 index 0000000..c7a7c16 --- /dev/null +++ b/modules/aws-route53-zones/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = ">= 2.49" + } +} diff --git a/modules/aws-s3/README.md b/modules/aws-s3/README.md new file mode 100644 index 0000000..5cbc5a1 --- /dev/null +++ b/modules/aws-s3/README.md @@ -0,0 +1,240 @@ +# AWS S3 bucket Terraform module + +Terraform module which creates S3 bucket on AWS with all (or almost all) features provided by Terraform AWS provider. + + + +These features of S3 bucket configurations are supported: + +- static web-site hosting +- access logging +- versioning +- CORS +- lifecycle rules +- server-side encryption +- object locking +- Cross-Region Replication (CRR) +- ELB log delivery bucket policy +- ALB/NLB log delivery bucket policy + +## Usage + +### Private bucket with versioning enabled + +```hcl +module "s3_bucket" { + source = "../terraform-aws-s3" + + bucket = "my-s3-bucket" + acl = "private" + + versioning = { + enabled = true + } + +} +``` + +### Cross Account Access Allow + +```hcl +module "s3_bucket" { + source = "../terraform-aws-s3" + + bucket = "" + + attach_cross_account_policy = true + cross_account_id = "" + + versioning = { + enabled = true + } + +} +``` + +### Bucket with ELB access log delivery policy attached + +```hcl +module "s3_bucket_for_logs" { + source = "../terraform-aws-s3" + + bucket = "my-s3-bucket-for-logs" + acl = "log-delivery-write" + + # Allow deletion of non-empty bucket + force_destroy = true + + attach_elb_log_delivery_policy = true +} +``` + +### Bucket with ALB/NLB access log delivery policy attached + +```hcl +module "s3_bucket_for_logs" { + source = "../terraform-aws-s3" + + bucket = "my-s3-bucket-for-logs" + acl = "log-delivery-write" + + # Allow deletion of non-empty bucket + force_destroy = true + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs +} +``` + +## Conditional creation + +Sometimes you need to have a way to create S3 resources conditionally but Terraform does not allow to use `count` inside `module` block, so the solution is to specify argument `create_bucket`. + +```hcl +# This S3 bucket will not be created +module "s3_bucket" { + source = "../terraform-aws-s3" + + create_bucket = false + # ... omitted +} +``` + +## Terragrunt and `variable "..." { type = any }` + +There is a bug [#1211](https://github.com/gruntwork-io/terragrunt/issues/1211) in Terragrunt related to the way how the variables of type `any` are passed to Terraform. + +This module solves this issue by supporting `jsonencode()`-string in addition to the expected type (`list` or `map`). + +In `terragrunt.hcl` you can write: + +```terraform +inputs = { + bucket = "foobar" # `bucket` has type `string`, no need to jsonencode() + cors_rule = jsonencode([...]) # `cors_rule` has type `any`, so `jsonencode()` is required +} +``` + + +## Module wrappers + +Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13. + +Users of Terragrunt can achieve similar results by using modules provided in the [wrappers](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/wrappers) directory, if they prefer to reduce amount of configuration files. + + +## Examples: + +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete) - Complete S3 bucket with most of supported features enabled +- [Cross-Region Replication](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/s3-replication) - S3 bucket with Cross-Region Replication (CRR) enabled +- [S3 Bucket Notifications](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/notification) - S3 bucket notifications to Lambda functions, SQS queues, and SNS topics. +- [S3 Bucket Object](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/object) - Manage S3 bucket objects. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.9 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.9 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_intelligent_tiering_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_intelligent_tiering_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | +| [aws_s3_bucket_ownership_controls.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | +| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_request_payment_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_request_payment_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_website_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | +| [aws_canonical_user_id.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | +| [aws_elb_service_account.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source | +| [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.deny_insecure_transport](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.elb_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.lb_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.require_latest_tls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acceleration\_status](#input\_acceleration\_status) | (Optional) Sets the accelerate configuration of an existing bucket. Can be Enabled or Suspended. | `string` | `null` | no | +| [acl](#input\_acl) | (Optional) The canned ACL to apply. Conflicts with `grant` | `string` | `null` | no | +| [attach\_deny\_insecure\_transport\_policy](#input\_attach\_deny\_insecure\_transport\_policy) | Controls if S3 bucket should have deny non-SSL transport policy attached | `bool` | `false` | no | +| [attach\_elb\_log\_delivery\_policy](#input\_attach\_elb\_log\_delivery\_policy) | Controls if S3 bucket should have ELB log delivery policy attached | `bool` | `false` | no | +| [attach\_lb\_log\_delivery\_policy](#input\_attach\_lb\_log\_delivery\_policy) | Controls if S3 bucket should have ALB/NLB log delivery policy attached | `bool` | `false` | no | +| [attach\_policy](#input\_attach\_policy) | Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy) | `bool` | `false` | no | +| [attach\_public\_policy](#input\_attach\_public\_policy) | Controls if a user defined public bucket policy will be attached (set to `false` to allow upstream to apply defaults to the bucket) | `bool` | `true` | no | +| [attach\_require\_latest\_tls\_policy](#input\_attach\_require\_latest\_tls\_policy) | Controls if S3 bucket should require the latest version of TLS | `bool` | `false` | no | +| [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket. | `bool` | `false` | no | +| [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket. | `bool` | `false` | no | +| [bucket](#input\_bucket) | (Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name. | `string` | `null` | no | +| [bucket\_prefix](#input\_bucket\_prefix) | (Optional, Forces new resource) Creates a unique bucket name beginning with the specified prefix. Conflicts with bucket. | `string` | `null` | no | +| [control\_object\_ownership](#input\_control\_object\_ownership) | Whether to manage S3 Bucket Ownership Controls on this bucket. | `bool` | `false` | no | +| [cors\_rule](#input\_cors\_rule) | List of maps containing rules for Cross-Origin Resource Sharing. | `any` | `[]` | no | +| [create\_bucket](#input\_create\_bucket) | Controls if S3 bucket should be created | `bool` | `true` | no | +| [expected\_bucket\_owner](#input\_expected\_bucket\_owner) | The account ID of the expected bucket owner | `string` | `null` | no | +| [force\_destroy](#input\_force\_destroy) | (Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable. | `bool` | `false` | no | +| [grant](#input\_grant) | An ACL policy grant. Conflicts with `acl` | `any` | `[]` | no | +| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket. | `bool` | `false` | no | +| [intelligent\_tiering](#input\_intelligent\_tiering) | Map containing intelligent tiering configuration. | `any` | `{}` | no | +| [lifecycle\_rule](#input\_lifecycle\_rule) | List of maps containing configuration of object lifecycle management. | `any` | `[]` | no | +| [logging](#input\_logging) | Map containing access bucket logging configuration. | `map(string)` | `{}` | no | +| [object\_lock\_configuration](#input\_object\_lock\_configuration) | Map containing S3 object locking configuration. | `any` | `{}` | no | +| [object\_lock\_enabled](#input\_object\_lock\_enabled) | Whether S3 bucket should have an Object Lock configuration enabled. | `bool` | `false` | no | +| [object\_ownership](#input\_object\_ownership) | Object ownership. Valid values: BucketOwnerEnforced, BucketOwnerPreferred or ObjectWriter. 'BucketOwnerEnforced': ACLs are disabled, and the bucket owner automatically owns and has full control over every object in the bucket. 'BucketOwnerPreferred': Objects uploaded to the bucket change ownership to the bucket owner if the objects are uploaded with the bucket-owner-full-control canned ACL. 'ObjectWriter': The uploading account will own the object if the object is uploaded with the bucket-owner-full-control canned ACL. | `string` | `"ObjectWriter"` | no | +| [owner](#input\_owner) | Bucket owner's display name and ID. Conflicts with `acl` | `map(string)` | `{}` | no | +| [policy](#input\_policy) | (Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide. | `string` | `null` | no | +| [replication\_configuration](#input\_replication\_configuration) | Map containing cross-region replication configuration. | `any` | `{}` | no | +| [request\_payer](#input\_request\_payer) | (Optional) Specifies who should bear the cost of Amazon S3 data transfer. Can be either BucketOwner or Requester. By default, the owner of the S3 bucket would incur the costs of any data transfer. See Requester Pays Buckets developer guide for more information. | `string` | `null` | no | +| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Whether Amazon S3 should restrict public bucket policies for this bucket. | `bool` | `false` | no | +| [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no | +| [tags](#input\_tags) | (Optional) A mapping of tags to assign to the bucket. | `map(string)` | `{}` | no | +| [versioning](#input\_versioning) | Map containing versioning configuration. | `map(string)` | `{}` | no | +| [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `any` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. | +| [s3\_bucket\_bucket\_domain\_name](#output\_s3\_bucket\_bucket\_domain\_name) | The bucket domain name. Will be of format bucketname.s3.amazonaws.com. | +| [s3\_bucket\_bucket\_regional\_domain\_name](#output\_s3\_bucket\_bucket\_regional\_domain\_name) | The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL. | +| [s3\_bucket\_hosted\_zone\_id](#output\_s3\_bucket\_hosted\_zone\_id) | The Route 53 Hosted Zone ID for this bucket's region. | +| [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. | +| [s3\_bucket\_region](#output\_s3\_bucket\_region) | The AWS region this bucket resides in. | +| [s3\_bucket\_website\_domain](#output\_s3\_bucket\_website\_domain) | The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records. | +| [s3\_bucket\_website\_endpoint](#output\_s3\_bucket\_website\_endpoint) | The website endpoint, if the bucket is configured with a website. If not, this will be an empty string. | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/LICENSE) for full details. + + diff --git a/modules/aws-s3/main.tf b/modules/aws-s3/main.tf new file mode 100644 index 0000000..cf02ef3 --- /dev/null +++ b/modules/aws-s3/main.tf @@ -0,0 +1,751 @@ +data "aws_canonical_user_id" "this" {} + +locals { + create_bucket = var.create_bucket + + attach_policy = var.attach_require_latest_tls_policy || var.attach_elb_log_delivery_policy || var.attach_lb_log_delivery_policy || var.attach_deny_insecure_transport_policy || var.attach_cross_account_policy || var.attach_policy + + # Variables with type `any` should be jsonencode()'d when value is coming from Terragrunt + grants = try(jsondecode(var.grant), var.grant) + cors_rules = try(jsondecode(var.cors_rule), var.cors_rule) + lifecycle_rules = try(jsondecode(var.lifecycle_rule), var.lifecycle_rule) + intelligent_tiering = try(jsondecode(var.intelligent_tiering), var.intelligent_tiering) +} + +resource "aws_s3_bucket" "this" { + count = local.create_bucket ? 1 : 0 + + bucket = var.bucket + bucket_prefix = var.bucket_prefix + + force_destroy = var.force_destroy + object_lock_enabled = var.object_lock_enabled + tags = var.tags +} + +resource "aws_s3_bucket_logging" "this" { + count = local.create_bucket && length(keys(var.logging)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + + target_bucket = var.logging["target_bucket"] + target_prefix = try(var.logging["target_prefix"], null) +} + +resource "aws_s3_bucket_acl" "this" { + count = local.create_bucket && ((var.acl != null && var.acl != "null") || length(local.grants) > 0) ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + # hack when `null` value can't be used (eg, from terragrunt, https://github.com/gruntwork-io/terragrunt/pull/1367) + acl = var.acl == "null" ? null : var.acl + + dynamic "access_control_policy" { + for_each = length(local.grants) > 0 ? [true] : [] + + content { + dynamic "grant" { + for_each = local.grants + + content { + permission = grant.value.permission + + grantee { + type = grant.value.type + id = try(grant.value.id, null) + uri = try(grant.value.uri, null) + email_address = try(grant.value.email, null) + } + } + } + + owner { + id = try(var.owner["id"], data.aws_canonical_user_id.this.id) + display_name = try(var.owner["display_name"], null) + } + } + } +} + +resource "aws_s3_bucket_website_configuration" "this" { + count = local.create_bucket && length(keys(var.website)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "index_document" { + for_each = try([var.website["index_document"]], []) + + content { + suffix = index_document.value + } + } + + dynamic "error_document" { + for_each = try([var.website["error_document"]], []) + + content { + key = error_document.value + } + } + + dynamic "redirect_all_requests_to" { + for_each = try([var.website["redirect_all_requests_to"]], []) + + content { + host_name = redirect_all_requests_to.value.host_name + protocol = try(redirect_all_requests_to.value.protocol, null) + } + } + + dynamic "routing_rule" { + for_each = try(flatten([var.website["routing_rules"]]), []) + + content { + dynamic "condition" { + for_each = [try([routing_rule.value.condition], [])] + + content { + http_error_code_returned_equals = try(routing_rule.value.condition["http_error_code_returned_equals"], null) + key_prefix_equals = try(routing_rule.value.condition["key_prefix_equals"], null) + } + } + + redirect { + host_name = try(routing_rule.value.redirect["host_name"], null) + http_redirect_code = try(routing_rule.value.redirect["http_redirect_code"], null) + protocol = try(routing_rule.value.redirect["protocol"], null) + replace_key_prefix_with = try(routing_rule.value.redirect["replace_key_prefix_with"], null) + replace_key_with = try(routing_rule.value.redirect["replace_key_with"], null) + } + } + } +} + +resource "aws_s3_bucket_versioning" "this" { + count = local.create_bucket && length(keys(var.versioning)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + mfa = try(var.versioning["mfa"], null) + + versioning_configuration { + # Valid values: "Enabled" or "Suspended" + status = try(var.versioning["enabled"] ? "Enabled" : "Suspended", tobool(var.versioning["status"]) ? "Enabled" : "Suspended", title(lower(var.versioning["status"]))) + + # Valid values: "Enabled" or "Disabled" + mfa_delete = try(tobool(var.versioning["mfa_delete"]) ? "Enabled" : "Disabled", title(lower(var.versioning["mfa_delete"])), null) + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + count = local.create_bucket && length(keys(var.server_side_encryption_configuration)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "rule" { + for_each = try(flatten([var.server_side_encryption_configuration["rule"]]), []) + + content { + bucket_key_enabled = try(rule.value.bucket_key_enabled, null) + + dynamic "apply_server_side_encryption_by_default" { + for_each = try([rule.value.apply_server_side_encryption_by_default], []) + + content { + sse_algorithm = apply_server_side_encryption_by_default.value.sse_algorithm + kms_master_key_id = try(apply_server_side_encryption_by_default.value.kms_master_key_id, null) + } + } + } + } +} + +resource "aws_s3_bucket_accelerate_configuration" "this" { + count = local.create_bucket && var.acceleration_status != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + # Valid values: "Enabled" or "Suspended" + status = title(lower(var.acceleration_status)) +} + +resource "aws_s3_bucket_request_payment_configuration" "this" { + count = local.create_bucket && var.request_payer != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + # Valid values: "BucketOwner" or "Requester" + payer = lower(var.request_payer) == "requester" ? "Requester" : "BucketOwner" +} + +resource "aws_s3_bucket_cors_configuration" "this" { + count = local.create_bucket && length(local.cors_rules) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "cors_rule" { + for_each = local.cors_rules + + content { + id = try(cors_rule.value.id, null) + allowed_methods = cors_rule.value.allowed_methods + allowed_origins = cors_rule.value.allowed_origins + allowed_headers = try(cors_rule.value.allowed_headers, null) + expose_headers = try(cors_rule.value.expose_headers, null) + max_age_seconds = try(cors_rule.value.max_age_seconds, null) + } + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "this" { + count = local.create_bucket && length(local.lifecycle_rules) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "rule" { + for_each = local.lifecycle_rules + + content { + id = try(rule.value.id, null) + status = try(rule.value.enabled ? "Enabled" : "Disabled", tobool(rule.value.status) ? "Enabled" : "Disabled", title(lower(rule.value.status))) + + # Max 1 block - abort_incomplete_multipart_upload + dynamic "abort_incomplete_multipart_upload" { + for_each = try([rule.value.abort_incomplete_multipart_upload_days], []) + + content { + days_after_initiation = try(rule.value.abort_incomplete_multipart_upload_days, null) + } + } + + + # Max 1 block - expiration + dynamic "expiration" { + for_each = try(flatten([rule.value.expiration]), []) + + content { + date = try(expiration.value.date, null) + days = try(expiration.value.days, null) + expired_object_delete_marker = try(expiration.value.expired_object_delete_marker, null) + } + } + + # Several blocks - transition + dynamic "transition" { + for_each = try(flatten([rule.value.transition]), []) + + content { + date = try(transition.value.date, null) + days = try(transition.value.days, null) + storage_class = transition.value.storage_class + } + } + + # Max 1 block - noncurrent_version_expiration + dynamic "noncurrent_version_expiration" { + for_each = try(flatten([rule.value.noncurrent_version_expiration]), []) + + content { + newer_noncurrent_versions = try(noncurrent_version_expiration.value.newer_noncurrent_versions, null) + noncurrent_days = try(noncurrent_version_expiration.value.days, noncurrent_version_expiration.value.noncurrent_days, null) + } + } + + # Several blocks - noncurrent_version_transition + dynamic "noncurrent_version_transition" { + for_each = try(flatten([rule.value.noncurrent_version_transition]), []) + + content { + newer_noncurrent_versions = try(noncurrent_version_transition.value.newer_noncurrent_versions, null) + noncurrent_days = try(noncurrent_version_transition.value.days, noncurrent_version_transition.value.noncurrent_days, null) + storage_class = noncurrent_version_transition.value.storage_class + } + } + + # Max 1 block - filter - without any key arguments or tags + dynamic "filter" { + for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : [] + + content { + # prefix = "" + } + } + + # Max 1 block - filter - with one key argument or a single tag + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) == 1] + + content { + object_size_greater_than = try(filter.value.object_size_greater_than, null) + object_size_less_than = try(filter.value.object_size_less_than, null) + prefix = try(filter.value.prefix, null) + + dynamic "tag" { + for_each = try(filter.value.tags, filter.value.tag, []) + + content { + key = tag.key + value = tag.value + } + } + } + } + + # Max 1 block - filter - with more than one key arguments or multiple tags + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) > 1] + + content { + and { + object_size_greater_than = try(filter.value.object_size_greater_than, null) + object_size_less_than = try(filter.value.object_size_less_than, null) + prefix = try(filter.value.prefix, null) + tags = try(filter.value.tags, filter.value.tag, null) + } + } + } + } + } + + # Must have bucket versioning enabled first + depends_on = [aws_s3_bucket_versioning.this] +} + +resource "aws_s3_bucket_object_lock_configuration" "this" { + count = local.create_bucket && var.object_lock_enabled && try(var.object_lock_configuration.rule.default_retention, null) != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + token = try(var.object_lock_configuration.token, null) + + rule { + default_retention { + mode = var.object_lock_configuration.rule.default_retention.mode + days = try(var.object_lock_configuration.rule.default_retention.days, null) + years = try(var.object_lock_configuration.rule.default_retention.years, null) + } + } +} + +resource "aws_s3_bucket_replication_configuration" "this" { + count = local.create_bucket && length(keys(var.replication_configuration)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + role = var.replication_configuration["role"] + + dynamic "rule" { + for_each = flatten(try([var.replication_configuration["rule"]], [var.replication_configuration["rules"]], [])) + + content { + id = try(rule.value.id, null) + priority = try(rule.value.priority, null) + prefix = try(rule.value.prefix, null) + status = try(tobool(rule.value.status) ? "Enabled" : "Disabled", title(lower(rule.value.status)), "Enabled") + + dynamic "delete_marker_replication" { + for_each = flatten(try([rule.value.delete_marker_replication_status], [rule.value.delete_marker_replication], [])) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(delete_marker_replication.value) ? "Enabled" : "Disabled", title(lower(delete_marker_replication.value))) + } + } + + # Amazon S3 does not support this argument according to: + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration + # More infor about what does Amazon S3 replicate? + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-what-is-isnot-replicated.html + dynamic "existing_object_replication" { + for_each = flatten(try([rule.value.existing_object_replication_status], [rule.value.existing_object_replication], [])) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(existing_object_replication.value) ? "Enabled" : "Disabled", title(lower(existing_object_replication.value))) + } + } + + dynamic "destination" { + for_each = try(flatten([rule.value.destination]), []) + + content { + bucket = destination.value.bucket + storage_class = try(destination.value.storage_class, null) + account = try(destination.value.account_id, destination.value.account, null) + + dynamic "access_control_translation" { + for_each = try(flatten([destination.value.access_control_translation]), []) + + content { + owner = title(lower(access_control_translation.value.owner)) + } + } + + dynamic "encryption_configuration" { + for_each = flatten([try(destination.value.encryption_configuration.replica_kms_key_id, destination.value.replica_kms_key_id, [])]) + + content { + replica_kms_key_id = encryption_configuration.value + } + } + + dynamic "replication_time" { + for_each = try(flatten([destination.value.replication_time]), []) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(replication_time.value.status) ? "Enabled" : "Disabled", title(lower(replication_time.value.status)), "Disabled") + + dynamic "time" { + for_each = try(flatten([replication_time.value.minutes]), []) + + content { + minutes = replication_time.value.minutes + } + } + } + + } + + dynamic "metrics" { + for_each = try(flatten([destination.value.metrics]), []) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(metrics.value.status) ? "Enabled" : "Disabled", title(lower(metrics.value.status)), "Disabled") + + dynamic "event_threshold" { + for_each = try(flatten([metrics.value.minutes]), []) + + content { + minutes = metrics.value.minutes + } + } + } + } + } + } + + dynamic "source_selection_criteria" { + for_each = try(flatten([rule.value.source_selection_criteria]), []) + + content { + dynamic "replica_modifications" { + for_each = flatten([try(source_selection_criteria.value.replica_modifications.enabled, source_selection_criteria.value.replica_modifications.status, [])]) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(replica_modifications.value) ? "Enabled" : "Disabled", title(lower(replica_modifications.value)), "Disabled") + } + } + + dynamic "sse_kms_encrypted_objects" { + for_each = flatten([try(source_selection_criteria.value.sse_kms_encrypted_objects.enabled, source_selection_criteria.value.sse_kms_encrypted_objects.status, [])]) + + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(sse_kms_encrypted_objects.value) ? "Enabled" : "Disabled", title(lower(sse_kms_encrypted_objects.value)), "Disabled") + } + } + } + } + + # Max 1 block - filter - without any key arguments or tags + dynamic "filter" { + for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : [] + + content { + } + } + + # Max 1 block - filter - with one key argument or a single tag + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) == 1] + + content { + prefix = try(filter.value.prefix, null) + + dynamic "tag" { + for_each = try(filter.value.tags, filter.value.tag, []) + + content { + key = tag.key + value = tag.value + } + } + } + } + + # Max 1 block - filter - with more than one key arguments or multiple tags + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) > 1] + + content { + and { + prefix = try(filter.value.prefix, null) + tags = try(filter.value.tags, filter.value.tag, null) + } + } + } + } + } + + # Must have bucket versioning enabled first + depends_on = [aws_s3_bucket_versioning.this] +} + +resource "aws_s3_bucket_policy" "this" { + count = local.create_bucket && local.attach_policy ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + policy = data.aws_iam_policy_document.combined[0].json +} + +data "aws_iam_policy_document" "combined" { + count = local.create_bucket && local.attach_policy ? 1 : 0 + + source_policy_documents = compact([ + var.attach_elb_log_delivery_policy ? data.aws_iam_policy_document.elb_log_delivery[0].json : "", + var.attach_lb_log_delivery_policy ? data.aws_iam_policy_document.lb_log_delivery[0].json : "", + var.attach_require_latest_tls_policy ? data.aws_iam_policy_document.require_latest_tls[0].json : "", + var.attach_deny_insecure_transport_policy ? data.aws_iam_policy_document.deny_insecure_transport[0].json : "", + var.attach_cross_account_policy ? data.aws_iam_policy_document.s3_private_cross_account_policy[0].json : "", + var.attach_policy ? var.policy : "" + ]) +} + +# AWS Load Balancer access log delivery policy +data "aws_elb_service_account" "this" { + count = local.create_bucket && var.attach_elb_log_delivery_policy ? 1 : 0 +} + +data "aws_iam_policy_document" "elb_log_delivery" { + count = local.create_bucket && var.attach_elb_log_delivery_policy ? 1 : 0 + + statement { + sid = "" + + principals { + type = "AWS" + identifiers = data.aws_elb_service_account.this.*.arn + } + + effect = "Allow" + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${aws_s3_bucket.this[0].arn}/*", + ] + } +} + +# ALB/NLB + +data "aws_iam_policy_document" "lb_log_delivery" { + count = local.create_bucket && var.attach_lb_log_delivery_policy ? 1 : 0 + + statement { + sid = "AWSLogDeliveryWrite" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + effect = "Allow" + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${aws_s3_bucket.this[0].arn}/*", + ] + + condition { + test = "StringEquals" + variable = "s3:x-amz-acl" + values = ["bucket-owner-full-control"] + } + } + + statement { + sid = "AWSLogDeliveryAclCheck" + + effect = "Allow" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = [ + "s3:GetBucketAcl", + ] + + resources = [ + aws_s3_bucket.this[0].arn, + ] + + } +} + +data "aws_iam_policy_document" "s3_private_cross_account_policy" { + count = local.create_bucket && var.attach_cross_account_policy ? 1 : 0 + statement { + sid = "1" + + actions = [ + "s3:GetBucketLocation", + "s3:GetBucketVersioning", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListBucket", + ] + + effect = "Allow" + + resources = [ + "arn:aws:s3:::${var.bucket}", + "arn:aws:s3:::${var.bucket}/*", + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.cross_account_id}:root", + ] + } + } +} + +data "aws_iam_policy_document" "deny_insecure_transport" { + count = local.create_bucket && var.attach_deny_insecure_transport_policy ? 1 : 0 + + statement { + sid = "denyInsecureTransport" + effect = "Deny" + + actions = [ + "s3:*", + ] + + resources = [ + aws_s3_bucket.this[0].arn, + "${aws_s3_bucket.this[0].arn}/*", + ] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = [ + "false" + ] + } + } +} + +data "aws_iam_policy_document" "require_latest_tls" { + count = local.create_bucket && var.attach_require_latest_tls_policy ? 1 : 0 + + statement { + sid = "denyOutdatedTLS" + effect = "Deny" + + actions = [ + "s3:*", + ] + + resources = [ + aws_s3_bucket.this[0].arn, + "${aws_s3_bucket.this[0].arn}/*", + ] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "NumericLessThan" + variable = "s3:TlsVersion" + values = [ + "1.2" + ] + } + } +} + +resource "aws_s3_bucket_public_access_block" "this" { + count = local.create_bucket && var.attach_public_policy ? 1 : 0 + + # Chain resources (s3_bucket -> s3_bucket_policy -> s3_bucket_public_access_block) + # to prevent "A conflicting conditional operation is currently in progress against this resource." + # Ref: https://github.com/hashicorp/terraform-provider-aws/issues/7628 + + bucket = local.attach_policy ? aws_s3_bucket_policy.this[0].id : aws_s3_bucket.this[0].id + + block_public_acls = var.block_public_acls + block_public_policy = var.block_public_policy + ignore_public_acls = var.ignore_public_acls + restrict_public_buckets = var.restrict_public_buckets +} + +resource "aws_s3_bucket_ownership_controls" "this" { + count = local.create_bucket && var.control_object_ownership ? 1 : 0 + + bucket = local.attach_policy ? aws_s3_bucket_policy.this[0].id : aws_s3_bucket.this[0].id + + rule { + object_ownership = var.object_ownership + } + + # This `depends_on` is to prevent "A conflicting conditional operation is currently in progress against this resource." + depends_on = [ + aws_s3_bucket_policy.this, + aws_s3_bucket_public_access_block.this, + aws_s3_bucket.this + ] +} + +resource "aws_s3_bucket_intelligent_tiering_configuration" "this" { + for_each = { for k, v in local.intelligent_tiering : k => v if local.create_bucket } + + name = each.key + bucket = aws_s3_bucket.this[0].id + status = try(tobool(each.value.status) ? "Enabled" : "Disabled", title(lower(each.value.status)), null) + + # Max 1 block - filter + dynamic "filter" { + for_each = length(try(flatten([each.value.filter]), [])) == 0 ? [] : [true] + + content { + prefix = try(each.value.filter.prefix, null) + tags = try(each.value.filter.tags, null) + } + } + + dynamic "tiering" { + for_each = each.value.tiering + + content { + access_tier = tiering.key + days = tiering.value.days + } + } + +} diff --git a/modules/aws-s3/modules/notification/README.md b/modules/aws-s3/modules/notification/README.md new file mode 100644 index 0000000..d93f19a --- /dev/null +++ b/modules/aws-s3/modules/notification/README.md @@ -0,0 +1,55 @@ +# S3 bucket notification + +Creates S3 bucket notification resource with all supported types of deliveries: AWS Lambda, SQS Queue, SNS Topic. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.74 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.74 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_lambda_permission.allow](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_s3_bucket_notification.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_notification) | resource | +| [aws_sns_topic_policy.allow](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | +| [aws_sqs_queue_policy.allow](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_arn.queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_iam_policy_document.sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [bucket](#input\_bucket) | Name of S3 bucket to use | `string` | `""` | no | +| [bucket\_arn](#input\_bucket\_arn) | ARN of S3 bucket to use in policies | `string` | `null` | no | +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [create\_sns\_policy](#input\_create\_sns\_policy) | Whether to create a policy for SNS permissions or not? | `bool` | `true` | no | +| [create\_sqs\_policy](#input\_create\_sqs\_policy) | Whether to create a policy for SQS permissions or not? | `bool` | `true` | no | +| [eventbridge](#input\_eventbridge) | Whether to enable Amazon EventBridge notifications | `bool` | `null` | no | +| [lambda\_notifications](#input\_lambda\_notifications) | Map of S3 bucket notifications to Lambda function | `any` | `{}` | no | +| [sns\_notifications](#input\_sns\_notifications) | Map of S3 bucket notifications to SNS topic | `any` | `{}` | no | +| [sqs\_notifications](#input\_sqs\_notifications) | Map of S3 bucket notifications to SQS queue | `any` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [s3\_bucket\_notification\_id](#output\_s3\_bucket\_notification\_id) | ID of S3 bucket | + diff --git a/modules/aws-s3/modules/notification/main.tf b/modules/aws-s3/modules/notification/main.tf new file mode 100644 index 0000000..733535c --- /dev/null +++ b/modules/aws-s3/modules/notification/main.tf @@ -0,0 +1,146 @@ +data "aws_partition" "this" {} + +locals { + bucket_arn = coalesce(var.bucket_arn, "arn:${data.aws_partition.this.partition}:s3:::${var.bucket}") + + queue_ids = { for k, v in var.sqs_notifications : k => format("https://%s.%s.amazonaws.com/%s/%s", data.aws_arn.queue[k].service, data.aws_arn.queue[k].region, data.aws_arn.queue[k].account, data.aws_arn.queue[k].resource) if try(v.queue_id, "") == "" } +} + +resource "aws_s3_bucket_notification" "this" { + count = var.create ? 1 : 0 + + bucket = var.bucket + + eventbridge = var.eventbridge + + dynamic "lambda_function" { + for_each = var.lambda_notifications + + content { + id = lambda_function.key + lambda_function_arn = lambda_function.value.function_arn + events = lambda_function.value.events + filter_prefix = try(lambda_function.value.filter_prefix, null) + filter_suffix = try(lambda_function.value.filter_suffix, null) + } + } + + dynamic "queue" { + for_each = var.sqs_notifications + + content { + id = queue.key + queue_arn = queue.value.queue_arn + events = queue.value.events + filter_prefix = try(queue.value.filter_prefix, null) + filter_suffix = try(queue.value.filter_suffix, null) + } + } + + dynamic "topic" { + for_each = var.sns_notifications + + content { + id = topic.key + topic_arn = topic.value.topic_arn + events = topic.value.events + filter_prefix = try(topic.value.filter_prefix, null) + filter_suffix = try(topic.value.filter_suffix, null) + } + } + + depends_on = [ + aws_lambda_permission.allow, + aws_sqs_queue_policy.allow, + aws_sns_topic_policy.allow, + ] +} + +# Lambda +resource "aws_lambda_permission" "allow" { + for_each = var.lambda_notifications + + statement_id_prefix = "AllowLambdaS3BucketNotification-" + action = "lambda:InvokeFunction" + function_name = each.value.function_name + qualifier = try(each.value.qualifier, null) + principal = "s3.amazonaws.com" + source_arn = local.bucket_arn + source_account = try(each.value.source_account, null) +} + +# SQS Queue +data "aws_arn" "queue" { + for_each = var.sqs_notifications + + arn = each.value.queue_arn +} + +data "aws_iam_policy_document" "sqs" { + for_each = { for k, v in var.sqs_notifications : k => v if var.create_sqs_policy } + + statement { + sid = "AllowSQSS3BucketNotification" + + effect = "Allow" + + actions = [ + "sqs:SendMessage", + ] + + principals { + type = "Service" + identifiers = ["s3.amazonaws.com"] + } + + resources = [each.value.queue_arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [local.bucket_arn] + } + } +} + +resource "aws_sqs_queue_policy" "allow" { + for_each = { for k, v in var.sqs_notifications : k => v if var.create_sqs_policy } + + queue_url = try(each.value.queue_id, local.queue_ids[each.key], null) + policy = data.aws_iam_policy_document.sqs[each.key].json +} + +# SNS Topic +data "aws_iam_policy_document" "sns" { + for_each = { for k, v in var.sns_notifications : k => v if var.create_sns_policy } + + statement { + sid = "AllowSNSS3BucketNotification" + + effect = "Allow" + + actions = [ + "sns:Publish", + ] + + principals { + type = "Service" + identifiers = ["s3.amazonaws.com"] + } + + resources = [each.value.topic_arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [local.bucket_arn] + } + } +} + +resource "aws_sns_topic_policy" "allow" { + for_each = { for k, v in var.sns_notifications : k => v if var.create_sns_policy } + + arn = each.value.topic_arn + policy = data.aws_iam_policy_document.sns[each.key].json +} diff --git a/modules/aws-s3/modules/notification/outputs.tf b/modules/aws-s3/modules/notification/outputs.tf new file mode 100644 index 0000000..6c08c23 --- /dev/null +++ b/modules/aws-s3/modules/notification/outputs.tf @@ -0,0 +1,4 @@ +output "s3_bucket_notification_id" { + description = "ID of S3 bucket" + value = try(aws_s3_bucket_notification.this[0].id, "") +} diff --git a/modules/aws-s3/modules/notification/variables.tf b/modules/aws-s3/modules/notification/variables.tf new file mode 100644 index 0000000..16f8193 --- /dev/null +++ b/modules/aws-s3/modules/notification/variables.tf @@ -0,0 +1,53 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "create_sns_policy" { + description = "Whether to create a policy for SNS permissions or not?" + type = bool + default = true +} + +variable "create_sqs_policy" { + description = "Whether to create a policy for SQS permissions or not?" + type = bool + default = true +} + +variable "bucket" { + description = "Name of S3 bucket to use" + type = string + default = "" +} + +variable "bucket_arn" { + description = "ARN of S3 bucket to use in policies" + type = string + default = null +} + +variable "eventbridge" { + description = "Whether to enable Amazon EventBridge notifications" + type = bool + default = null +} + +variable "lambda_notifications" { + description = "Map of S3 bucket notifications to Lambda function" + type = any + default = {} +} + +variable "sqs_notifications" { + description = "Map of S3 bucket notifications to SQS queue" + type = any + default = {} +} + +variable "sns_notifications" { + description = "Map of S3 bucket notifications to SNS topic" + type = any + default = {} +} diff --git a/modules/aws-s3/modules/notification/versions.tf b/modules/aws-s3/modules/notification/versions.tf new file mode 100644 index 0000000..538b915 --- /dev/null +++ b/modules/aws-s3/modules/notification/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.74" + } + } +} diff --git a/modules/aws-s3/modules/object/README.md b/modules/aws-s3/modules/object/README.md new file mode 100644 index 0000000..ea2405c --- /dev/null +++ b/modules/aws-s3/modules/object/README.md @@ -0,0 +1,66 @@ +# S3 bucket object + +Creates S3 bucket objects with different configurations. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.75 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.75 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_s3_object.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acl](#input\_acl) | The canned ACL to apply. Valid values are private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, and bucket-owner-full-control. Defaults to private. | `string` | `null` | no | +| [bucket](#input\_bucket) | The name of the bucket to put the file in. Alternatively, an S3 access point ARN can be specified. | `string` | `""` | no | +| [bucket\_key\_enabled](#input\_bucket\_key\_enabled) | Whether or not to use Amazon S3 Bucket Keys for SSE-KMS. | `bool` | `null` | no | +| [cache\_control](#input\_cache\_control) | Specifies caching behavior along the request/reply chain. | `string` | `null` | no | +| [content](#input\_content) | Literal string value to use as the object content, which will be uploaded as UTF-8-encoded text. | `string` | `null` | no | +| [content\_base64](#input\_content\_base64) | Base64-encoded data that will be decoded and uploaded as raw bytes for the object content. This allows safely uploading non-UTF8 binary data, but is recommended only for small content such as the result of the gzipbase64 function with small text strings. For larger objects, use source to stream the content from a disk file. | `string` | `null` | no | +| [content\_disposition](#input\_content\_disposition) | Specifies presentational information for the object. | `string` | `null` | no | +| [content\_encoding](#input\_content\_encoding) | Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field. | `string` | `null` | no | +| [content\_language](#input\_content\_language) | The language the content is in e.g. en-US or en-GB. | `string` | `null` | no | +| [content\_type](#input\_content\_type) | A standard MIME type describing the format of the object data, e.g. application/octet-stream. All Valid MIME Types are valid for this input. | `string` | `null` | no | +| [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | +| [etag](#input\_etag) | Used to trigger updates. This attribute is not compatible with KMS encryption, kms\_key\_id or server\_side\_encryption = "aws:kms". | `string` | `null` | no | +| [file\_source](#input\_file\_source) | The path to a file that will be read and uploaded as raw bytes for the object content. | `string` | `null` | no | +| [force\_destroy](#input\_force\_destroy) | Allow the object to be deleted by removing any legal hold on any object version. Default is false. This value should be set to true only if the bucket has S3 object lock enabled. | `bool` | `false` | no | +| [key](#input\_key) | The name of the object once it is in the bucket. | `string` | `""` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | Amazon Resource Name (ARN) of the KMS Key to use for object encryption. If the S3 Bucket has server-side encryption enabled, that value will automatically be used. If referencing the aws\_kms\_key resource, use the arn attribute. If referencing the aws\_kms\_alias data source or resource, use the target\_key\_arn attribute. Terraform will only perform drift detection if a configuration value is provided. | `string` | `null` | no | +| [metadata](#input\_metadata) | A map of keys/values to provision metadata (will be automatically prefixed by x-amz-meta-, note that only lowercase label are currently supported by the AWS Go API). | `map(string)` | `{}` | no | +| [object\_lock\_legal\_hold\_status](#input\_object\_lock\_legal\_hold\_status) | The legal hold status that you want to apply to the specified object. Valid values are ON and OFF. | `string` | `null` | no | +| [object\_lock\_mode](#input\_object\_lock\_mode) | The object lock retention mode that you want to apply to this object. Valid values are GOVERNANCE and COMPLIANCE. | `string` | `null` | no | +| [object\_lock\_retain\_until\_date](#input\_object\_lock\_retain\_until\_date) | The date and time, in RFC3339 format, when this object's object lock will expire. | `string` | `null` | no | +| [server\_side\_encryption](#input\_server\_side\_encryption) | Specifies server-side encryption of the object in S3. Valid values are "AES256" and "aws:kms". | `string` | `null` | no | +| [source\_hash](#input\_source\_hash) | Triggers updates like etag but useful to address etag encryption limitations. Set using filemd5("path/to/source") (Terraform 0.11.12 or later). (The value is only stored in state and not saved by AWS.) | `string` | `null` | no | +| [storage\_class](#input\_storage\_class) | Specifies the desired Storage Class for the object. Can be either STANDARD, REDUCED\_REDUNDANCY, ONEZONE\_IA, INTELLIGENT\_TIERING, GLACIER, DEEP\_ARCHIVE, or STANDARD\_IA. Defaults to STANDARD. | `string` | `null` | no | +| [tags](#input\_tags) | A map of tags to assign to the object. | `map(string)` | `{}` | no | +| [website\_redirect](#input\_website\_redirect) | Specifies a target URL for website redirect. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [s3\_object\_etag](#output\_s3\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). | +| [s3\_object\_id](#output\_s3\_object\_id) | The key of S3 object | +| [s3\_object\_version\_id](#output\_s3\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. | + diff --git a/modules/aws-s3/modules/object/main.tf b/modules/aws-s3/modules/object/main.tf new file mode 100644 index 0000000..a1eecef --- /dev/null +++ b/modules/aws-s3/modules/object/main.tf @@ -0,0 +1,39 @@ +resource "aws_s3_object" "this" { + count = var.create ? 1 : 0 + + bucket = var.bucket + key = var.key + force_destroy = var.force_destroy + + acl = var.acl + storage_class = try(upper(var.storage_class), var.storage_class) + + source = var.file_source + content = var.content + content_base64 = var.content_base64 + etag = var.etag + + cache_control = var.cache_control + content_disposition = var.content_disposition + content_encoding = var.content_encoding + content_language = var.content_language + content_type = var.content_type + website_redirect = var.website_redirect + metadata = var.metadata + + server_side_encryption = var.server_side_encryption + kms_key_id = var.kms_key_id + bucket_key_enabled = var.bucket_key_enabled + + object_lock_legal_hold_status = try(tobool(var.object_lock_legal_hold_status) ? "ON" : upper(var.object_lock_legal_hold_status), var.object_lock_legal_hold_status) + object_lock_mode = try(upper(var.object_lock_mode), var.object_lock_mode) + object_lock_retain_until_date = var.object_lock_retain_until_date + + source_hash = var.source_hash + + tags = var.tags + + lifecycle { + ignore_changes = [object_lock_retain_until_date] + } +} diff --git a/modules/aws-s3/modules/object/outputs.tf b/modules/aws-s3/modules/object/outputs.tf new file mode 100644 index 0000000..5cdb1db --- /dev/null +++ b/modules/aws-s3/modules/object/outputs.tf @@ -0,0 +1,14 @@ +output "s3_object_id" { + description = "The key of S3 object" + value = try(aws_s3_object.this[0].id, "") +} + +output "s3_object_etag" { + description = "The ETag generated for the object (an MD5 sum of the object content)." + value = try(aws_s3_object.this[0].etag, "") +} + +output "s3_object_version_id" { + description = "A unique version ID value for the object, if bucket versioning is enabled." + value = try(aws_s3_object.this[0].version_id, "") +} diff --git a/modules/aws-s3/modules/object/variables.tf b/modules/aws-s3/modules/object/variables.tf new file mode 100644 index 0000000..6f07f39 --- /dev/null +++ b/modules/aws-s3/modules/object/variables.tf @@ -0,0 +1,149 @@ +variable "create" { + description = "Whether to create this resource or not?" + type = bool + default = true +} + +variable "bucket" { + description = "The name of the bucket to put the file in. Alternatively, an S3 access point ARN can be specified." + type = string + default = "" +} + +variable "key" { + description = "The name of the object once it is in the bucket." + type = string + default = "" +} + +variable "file_source" { + description = "The path to a file that will be read and uploaded as raw bytes for the object content." + type = string + default = null +} + +variable "content" { + description = "Literal string value to use as the object content, which will be uploaded as UTF-8-encoded text." + type = string + default = null +} + +variable "content_base64" { + description = "Base64-encoded data that will be decoded and uploaded as raw bytes for the object content. This allows safely uploading non-UTF8 binary data, but is recommended only for small content such as the result of the gzipbase64 function with small text strings. For larger objects, use source to stream the content from a disk file." + type = string + default = null +} + +variable "acl" { + description = "The canned ACL to apply. Valid values are private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, and bucket-owner-full-control. Defaults to private." + type = string + default = null +} + +variable "cache_control" { + description = "Specifies caching behavior along the request/reply chain." + type = string # map? + default = null +} + +variable "content_disposition" { + description = "Specifies presentational information for the object." + type = string # map? + default = null +} + +variable "content_encoding" { + description = "Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field." + type = string + default = null +} + +variable "content_language" { + description = "The language the content is in e.g. en-US or en-GB." + type = string + default = null +} + +variable "content_type" { + description = "A standard MIME type describing the format of the object data, e.g. application/octet-stream. All Valid MIME Types are valid for this input." + type = string + default = null +} + +variable "website_redirect" { + description = "Specifies a target URL for website redirect." + type = string + default = null +} + +variable "storage_class" { + description = "Specifies the desired Storage Class for the object. Can be either STANDARD, REDUCED_REDUNDANCY, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER, DEEP_ARCHIVE, or STANDARD_IA. Defaults to STANDARD." + type = string + default = null +} + +variable "etag" { + description = "Used to trigger updates. This attribute is not compatible with KMS encryption, kms_key_id or server_side_encryption = \"aws:kms\"." + type = string + default = null +} + +variable "server_side_encryption" { + description = "Specifies server-side encryption of the object in S3. Valid values are \"AES256\" and \"aws:kms\"." + type = string + default = null +} + +variable "kms_key_id" { + description = "Amazon Resource Name (ARN) of the KMS Key to use for object encryption. If the S3 Bucket has server-side encryption enabled, that value will automatically be used. If referencing the aws_kms_key resource, use the arn attribute. If referencing the aws_kms_alias data source or resource, use the target_key_arn attribute. Terraform will only perform drift detection if a configuration value is provided." + type = string + default = null +} + +variable "bucket_key_enabled" { + description = "Whether or not to use Amazon S3 Bucket Keys for SSE-KMS." + type = bool + default = null +} + +variable "metadata" { + description = "A map of keys/values to provision metadata (will be automatically prefixed by x-amz-meta-, note that only lowercase label are currently supported by the AWS Go API)." + type = map(string) + default = {} +} + +variable "tags" { + description = "A map of tags to assign to the object." + type = map(string) + default = {} +} + +variable "force_destroy" { + description = "Allow the object to be deleted by removing any legal hold on any object version. Default is false. This value should be set to true only if the bucket has S3 object lock enabled." + type = bool + default = false +} + +variable "object_lock_legal_hold_status" { + description = "The legal hold status that you want to apply to the specified object. Valid values are ON and OFF." + type = string + default = null +} + +variable "object_lock_mode" { + description = "The object lock retention mode that you want to apply to this object. Valid values are GOVERNANCE and COMPLIANCE." + type = string + default = null +} + +variable "object_lock_retain_until_date" { + description = "The date and time, in RFC3339 format, when this object's object lock will expire." + type = string + default = null +} + +variable "source_hash" { + description = "Triggers updates like etag but useful to address etag encryption limitations. Set using filemd5(\"path/to/source\") (Terraform 0.11.12 or later). (The value is only stored in state and not saved by AWS.)" + type = string + default = null +} diff --git a/modules/aws-s3/modules/object/versions.tf b/modules/aws-s3/modules/object/versions.tf new file mode 100644 index 0000000..8a65df6 --- /dev/null +++ b/modules/aws-s3/modules/object/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.75" + } + } +} diff --git a/modules/aws-s3/outputs.tf b/modules/aws-s3/outputs.tf new file mode 100644 index 0000000..518cf43 --- /dev/null +++ b/modules/aws-s3/outputs.tf @@ -0,0 +1,39 @@ +output "s3_bucket_id" { + description = "The name of the bucket." + value = try(aws_s3_bucket_policy.this[0].id, aws_s3_bucket.this[0].id, "") +} + +output "s3_bucket_arn" { + description = "The ARN of the bucket. Will be of format arn:aws:s3:::bucketname." + value = try(aws_s3_bucket.this[0].arn, "") +} + +output "s3_bucket_bucket_domain_name" { + description = "The bucket domain name. Will be of format bucketname.s3.amazonaws.com." + value = try(aws_s3_bucket.this[0].bucket_domain_name, "") +} + +output "s3_bucket_bucket_regional_domain_name" { + description = "The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL." + value = try(aws_s3_bucket.this[0].bucket_regional_domain_name, "") +} + +output "s3_bucket_hosted_zone_id" { + description = "The Route 53 Hosted Zone ID for this bucket's region." + value = try(aws_s3_bucket.this[0].hosted_zone_id, "") +} + +output "s3_bucket_region" { + description = "The AWS region this bucket resides in." + value = try(aws_s3_bucket.this[0].region, "") +} + +output "s3_bucket_website_endpoint" { + description = "The website endpoint, if the bucket is configured with a website. If not, this will be an empty string." + value = try(aws_s3_bucket_website_configuration.this[0].website_endpoint, "") +} + +output "s3_bucket_website_domain" { + description = "The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records." + value = try(aws_s3_bucket_website_configuration.this[0].website_domain, "") +} diff --git a/modules/aws-s3/variables.tf b/modules/aws-s3/variables.tf new file mode 100644 index 0000000..d7fa7bd --- /dev/null +++ b/modules/aws-s3/variables.tf @@ -0,0 +1,218 @@ +variable "create_bucket" { + description = "Controls if S3 bucket should be created" + type = bool + default = true +} + +variable "attach_elb_log_delivery_policy" { + description = "Controls if S3 bucket should have ELB log delivery policy attached" + type = bool + default = false +} + +variable "attach_cross_account_policy" { + description = "Controls if S3 bucket should have bucket cross account policy attached." + type = bool + default = false +} + +variable "cross_account_id" { + type = string + description = "Account ID for cross account bucket access" + default = "" + +} + +variable "attach_lb_log_delivery_policy" { + description = "Controls if S3 bucket should have ALB/NLB log delivery policy attached" + type = bool + default = false +} + +variable "attach_deny_insecure_transport_policy" { + description = "Controls if S3 bucket should have deny non-SSL transport policy attached" + type = bool + default = false +} + +variable "attach_require_latest_tls_policy" { + description = "Controls if S3 bucket should require the latest version of TLS" + type = bool + default = false +} + +variable "attach_policy" { + description = "Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy)" + type = bool + default = false +} + +variable "attach_public_policy" { + description = "Controls if a user defined public bucket policy will be attached (set to `false` to allow upstream to apply defaults to the bucket)" + type = bool + default = true +} + +variable "bucket" { + description = "(Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name." + type = string + default = null +} + +variable "bucket_prefix" { + description = "(Optional, Forces new resource) Creates a unique bucket name beginning with the specified prefix. Conflicts with bucket." + type = string + default = null +} + +variable "acl" { + description = "(Optional) The canned ACL to apply. Conflicts with `grant`" + type = string + default = null +} + +variable "policy" { + description = "(Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide." + type = string + default = null +} + +variable "tags" { + description = "(Optional) A mapping of tags to assign to the bucket." + type = map(string) + default = {} +} + +variable "force_destroy" { + description = "(Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable." + type = bool + default = false +} + +variable "acceleration_status" { + description = "(Optional) Sets the accelerate configuration of an existing bucket. Can be Enabled or Suspended." + type = string + default = null +} + +variable "request_payer" { + description = "(Optional) Specifies who should bear the cost of Amazon S3 data transfer. Can be either BucketOwner or Requester. By default, the owner of the S3 bucket would incur the costs of any data transfer. See Requester Pays Buckets developer guide for more information." + type = string + default = null +} + +variable "website" { + description = "Map containing static web-site hosting or redirect configuration." + type = any # map(string) + default = {} +} + +variable "cors_rule" { + description = "List of maps containing rules for Cross-Origin Resource Sharing." + type = any + default = [] +} + +variable "versioning" { + description = "Map containing versioning configuration." + type = map(string) + default = {} +} + +variable "logging" { + description = "Map containing access bucket logging configuration." + type = map(string) + default = {} +} + +variable "grant" { + description = "An ACL policy grant. Conflicts with `acl`" + type = any + default = [] +} + +variable "owner" { + description = "Bucket owner's display name and ID. Conflicts with `acl`" + type = map(string) + default = {} +} + +variable "expected_bucket_owner" { + description = "The account ID of the expected bucket owner" + type = string + default = null +} + +variable "lifecycle_rule" { + description = "List of maps containing configuration of object lifecycle management." + type = any + default = [] +} + +variable "replication_configuration" { + description = "Map containing cross-region replication configuration." + type = any + default = {} +} + +variable "server_side_encryption_configuration" { + description = "Map containing server-side encryption configuration." + type = any + default = {} +} + +variable "intelligent_tiering" { + description = "Map containing intelligent tiering configuration." + type = any + default = {} +} + +variable "object_lock_configuration" { + description = "Map containing S3 object locking configuration." + type = any + default = {} +} + +variable "object_lock_enabled" { + description = "Whether S3 bucket should have an Object Lock configuration enabled." + type = bool + default = false +} + +variable "block_public_acls" { + description = "Whether Amazon S3 should block public ACLs for this bucket." + type = bool + default = false +} + +variable "block_public_policy" { + description = "Whether Amazon S3 should block public bucket policies for this bucket." + type = bool + default = false +} + +variable "ignore_public_acls" { + description = "Whether Amazon S3 should ignore public ACLs for this bucket." + type = bool + default = false +} + +variable "restrict_public_buckets" { + description = "Whether Amazon S3 should restrict public bucket policies for this bucket." + type = bool + default = false +} + +variable "control_object_ownership" { + description = "Whether to manage S3 Bucket Ownership Controls on this bucket." + type = bool + default = false +} + +variable "object_ownership" { + description = "Object ownership. Valid values: BucketOwnerEnforced, BucketOwnerPreferred or ObjectWriter. 'BucketOwnerEnforced': ACLs are disabled, and the bucket owner automatically owns and has full control over every object in the bucket. 'BucketOwnerPreferred': Objects uploaded to the bucket change ownership to the bucket owner if the objects are uploaded with the bucket-owner-full-control canned ACL. 'ObjectWriter': The uploading account will own the object if the object is uploaded with the bucket-owner-full-control canned ACL." + type = string + default = "ObjectWriter" +} + + diff --git a/modules/aws-s3/versions.tf b/modules/aws-s3/versions.tf new file mode 100644 index 0000000..affc11e --- /dev/null +++ b/modules/aws-s3/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.9" + } + } +} diff --git a/modules/aws-secrets-manager/README.md b/modules/aws-secrets-manager/README.md new file mode 100644 index 0000000..211cd73 --- /dev/null +++ b/modules/aws-secrets-manager/README.md @@ -0,0 +1,315 @@ +![Terraform](https://lgallardo.com/images/terraform.jpg) +# terraform-aws-secrets-manager + +Terraform module to create [Amazon Secrets Manager](https://aws.amazon.com/secrets-manager/) resources. + +AWS Secrets Manager helps you protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. + + +## Usage + +You can create secrets for plain texts, keys/values and binary data: + +### Plain text secrets + +``` +module "secrets-manager-1" { + + + secrets = { + secret-1 = { + description = "My secret 1" + recovery_window_in_days = 7 + secret_string = "This is an example" + }, + secret-2 = { + description = "My secret 2" + recovery_window_in_days = 7 + secret_string = "This is another example" + } + } + + tags = { + Owner = "DevOps team" + Environment = "dev" + Terraform = true + + } +} +``` + +### Key/Value secrets + +``` +module "secrets-manager-2" { + + + secrets = { + secret-kv-1 = { + description = "This is a key/value secret" + secret_key_value = { + key1 = "value1" + key2 = "value2" + } + recovery_window_in_days = 7 + }, + secret-kv-2 = { + description = "Another key/value secret" + secret_key_value = { + username = "user" + password = "topsecret" + } + tags = { + app = "web" + } + recovery_window_in_days = 7 + }, + } + + tags = { + Owner = "DevOps team" + Environment = "dev" + Terraform = true + } +} + +``` + +### Binary secrets + +``` +module "secrets-manager-3" { + + + secrets = { + secret-binary-1 = { + description = "This is a binary secret" + secret_binary = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDt4TcI58h4G0wR+GcDY+0VJR10JNvG92jEKGaKxeMaOkfsXflVGsYXbfVBBCG/n3uHtTse7baYLB6LWQAuYWL1SHJVhhTQ7pPiocFWibAvJlVo1l7qJEDu2OxKpWEleCE+p3ufNXAy7v5UFO7EOnj0Zg6R3F/MiAWbQnaEHcYzNtogyC24YBecBLrBXZNi1g0AN1hM9k+3XvWUYTf9vPv8LIWnqo7y4Q2iEGWWurf37YFl1LzX4mG/Co+Vfe5TlZSe2YPMYWlw0ZKaKvwzInRR6dPMAflo3ABzlduiIbSdp110uGqB8i2M8eGXNDxR7Ni4nnLWnT9r1cpWhXWP6pAG4Xg8+x7+PIg/pgjgJNmsURw+jPD6+hkCw2Vz16EIgkC2b7lj0V6J4LncUoRzU/1sAzCQ4tspy3SKBUinYoxbDvXleF66FHEjfparnvNwfslBx0IJjG2uRwuX6zrsNIsGF1stEjz+eyAOtFV4/wRjRcCNDZvl1ODzIvwf8pAWddE= lgallard@server1" + }, + secret-binary-2 = { + description = "This is a binary secret" + name = "secret-binary-2" + description = "Another binary secret" + secret_binary = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzc818NSC6oJYnNjVWoF43+IuQpqc3WyS8BWZ50uawK5lY/aObweX2YiXPv2CoVvHUM0vG7U7BDBvNi2xwsT9n9uT27lcVQsTa8iDtpyoeBhcj3vJ60Jd04UfoMP7Og6UbD+KGiaqQ0LEtMXq6d3i619t7V0UkaJ4MXh2xl5y3bV4zNzTXdSScJnvMFfjLW0pJOOqltLma3NQ9ILVdMSK2Vzxc87T+h/jp0VuUAX4Rx9DqmxEU/4JadXmow/BKy69KVwAk/AQ8jL7OwD2YAxlMKqKnOsBJQF27YjmMD240UjkmnPlxkV8+g9b2hA0iM5GL+5MWg6pPUE0BYdarCmwyuaWYhv/426LnfHTz9UVC3y9Hg5c4X4I6AdJJUmarZXqxnMe9jJiqiQ+CAuxW3m0gIGsEbUul6raG73xFuozlaXq3J+kMCVW24eG2i5fezgmtiysIf/dpcUo+YLkX+U8jdMQg9IwCY0bf8XL39kwJ7u8uWU8+7nMcS9VQ5llVVMk= lgallard@server2" + recovery_window_in_days = 7 + tags = { + app = "web" + } + } + } + + tags = { + Owner = "DevOps team" + Environment = "dev" + Terraform = true + } +} + +``` +## Secrets Rotation + +If you need to rotate your secrets, use `rotate_secrets` map to define them. Take into account that the lambda function must exist and it must have the right permissions to rotate the secrets in AWS Secret manager: + + +``` +module "secrets-manager-4" { + + + rotate_secrets = { + secret-rotate-1 = { + description = "This is a secret to be rotated by a lambda" + secret_string = "This is an example" + rotation_lambda_arn = "arn:aws:lambda:us-east-1:123455678910:function:lambda-rotate-secret" + recovery_window_in_days = 15 + }, + secret-rotate-2 = { + description = "This is another secret to be rotated by a lambda" + secret_string = "This is another example" + rotation_lambda_arn = "arn:aws:lambda:us-east-1:123455678910:function:lambda-rotate-secret" + recovery_window_in_days = 7 + }, + } + + tags = { + Owner = "DevOps team" + Environment = "dev" + Terraform = true + } + +} + +# Lambda to rotate secrets +# AWS temaplates available here https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas +module "rotate_secret_lambda" { + source = "spring-media/lambda/aws" + version = "5.2.0" + + filename = "secrets_manager_rotation.zip" + function_name = "secrets-manager-rotation" + handler = "secrets_manager_rotation.lambda_handler" + runtime = "python3.7" + source_code_hash = filebase64sha256("${path.module}/secrets_manager_rotation.zip") + + environment = { + variables = { + SECRETS_MANAGER_ENDPOINT = "https://secretsmanager.us-east-1.amazonaws.com" + } + } + +} + +resource "aws_lambda_permission" "allow_secret_manager_call_Lambda" { + function_name = module.rotate_secret_lambda.function_name + statement_id = "AllowExecutionSecretManager" + action = "lambda:InvokeFunction" + principal = "secretsmanager.amazonaws.com" +} +``` + +## Several secret definitions + +You can define different type of secrets (string, key/value or binary) in the same `secrets` or `rotate_secrets` map: + +``` +module "secrets-manager-5" { + + + secrets = { + secret-plain = { + description = "My plain text secret" + recovery_window_in_days = 7 + secret_string = "This is an example" + }, + secret-key-value = { + description = "This is a key/value secret" + secret_key_value = { + username = "user" + password = "topsecret" + } + tags = { + app = "web" + } + recovery_window_in_days = 7 + }, + } + + tags = { + Owner = "DevOps team" + Environment = "dev" + Terraform = true + } + +} +``` + +## Version 0.5.0+ breaking changes +Issue [#13](https://github.com/lgallard/terraform-aws-secrets-manager/issues/13) highlighted the fact that changing the secrets order will recreate the secrets (for example, adding a new secret in the top of the list o removing a secret that is not the last one). The suggested approach to tackle this issue was to use `for_each` to iterate over a map of secrets. + +Version 0.5.0 has this implementation, but it's not backward compatible. Therefore you must migrate your Terraform code and the objects in the tfstate. + +### Migrating the code: + +Before 0.5.0 your secrets were defined as list as follow: + +``` + secrets = [ + { + name = "secret-1" + description = "My secret 1" + recovery_window_in_days = 7 + secret_string = "This is an example" + }, + ] + +``` + +After version 0.5.0 you have to define you secrets as a map: + +``` + secrets = { + secret-1 = { + description = "My secret 1" + recovery_window_in_days = 7 + secret_string = "This is an example" + }, + } +``` + +Notice that the map key is the name of the secret, thefore a `name` field is not needed anymore. + +### Migrating the objects in the tfstate file + +To avoid recreating your already deploy secrets you can rename or move the object in the tfstate file as follow: + +``` +terraform state mv 'module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv['0']' 'module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv["secret-1"]' +``` + +Another option is to use a script to iterate over your secrets. In the [migration-scripts](/examples/migration-scripts) folder you'll find a couple of scripts that can be used as a starting point. + +For example, to migrate a module named `secrets-manager-1` run the script as follow: + +``` +$ ./secret_list_to_map.sh secrets-manager-1 + +Move "module.secrets-manager-1.aws_secretsmanager_secret.sm[0]" to "module.secrets-manager-1.aws_secretsmanager_secret.sm[\"secret-1\"]" +Successfully moved 1 object(s). +Move "module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv[0]" to "module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv[\"secret-1\"]" +Successfully moved 1 object(s). +Move "module.secrets-manager-1.aws_secretsmanager_secret.sm[1]" to "module.secrets-manager-1.aws_secretsmanager_secret.sm[\"secret-2\"]" +Successfully moved 1 object(s). +Move "module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv[1]" to "module.secrets-manager-1.aws_secretsmanager_secret_version.sm-sv[\"secret-2\"]" +Successfully moved 1 object(s). + +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | >= 2.67.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.28.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_secretsmanager_secret.rsm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource | +| [aws_secretsmanager_secret.sm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource | +| [aws_secretsmanager_secret_rotation.rsm-sr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation) | resource | +| [aws_secretsmanager_secret_version.rsm-sv](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | +| [aws_secretsmanager_secret_version.rsm-svu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | +| [aws_secretsmanager_secret_version.sm-sv](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | +| [aws_secretsmanager_secret_version.sm-svu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [automatically\_after\_days](#input\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. | `number` | `30` | no | +| [recovery\_window\_in\_days](#input\_recovery\_window\_in\_days) | Specifies the number of days that AWS Secrets Manager waits before it can delete the secret. This value can be 0 to force deletion without recovery or range from 7 to 30 days. | `number` | `30` | no | +| [replica\_regions](#input\_replica\_regions) | Map of regions to replicate the secret as the key and related kms\_key\_id as the value | `map(any)` | `{}` | no | +| [rotate\_secrets](#input\_rotate\_secrets) | Map of secrets to keep and rotate in AWS Secrets Manager | `any` | `{}` | no | +| [secrets](#input\_secrets) | Map of secrets to keep in AWS Secrets Manager | `any` | `{}` | no | +| [tags](#input\_tags) | Specifies a key-value map of user-defined tags that are attached to the secret. | `any` | `{}` | no | +| [unmanaged](#input\_unmanaged) | Terraform must ignore secrets lifecycle. Using this option you can initialize the secrets and rotate them outside Terraform, thus, avoiding other users to change or rotate the secrets by subsequent runs of Terraform | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [rotate\_secret\_arns](#output\_rotate\_secret\_arns) | Rotate secret arns map | +| [rotate\_secret\_ids](#output\_rotate\_secret\_ids) | Rotate secret ids map | +| [secret\_arns](#output\_secret\_arns) | Secrets arns map | +| [secret\_ids](#output\_secret\_ids) | Secret ids map | + diff --git a/modules/aws-secrets-manager/main.tf b/modules/aws-secrets-manager/main.tf new file mode 100644 index 0000000..10986f2 --- /dev/null +++ b/modules/aws-secrets-manager/main.tf @@ -0,0 +1,85 @@ +resource "aws_secretsmanager_secret" "sm" { + for_each = var.secrets + name = lookup(each.value, "name_prefix", null) == null ? each.key : null + name_prefix = lookup(each.value, "name_prefix", null) != null ? lookup(each.value, "name_prefix") : null + description = lookup(each.value, "description", null) + kms_key_id = lookup(each.value, "kms_key_id", null) + policy = lookup(each.value, "policy", null) + recovery_window_in_days = lookup(each.value, "recovery_window_in_days", var.recovery_window_in_days) + tags = merge(var.tags, lookup(each.value, "tags", null)) + dynamic "replica" { + for_each = var.replica_regions + content { + region = replica.key + kms_key_id = replica.value + } + } +} + +resource "aws_secretsmanager_secret_version" "sm-sv" { + for_each = { for k, v in var.secrets : k => v if !var.unmanaged } + secret_id = each.key + secret_string = lookup(each.value, "secret_string", null) != null ? lookup(each.value, "secret_string", null) : (lookup(each.value, "secret_key_value", null) != null ? jsonencode(lookup(each.value, "secret_key_value", {})) : null) + secret_binary = lookup(each.value, "secret_binary", null) != null ? base64encode(lookup(each.value, "secret_binary")) : null + depends_on = [aws_secretsmanager_secret.sm] +} + +resource "aws_secretsmanager_secret_version" "sm-svu" { + for_each = { for k, v in var.secrets : k => v if var.unmanaged } + secret_id = each.key + secret_string = lookup(each.value, "secret_string", null) != null ? lookup(each.value, "secret_string") : (lookup(each.value, "secret_key_value", null) != null ? jsonencode(lookup(each.value, "secret_key_value", {})) : null) + secret_binary = lookup(each.value, "secret_binary", null) != null ? base64encode(lookup(each.value, "secret_binary")) : null + depends_on = [aws_secretsmanager_secret.sm] + + lifecycle { + ignore_changes = [ + secret_string, + secret_binary, + ] + } +} + +# Rotate secrets +resource "aws_secretsmanager_secret" "rsm" { + for_each = var.rotate_secrets + name = lookup(each.value, "name_prefix", null) == null ? each.key : null + name_prefix = lookup(each.value, "name_prefix", null) != null ? lookup(each.value, "name_prefix") : null + description = lookup(each.value, "description") + kms_key_id = lookup(each.value, "kms_key_id", null) + policy = lookup(each.value, "policy", null) + recovery_window_in_days = lookup(each.value, "recovery_window_in_days", var.recovery_window_in_days) + tags = merge(var.tags, lookup(each.value, "tags", null)) +} + +resource "aws_secretsmanager_secret_version" "rsm-sv" { + for_each = { for k, v in var.rotate_secrets : k => v if !var.unmanaged } + secret_id = each.key + secret_string = lookup(each.value, "secret_string", null) != null ? lookup(each.value, "secret_string") : (lookup(each.value, "secret_key_value", null) != null ? jsonencode(lookup(each.value, "secret_key_value", {})) : null) + secret_binary = lookup(each.value, "secret_binary", null) != null ? base64encode(lookup(each.value, "secret_binary")) : null + depends_on = [aws_secretsmanager_secret.rsm] +} + +resource "aws_secretsmanager_secret_version" "rsm-svu" { + for_each = { for k, v in var.rotate_secrets : k => v if var.unmanaged } + secret_id = each.key + secret_string = lookup(each.value, "secret_string", null) != null ? lookup(each.value, "secret_string") : (lookup(each.value, "secret_key_value", null) != null ? jsonencode(lookup(each.value, "secret_key_value", {})) : null) + secret_binary = lookup(each.value, "secret_binary", null) != null ? base64encode(lookup(each.value, "secret_binary")) : null + depends_on = [aws_secretsmanager_secret.rsm] + + lifecycle { + ignore_changes = [ + secret_string, + secret_binary, + ] + } +} + +resource "aws_secretsmanager_secret_rotation" "rsm-sr" { + for_each = var.rotate_secrets + secret_id = each.key + rotation_lambda_arn = lookup(each.value, "rotation_lambda_arn") + + rotation_rules { + automatically_after_days = lookup(each.value, "automatically_after_days", var.automatically_after_days) + } +} diff --git a/modules/aws-secrets-manager/outputs.tf b/modules/aws-secrets-manager/outputs.tf new file mode 100644 index 0000000..48f6665 --- /dev/null +++ b/modules/aws-secrets-manager/outputs.tf @@ -0,0 +1,20 @@ +output "secret_ids" { + description = "Secret ids map" + value = { for k, v in aws_secretsmanager_secret.sm : k => v["id"] } +} + +output "secret_arns" { + description = "Secrets arns map" + value = { for k, v in aws_secretsmanager_secret.sm : k => v["arn"] } +} + +# Rotate secrets +output "rotate_secret_ids" { + description = "Rotate secret ids map" + value = { for k, v in aws_secretsmanager_secret.rsm : k => v["id"] } +} + +output "rotate_secret_arns" { + description = "Rotate secret arns map" + value = { for k, v in aws_secretsmanager_secret.rsm : k => v["arn"] } +} diff --git a/modules/aws-secrets-manager/variables.tf b/modules/aws-secrets-manager/variables.tf new file mode 100644 index 0000000..456f49f --- /dev/null +++ b/modules/aws-secrets-manager/variables.tf @@ -0,0 +1,44 @@ +variable "recovery_window_in_days" { + description = "Specifies the number of days that AWS Secrets Manager waits before it can delete the secret. This value can be 0 to force deletion without recovery or range from 7 to 30 days." + type = number + default = 30 +} + +# Secrets +variable "rotate_secrets" { + description = "Map of secrets to keep and rotate in AWS Secrets Manager" + type = any + default = {} +} + +# Secrets +variable "secrets" { + description = "Map of secrets to keep in AWS Secrets Manager" + type = any + default = {} +} + +variable "replica_regions" { + description = "Map of regions to replicate the secret as the key and related kms_key_id as the value" + type = map(any) + default = {} +} + +variable "unmanaged" { + description = "Terraform must ignore secrets lifecycle. Using this option you can initialize the secrets and rotate them outside Terraform, thus, avoiding other users to change or rotate the secrets by subsequent runs of Terraform" + type = bool + default = false +} + +variable "automatically_after_days" { + description = "Specifies the number of days between automatic scheduled rotations of the secret." + type = number + default = 30 +} + +# Tags +variable "tags" { + description = "Specifies a key-value map of user-defined tags that are attached to the secret." + type = any + default = {} +} diff --git a/modules/aws-secrets-manager/versions.tf b/modules/aws-secrets-manager/versions.tf new file mode 100644 index 0000000..06b24a9 --- /dev/null +++ b/modules/aws-secrets-manager/versions.tf @@ -0,0 +1,6 @@ +terraform { + + required_providers { + aws = ">= 2.67.0" + } +} diff --git a/modules/aws-security-group/README.md b/modules/aws-security-group/README.md new file mode 100644 index 0000000..286c288 --- /dev/null +++ b/modules/aws-security-group/README.md @@ -0,0 +1,214 @@ +# AWS EC2-VPC Security Group Terraform module + +Terraform module which creates EC2 security group within VPC + + + +## Features + +This module aims to implement **ALL** combinations of arguments supported by AWS and latest stable version of Terraform: +* IPv4/IPv6 CIDR blocks +* [VPC endpoint prefix lists](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-endpoints.html) (use data source [aws_prefix_list](https://www.terraform.io/docs/providers/aws/d/prefix_list.html)) +* Access from source security groups +* Access from self +* Named rules ([see the rules here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/rules.tf)) +* Named groups of rules with ingress (inbound) and egress (outbound) ports open for common scenarios (eg, [ssh](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ssh), [http-80](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/http-80), [mysql](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/mysql), see the whole list [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/README.md)) +* Conditionally create security group and/or all required security group rules. + +Ingress and egress rules can be configured in a variety of ways. See [inputs section](#inputs) for all supported arguments and [complete example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/complete) for the complete use-case. + + +## Terraform versions + +For Terraform 0.13 or later use any version from `v4.5.0` of this module or newer. + + +### Security group with predefined rules + +```hcl +module "web_server_sg" { + source = "terraform-modules/security-group/aws//modules/http-80" + + name = "web-server" + description = "Security group for web-server with HTTP ports open within VPC" + vpc_id = "vpc-12345678" + + ingress_cidr_blocks = ["10.10.0.0/16"] +} +``` + +### Security group with custom rules + +```hcl +module "vote_service_sg" { + source = "terraform-modules/security-group/aws" + + name = "user-service" + description = "Security group for user-service with custom ports open within VPC, and PostgreSQL publicly open" + vpc_id = "vpc-12345678" + + ingress_cidr_blocks = ["10.10.0.0/16"] + ingress_rules = ["https-443-tcp"] + ingress_with_cidr_blocks = [ + { + from_port = 8080 + to_port = 8090 + protocol = "tcp" + description = "User-service ports" + cidr_blocks = "10.10.0.0/16" + }, + { + rule = "postgresql-tcp" + cidr_blocks = "0.0.0.0/0" + }, + ] +} +`` + +## Conditional creation + +Sometimes you need a way to conditionally create a security group. If you're using Terraform < 0.13 which lacks module support for [count](https://www.terraform.io/docs/language/meta-arguments/count.html), you can instead specify the argument `create`. + +```hcl +# This security group will not be created +module "vote_service_sg" { + source = "terraform-modules/security-group/aws" + + create = false + # ... omitted +} +``` + +## Examples + +* [Complete Security Group example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/complete) shows all available parameters to configure security group. +* [Security Group "Rules Only" example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/complete) shows how to manage just rules of a security group that is created outside. +* [HTTP Security Group example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/http) shows more applicable security groups for common web-servers. +* [Disable creation of Security Group example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/disabled) shows how to disable creation of security group. +* [Dynamic values inside Security Group rules example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/dynamic) shows how to specify values inside security group rules (data-sources and variables are allowed). +* [Computed values inside Security Group rules example](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/examples/computed) shows how to specify computed values inside security group rules (solution for `value of 'count' cannot be computed` problem). + +## How to add/update rules/groups? + +Rules and groups are defined in [rules.tf](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/rules.tf). Run `update_groups.sh` when content of that file has changed to recreate content of all automatic modules. + +## Known issues + +No issue is creating limit on this module. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.29 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.this_name_prefix](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.computed_egress_rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_egress_with_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_egress_with_ipv6_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_egress_with_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_egress_with_source_security_group_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_ingress_rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_ingress_with_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_ingress_with_ipv6_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_ingress_with_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.computed_ingress_with_source_security_group_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress_rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress_with_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress_with_ipv6_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress_with_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.egress_with_source_security_group_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_with_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_with_ipv6_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_with_self](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ingress_with_source_security_group_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_groups](#input\_auto\_groups) | Map of groups of security group rules to use to generate modules (see update\_groups.sh) | `map(map(list(string)))` |
{
"activemq": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"activemq-5671-tcp",
"activemq-8883-tcp",
"activemq-61614-tcp",
"activemq-61617-tcp",
"activemq-61619-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"alertmanager": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"alertmanager-9093-tcp",
"alertmanager-9094-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"carbon-relay-ng": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"carbon-line-in-tcp",
"carbon-line-in-udp",
"carbon-pickle-tcp",
"carbon-pickle-udp",
"carbon-gui-udp"
],
"ingress_with_self": [
"all-all"
]
},
"cassandra": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"cassandra-clients-tcp",
"cassandra-thrift-clients-tcp",
"cassandra-jmx-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"consul": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"consul-tcp",
"consul-grpc-tcp",
"consul-webui-http-tcp",
"consul-webui-https-tcp",
"consul-dns-tcp",
"consul-dns-udp",
"consul-serf-lan-tcp",
"consul-serf-lan-udp",
"consul-serf-wan-tcp",
"consul-serf-wan-udp"
],
"ingress_with_self": [
"all-all"
]
},
"docker-swarm": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"docker-swarm-mngmt-tcp",
"docker-swarm-node-tcp",
"docker-swarm-node-udp",
"docker-swarm-overlay-udp"
],
"ingress_with_self": [
"all-all"
]
},
"elasticsearch": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"elasticsearch-rest-tcp",
"elasticsearch-java-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"etcd": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"etcd-client-tcp",
"etcd-peer-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"grafana": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"grafana-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"graphite-statsd": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"graphite-webui",
"graphite-2003-tcp",
"graphite-2004-tcp",
"graphite-2023-tcp",
"graphite-2024-tcp",
"graphite-8080-tcp",
"graphite-8125-tcp",
"graphite-8125-udp",
"graphite-8126-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"http-80": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"http-80-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"http-8080": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"http-8080-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"https-443": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"https-443-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"https-8443": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"https-8443-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"ipsec-4500": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ipsec-4500-udp"
],
"ingress_with_self": [
"all-all"
]
},
"ipsec-500": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ipsec-500-udp"
],
"ingress_with_self": [
"all-all"
]
},
"kafka": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"kafka-broker-tcp",
"kafka-broker-tls-tcp",
"kafka-broker-tls-public-tcp",
"kafka-broker-sasl-scram-tcp",
"kafka-broker-sasl-scram-tcp",
"kafka-broker-sasl-iam-tcp",
"kafka-broker-sasl-iam-public-tcp",
"kafka-jmx-exporter-tcp",
"kafka-node-exporter-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"kibana": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"kibana-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"kubernetes-api": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"kubernetes-api-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"ldap": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ldap-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"ldaps": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ldaps-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"logstash": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"logstash-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"memcached": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"memcached-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"minio": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"minio-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"mongodb": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"mongodb-27017-tcp",
"mongodb-27018-tcp",
"mongodb-27019-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"mssql": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"mssql-tcp",
"mssql-udp",
"mssql-analytics-tcp",
"mssql-broker-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"mysql": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"mysql-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"nfs": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"nfs-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"nomad": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"nomad-http-tcp",
"nomad-rpc-tcp",
"nomad-serf-tcp",
"nomad-serf-udp"
],
"ingress_with_self": [
"all-all"
]
},
"ntp": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ntp-udp"
],
"ingress_with_self": [
"all-all"
]
},
"openvpn": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"openvpn-udp",
"openvpn-tcp",
"openvpn-https-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"oracle-db": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"oracle-db-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"postgresql": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"postgresql-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"prometheus": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"prometheus-http-tcp",
"prometheus-pushgateway-http-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"puppet": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"puppet-tcp",
"puppetdb-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"rabbitmq": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"rabbitmq-4369-tcp",
"rabbitmq-5671-tcp",
"rabbitmq-5672-tcp",
"rabbitmq-15672-tcp",
"rabbitmq-25672-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"rdp": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"rdp-tcp",
"rdp-udp"
],
"ingress_with_self": [
"all-all"
]
},
"redis": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"redis-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"redshift": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"redshift-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"smtp": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"smtp-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"smtp-submission": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"smtp-submission-587-tcp",
"smtp-submission-2587-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"smtps": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"smtps-465-tcp",
"smtps-2465-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"solr": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"solr-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"splunk": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"splunk-indexer-tcp",
"splunk-clients-tcp",
"splunk-splunkd-tcp",
"splunk-hec-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"squid": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"squid-proxy-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"ssh": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"ssh-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"storm": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"storm-nimbus-tcp",
"storm-ui-tcp",
"storm-supervisor-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"wazuh": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"wazuh-server-agent-connection-tcp",
"wazuh-server-agent-connection-udp",
"wazuh-server-agent-enrollment",
"wazuh-server-agent-cluster-daemon",
"wazuh-server-syslog-collector-tcp",
"wazuh-server-syslog-collector-udp",
"wazuh-server-restful-api",
"wazuh-indexer-restful-api",
"wazuh-dashboard"
],
"ingress_with_self": [
"all-all"
]
},
"web": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"http-80-tcp",
"http-8080-tcp",
"https-443-tcp",
"web-jmx-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"winrm": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"winrm-http-tcp",
"winrm-https-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"zabbix": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"zabbix-server",
"zabbix-proxy",
"zabbix-agent"
],
"ingress_with_self": [
"all-all"
]
},
"zipkin": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"zipkin-admin-tcp",
"zipkin-admin-query-tcp",
"zipkin-admin-web-tcp",
"zipkin-query-tcp",
"zipkin-web-tcp"
],
"ingress_with_self": [
"all-all"
]
},
"zookeeper": {
"egress_rules": [
"all-all"
],
"ingress_rules": [
"zookeeper-2181-tcp",
"zookeeper-2182-tls-tcp",
"zookeeper-2888-tcp",
"zookeeper-3888-tcp",
"zookeeper-jmx-tcp"
],
"ingress_with_self": [
"all-all"
]
}
}
| no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [create\_sg](#input\_create\_sg) | Whether to create security group | `bool` | `true` | no | +| [create\_timeout](#input\_create\_timeout) | Time to wait for a security group to be created | `string` | `"10m"` | no | +| [delete\_timeout](#input\_delete\_timeout) | Time to wait for a security group to be deleted | `string` | `"15m"` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group - not required if create\_sg is false | `string` | `null` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [rules](#input\_rules) | Map of known security group rules (define as 'name' = ['from port', 'to port', 'protocol', 'description']) | `map(list(any))` |
{
"_": [
"",
"",
""
],
"activemq-5671-tcp": [
5671,
5671,
"tcp",
"ActiveMQ AMQP"
],
"activemq-61614-tcp": [
61614,
61614,
"tcp",
"ActiveMQ STOMP"
],
"activemq-61617-tcp": [
61617,
61617,
"tcp",
"ActiveMQ OpenWire"
],
"activemq-61619-tcp": [
61619,
61619,
"tcp",
"ActiveMQ WebSocket"
],
"activemq-8883-tcp": [
8883,
8883,
"tcp",
"ActiveMQ MQTT"
],
"alertmanager-9093-tcp": [
9093,
9093,
"tcp",
"Alert Manager"
],
"alertmanager-9094-tcp": [
9094,
9094,
"tcp",
"Alert Manager Cluster"
],
"all-all": [
-1,
-1,
"-1",
"All protocols"
],
"all-icmp": [
-1,
-1,
"icmp",
"All IPV4 ICMP"
],
"all-ipv6-icmp": [
-1,
-1,
58,
"All IPV6 ICMP"
],
"all-tcp": [
0,
65535,
"tcp",
"All TCP ports"
],
"all-udp": [
0,
65535,
"udp",
"All UDP ports"
],
"carbon-admin-tcp": [
2004,
2004,
"tcp",
"Carbon admin"
],
"carbon-gui-udp": [
8081,
8081,
"tcp",
"Carbon GUI"
],
"carbon-line-in-tcp": [
2003,
2003,
"tcp",
"Carbon line-in"
],
"carbon-line-in-udp": [
2003,
2003,
"udp",
"Carbon line-in"
],
"carbon-pickle-tcp": [
2013,
2013,
"tcp",
"Carbon pickle"
],
"carbon-pickle-udp": [
2013,
2013,
"udp",
"Carbon pickle"
],
"cassandra-clients-tcp": [
9042,
9042,
"tcp",
"Cassandra clients"
],
"cassandra-jmx-tcp": [
7199,
7199,
"tcp",
"JMX"
],
"cassandra-thrift-clients-tcp": [
9160,
9160,
"tcp",
"Cassandra Thrift clients"
],
"consul-dns-tcp": [
8600,
8600,
"tcp",
"Consul DNS"
],
"consul-dns-udp": [
8600,
8600,
"udp",
"Consul DNS"
],
"consul-grpc-tcp": [
8502,
8502,
"tcp",
"Consul gRPC"
],
"consul-serf-lan-tcp": [
8301,
8301,
"tcp",
"Serf LAN"
],
"consul-serf-lan-udp": [
8301,
8301,
"udp",
"Serf LAN"
],
"consul-serf-wan-tcp": [
8302,
8302,
"tcp",
"Serf WAN"
],
"consul-serf-wan-udp": [
8302,
8302,
"udp",
"Serf WAN"
],
"consul-tcp": [
8300,
8300,
"tcp",
"Consul server"
],
"consul-webui-http-tcp": [
8500,
8500,
"tcp",
"Consul web UI HTTP"
],
"consul-webui-https-tcp": [
8501,
8501,
"tcp",
"Consul web UI HTTPS"
],
"dns-tcp": [
53,
53,
"tcp",
"DNS"
],
"dns-udp": [
53,
53,
"udp",
"DNS"
],
"docker-swarm-mngmt-tcp": [
2377,
2377,
"tcp",
"Docker Swarm cluster management"
],
"docker-swarm-node-tcp": [
7946,
7946,
"tcp",
"Docker Swarm node"
],
"docker-swarm-node-udp": [
7946,
7946,
"udp",
"Docker Swarm node"
],
"docker-swarm-overlay-udp": [
4789,
4789,
"udp",
"Docker Swarm Overlay Network Traffic"
],
"elasticsearch-java-tcp": [
9300,
9300,
"tcp",
"Elasticsearch Java interface"
],
"elasticsearch-rest-tcp": [
9200,
9200,
"tcp",
"Elasticsearch REST interface"
],
"etcd-client-tcp": [
2379,
2379,
"tcp",
"Etcd Client"
],
"etcd-peer-tcp": [
2380,
2380,
"tcp",
"Etcd Peer"
],
"grafana-tcp": [
3000,
3000,
"tcp",
"Grafana Dashboard"
],
"graphite-2003-tcp": [
2003,
2003,
"tcp",
"Carbon receiver plain text"
],
"graphite-2004-tcp": [
2004,
2004,
"tcp",
"Carbon receiver pickle"
],
"graphite-2023-tcp": [
2023,
2023,
"tcp",
"Carbon aggregator plaintext"
],
"graphite-2024-tcp": [
2024,
2024,
"tcp",
"Carbon aggregator pickle"
],
"graphite-8080-tcp": [
8080,
8080,
"tcp",
"Graphite gunicorn port"
],
"graphite-8125-tcp": [
8125,
8125,
"tcp",
"Statsd TCP"
],
"graphite-8125-udp": [
8125,
8125,
"udp",
"Statsd UDP default"
],
"graphite-8126-tcp": [
8126,
8126,
"tcp",
"Statsd admin"
],
"graphite-webui": [
80,
80,
"tcp",
"Graphite admin interface"
],
"http-80-tcp": [
80,
80,
"tcp",
"HTTP"
],
"http-8080-tcp": [
8080,
8080,
"tcp",
"HTTP"
],
"https-443-tcp": [
443,
443,
"tcp",
"HTTPS"
],
"https-8443-tcp": [
8443,
8443,
"tcp",
"HTTPS"
],
"ipsec-4500-udp": [
4500,
4500,
"udp",
"IPSEC NAT-T"
],
"ipsec-500-udp": [
500,
500,
"udp",
"IPSEC ISAKMP"
],
"kafka-broker-sasl-iam-public-tcp": [
9198,
9198,
"tcp",
"Kafka SASL/IAM Public access control enabled (MSK specific)"
],
"kafka-broker-sasl-iam-tcp": [
9098,
9098,
"tcp",
"Kafka SASL/IAM access control enabled (MSK specific)"
],
"kafka-broker-sasl-scram-public-tcp": [
9196,
9196,
"tcp",
"Kafka SASL/SCRAM Public enabled broker (MSK specific)"
],
"kafka-broker-sasl-scram-tcp": [
9096,
9096,
"tcp",
"Kafka SASL/SCRAM enabled broker (MSK specific)"
],
"kafka-broker-tcp": [
9092,
9092,
"tcp",
"Kafka PLAINTEXT enable broker 0.8.2+"
],
"kafka-broker-tls-public-tcp": [
9194,
9194,
"tcp",
"Kafka TLS Public enabled broker 0.8.2+ (MSK specific)"
],
"kafka-broker-tls-tcp": [
9094,
9094,
"tcp",
"Kafka TLS enabled broker 0.8.2+"
],
"kafka-jmx-exporter-tcp": [
11001,
11001,
"tcp",
"Kafka JMX Exporter"
],
"kafka-node-exporter-tcp": [
11002,
11002,
"tcp",
"Kafka Node Exporter"
],
"kibana-tcp": [
5601,
5601,
"tcp",
"Kibana Web Interface"
],
"kubernetes-api-tcp": [
6443,
6443,
"tcp",
"Kubernetes API Server"
],
"ldap-tcp": [
389,
389,
"tcp",
"LDAP"
],
"ldaps-tcp": [
636,
636,
"tcp",
"LDAPS"
],
"logstash-tcp": [
5044,
5044,
"tcp",
"Logstash"
],
"memcached-tcp": [
11211,
11211,
"tcp",
"Memcached"
],
"minio-tcp": [
9000,
9000,
"tcp",
"MinIO"
],
"mongodb-27017-tcp": [
27017,
27017,
"tcp",
"MongoDB"
],
"mongodb-27018-tcp": [
27018,
27018,
"tcp",
"MongoDB shard"
],
"mongodb-27019-tcp": [
27019,
27019,
"tcp",
"MongoDB config server"
],
"mssql-analytics-tcp": [
2383,
2383,
"tcp",
"MSSQL Analytics"
],
"mssql-broker-tcp": [
4022,
4022,
"tcp",
"MSSQL Broker"
],
"mssql-tcp": [
1433,
1433,
"tcp",
"MSSQL Server"
],
"mssql-udp": [
1434,
1434,
"udp",
"MSSQL Browser"
],
"mysql-tcp": [
3306,
3306,
"tcp",
"MySQL/Aurora"
],
"nfs-tcp": [
2049,
2049,
"tcp",
"NFS/EFS"
],
"nomad-http-tcp": [
4646,
4646,
"tcp",
"Nomad HTTP"
],
"nomad-rpc-tcp": [
4647,
4647,
"tcp",
"Nomad RPC"
],
"nomad-serf-tcp": [
4648,
4648,
"tcp",
"Serf"
],
"nomad-serf-udp": [
4648,
4648,
"udp",
"Serf"
],
"ntp-udp": [
123,
123,
"udp",
"NTP"
],
"octopus-tentacle-tcp": [
10933,
10933,
"tcp",
"Octopus Tentacle"
],
"openvpn-https-tcp": [
443,
443,
"tcp",
"OpenVPN"
],
"openvpn-tcp": [
943,
943,
"tcp",
"OpenVPN"
],
"openvpn-udp": [
1194,
1194,
"udp",
"OpenVPN"
],
"oracle-db-tcp": [
1521,
1521,
"tcp",
"Oracle"
],
"postgresql-tcp": [
5432,
5432,
"tcp",
"PostgreSQL"
],
"prometheus-http-tcp": [
9090,
9090,
"tcp",
"Prometheus"
],
"prometheus-pushgateway-http-tcp": [
9091,
9091,
"tcp",
"Prometheus Pushgateway"
],
"puppet-tcp": [
8140,
8140,
"tcp",
"Puppet"
],
"puppetdb-tcp": [
8081,
8081,
"tcp",
"PuppetDB"
],
"rabbitmq-15672-tcp": [
15672,
15672,
"tcp",
"RabbitMQ"
],
"rabbitmq-25672-tcp": [
25672,
25672,
"tcp",
"RabbitMQ"
],
"rabbitmq-4369-tcp": [
4369,
4369,
"tcp",
"RabbitMQ epmd"
],
"rabbitmq-5671-tcp": [
5671,
5671,
"tcp",
"RabbitMQ"
],
"rabbitmq-5672-tcp": [
5672,
5672,
"tcp",
"RabbitMQ"
],
"rdp-tcp": [
3389,
3389,
"tcp",
"Remote Desktop"
],
"rdp-udp": [
3389,
3389,
"udp",
"Remote Desktop"
],
"redis-tcp": [
6379,
6379,
"tcp",
"Redis"
],
"redshift-tcp": [
5439,
5439,
"tcp",
"Redshift"
],
"saltstack-tcp": [
4505,
4506,
"tcp",
"SaltStack"
],
"smtp-submission-2587-tcp": [
2587,
2587,
"tcp",
"SMTP Submission"
],
"smtp-submission-587-tcp": [
587,
587,
"tcp",
"SMTP Submission"
],
"smtp-tcp": [
25,
25,
"tcp",
"SMTP"
],
"smtps-2456-tcp": [
2465,
2465,
"tcp",
"SMTPS"
],
"smtps-465-tcp": [
465,
465,
"tcp",
"SMTPS"
],
"solr-tcp": [
8983,
8987,
"tcp",
"Solr"
],
"splunk-hec-tcp": [
8088,
8088,
"tcp",
"Splunk HEC"
],
"splunk-indexer-tcp": [
9997,
9997,
"tcp",
"Splunk indexer"
],
"splunk-splunkd-tcp": [
8089,
8089,
"tcp",
"Splunkd"
],
"splunk-web-tcp": [
8000,
8000,
"tcp",
"Splunk Web"
],
"squid-proxy-tcp": [
3128,
3128,
"tcp",
"Squid default proxy"
],
"ssh-tcp": [
22,
22,
"tcp",
"SSH"
],
"storm-nimbus-tcp": [
6627,
6627,
"tcp",
"Nimbus"
],
"storm-supervisor-tcp": [
6700,
6703,
"tcp",
"Supervisor"
],
"storm-ui-tcp": [
8080,
8080,
"tcp",
"Storm UI"
],
"wazuh-dashboard": [
443,
443,
"tcp",
"Wazuh web user interface"
],
"wazuh-indexer-restful-api": [
9200,
9200,
"tcp",
"Wazuh indexer RESTful API"
],
"wazuh-server-agent-cluster-daemon": [
1516,
1516,
"tcp",
"Wazuh cluster daemon"
],
"wazuh-server-agent-connection-tcp": [
1514,
1514,
"tcp",
"Agent connection service(TCP)"
],
"wazuh-server-agent-connection-udp": [
1514,
1514,
"udp",
"Agent connection service(UDP)"
],
"wazuh-server-agent-enrollment": [
1515,
1515,
"tcp",
"Agent enrollment service"
],
"wazuh-server-restful-api": [
55000,
55000,
"tcp",
"Wazuh server RESTful API"
],
"wazuh-server-syslog-collector-tcp": [
514,
514,
"tcp",
"Wazuh Syslog collector(TCP)"
],
"wazuh-server-syslog-collector-udp": [
514,
514,
"udp",
"Wazuh Syslog collector(UDP)"
],
"web-jmx-tcp": [
1099,
1099,
"tcp",
"JMX"
],
"winrm-http-tcp": [
5985,
5985,
"tcp",
"WinRM HTTP"
],
"winrm-https-tcp": [
5986,
5986,
"tcp",
"WinRM HTTPS"
],
"zabbix-agent": [
10050,
10050,
"tcp",
"Zabbix Agent"
],
"zabbix-proxy": [
10051,
10051,
"tcp",
"Zabbix Proxy"
],
"zabbix-server": [
10051,
10051,
"tcp",
"Zabbix Server"
],
"zipkin-admin-query-tcp": [
9901,
9901,
"tcp",
"Zipkin Admin port query"
],
"zipkin-admin-tcp": [
9990,
9990,
"tcp",
"Zipkin Admin port collector"
],
"zipkin-admin-web-tcp": [
9991,
9991,
"tcp",
"Zipkin Admin port web"
],
"zipkin-query-tcp": [
9411,
9411,
"tcp",
"Zipkin query port"
],
"zipkin-web-tcp": [
8080,
8080,
"tcp",
"Zipkin web port"
],
"zookeeper-2181-tcp": [
2181,
2181,
"tcp",
"Zookeeper"
],
"zookeeper-2182-tls-tcp": [
2182,
2182,
"tcp",
"Zookeeper TLS (MSK specific)"
],
"zookeeper-2888-tcp": [
2888,
2888,
"tcp",
"Zookeeper"
],
"zookeeper-3888-tcp": [
3888,
3888,
"tcp",
"Zookeeper"
],
"zookeeper-jmx-tcp": [
7199,
7199,
"tcp",
"JMX"
]
}
| no | +| [security\_group\_id](#input\_security\_group\_id) | ID of existing security group whose rules we will manage | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + + + + + + + diff --git a/modules/aws-security-group/main.tf b/modules/aws-security-group/main.tf new file mode 100644 index 0000000..8b75822 --- /dev/null +++ b/modules/aws-security-group/main.tf @@ -0,0 +1,812 @@ +################################## +# Get ID of created Security Group +################################## +locals { + create = var.create + + this_sg_id = var.create_sg ? concat(aws_security_group.this.*.id, aws_security_group.this_name_prefix.*.id, [""])[0] : var.security_group_id +} + +########################## +# Security group with name +########################## +resource "aws_security_group" "this" { + count = local.create && var.create_sg && !var.use_name_prefix ? 1 : 0 + + name = var.name + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + + tags = merge( + { + "Name" = format("%s", var.name) + }, + var.tags, + ) + + timeouts { + create = var.create_timeout + delete = var.delete_timeout + } +} + +################################# +# Security group with name_prefix +################################# +resource "aws_security_group" "this_name_prefix" { + count = local.create && var.create_sg && var.use_name_prefix ? 1 : 0 + + name_prefix = "${var.name}-" + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + + tags = merge( + { + "Name" = format("%s", var.name) + }, + var.tags, + ) + + lifecycle { + create_before_destroy = true + } + + timeouts { + create = var.create_timeout + delete = var.delete_timeout + } +} + +################################### +# Ingress - List of rules (simple) +################################### +# Security group rules with "cidr_blocks" and it uses list of rules names +resource "aws_security_group_rule" "ingress_rules" { + count = local.create ? length(var.ingress_rules) : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + cidr_blocks = var.ingress_cidr_blocks + ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + prefix_list_ids = var.ingress_prefix_list_ids + description = var.rules[var.ingress_rules[count.index]][3] + + from_port = var.rules[var.ingress_rules[count.index]][0] + to_port = var.rules[var.ingress_rules[count.index]][1] + protocol = var.rules[var.ingress_rules[count.index]][2] +} + +# Computed - Security group rules with "cidr_blocks" and it uses list of rules names +resource "aws_security_group_rule" "computed_ingress_rules" { + count = local.create ? var.number_of_computed_ingress_rules : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + cidr_blocks = var.ingress_cidr_blocks + ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + prefix_list_ids = var.ingress_prefix_list_ids + description = var.rules[var.computed_ingress_rules[count.index]][3] + + from_port = var.rules[var.computed_ingress_rules[count.index]][0] + to_port = var.rules[var.computed_ingress_rules[count.index]][1] + protocol = var.rules[var.computed_ingress_rules[count.index]][2] +} + +########################## +# Ingress - Maps of rules +########################## +# Security group rules with "source_security_group_id", but without "cidr_blocks" and "self" +resource "aws_security_group_rule" "ingress_with_source_security_group_id" { + count = local.create ? length(var.ingress_with_source_security_group_id) : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + source_security_group_id = var.ingress_with_source_security_group_id[count.index]["source_security_group_id"] + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.ingress_with_source_security_group_id[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.ingress_with_source_security_group_id[count.index], + "from_port", + var.rules[lookup( + var.ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.ingress_with_source_security_group_id[count.index], + "to_port", + var.rules[lookup( + var.ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.ingress_with_source_security_group_id[count.index], + "protocol", + var.rules[lookup( + var.ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][2], + ) +} + +# Computed - Security group rules with "source_security_group_id", but without "cidr_blocks" and "self" +resource "aws_security_group_rule" "computed_ingress_with_source_security_group_id" { + count = local.create ? var.number_of_computed_ingress_with_source_security_group_id : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + source_security_group_id = var.computed_ingress_with_source_security_group_id[count.index]["source_security_group_id"] + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "from_port", + var.rules[lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "to_port", + var.rules[lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "protocol", + var.rules[lookup( + var.computed_ingress_with_source_security_group_id[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "cidr_blocks", but without "ipv6_cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "ingress_with_cidr_blocks" { + count = local.create ? length(var.ingress_with_cidr_blocks) : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + cidr_blocks = split( + ",", + lookup( + var.ingress_with_cidr_blocks[count.index], + "cidr_blocks", + join(",", var.ingress_cidr_blocks), + ), + ) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.ingress_with_cidr_blocks[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.ingress_with_cidr_blocks[count.index], + "from_port", + var.rules[lookup(var.ingress_with_cidr_blocks[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.ingress_with_cidr_blocks[count.index], + "to_port", + var.rules[lookup(var.ingress_with_cidr_blocks[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.ingress_with_cidr_blocks[count.index], + "protocol", + var.rules[lookup(var.ingress_with_cidr_blocks[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "cidr_blocks", but without "ipv6_cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "computed_ingress_with_cidr_blocks" { + count = local.create ? var.number_of_computed_ingress_with_cidr_blocks : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + cidr_blocks = split( + ",", + lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "cidr_blocks", + join(",", var.ingress_cidr_blocks), + ), + ) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "from_port", + var.rules[lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "to_port", + var.rules[lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "protocol", + var.rules[lookup( + var.computed_ingress_with_cidr_blocks[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "ipv6_cidr_blocks", but without "cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "ingress_with_ipv6_cidr_blocks" { + count = local.create ? length(var.ingress_with_ipv6_cidr_blocks) : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + ipv6_cidr_blocks = split( + ",", + lookup( + var.ingress_with_ipv6_cidr_blocks[count.index], + "ipv6_cidr_blocks", + join(",", var.ingress_ipv6_cidr_blocks), + ), + ) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.ingress_with_ipv6_cidr_blocks[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.ingress_with_ipv6_cidr_blocks[count.index], + "from_port", + var.rules[lookup(var.ingress_with_ipv6_cidr_blocks[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.ingress_with_ipv6_cidr_blocks[count.index], + "to_port", + var.rules[lookup(var.ingress_with_ipv6_cidr_blocks[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.ingress_with_ipv6_cidr_blocks[count.index], + "protocol", + var.rules[lookup(var.ingress_with_ipv6_cidr_blocks[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "ipv6_cidr_blocks", but without "cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "computed_ingress_with_ipv6_cidr_blocks" { + count = local.create ? var.number_of_computed_ingress_with_ipv6_cidr_blocks : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + ipv6_cidr_blocks = split( + ",", + lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "ipv6_cidr_blocks", + join(",", var.ingress_ipv6_cidr_blocks), + ), + ) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "from_port", + var.rules[lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "to_port", + var.rules[lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "protocol", + var.rules[lookup( + var.computed_ingress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "self", but without "cidr_blocks" and "source_security_group_id" +resource "aws_security_group_rule" "ingress_with_self" { + count = local.create ? length(var.ingress_with_self) : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + self = lookup(var.ingress_with_self[count.index], "self", true) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.ingress_with_self[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.ingress_with_self[count.index], + "from_port", + var.rules[lookup(var.ingress_with_self[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.ingress_with_self[count.index], + "to_port", + var.rules[lookup(var.ingress_with_self[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.ingress_with_self[count.index], + "protocol", + var.rules[lookup(var.ingress_with_self[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "self", but without "cidr_blocks" and "source_security_group_id" +resource "aws_security_group_rule" "computed_ingress_with_self" { + count = local.create ? var.number_of_computed_ingress_with_self : 0 + + security_group_id = local.this_sg_id + type = "ingress" + + self = lookup(var.computed_ingress_with_self[count.index], "self", true) + prefix_list_ids = var.ingress_prefix_list_ids + description = lookup( + var.computed_ingress_with_self[count.index], + "description", + "Ingress Rule", + ) + + from_port = lookup( + var.computed_ingress_with_self[count.index], + "from_port", + var.rules[lookup(var.computed_ingress_with_self[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.computed_ingress_with_self[count.index], + "to_port", + var.rules[lookup(var.computed_ingress_with_self[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.computed_ingress_with_self[count.index], + "protocol", + var.rules[lookup(var.computed_ingress_with_self[count.index], "rule", "_")][2], + ) +} + +################# +# End of ingress +################# + +################################## +# Egress - List of rules (simple) +################################## +# Security group rules with "cidr_blocks" and it uses list of rules names +resource "aws_security_group_rule" "egress_rules" { + count = local.create ? length(var.egress_rules) : 0 + + security_group_id = local.this_sg_id + type = "egress" + + cidr_blocks = var.egress_cidr_blocks + ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + prefix_list_ids = var.egress_prefix_list_ids + description = var.rules[var.egress_rules[count.index]][3] + + from_port = var.rules[var.egress_rules[count.index]][0] + to_port = var.rules[var.egress_rules[count.index]][1] + protocol = var.rules[var.egress_rules[count.index]][2] +} + +# Computed - Security group rules with "cidr_blocks" and it uses list of rules names +resource "aws_security_group_rule" "computed_egress_rules" { + count = local.create ? var.number_of_computed_egress_rules : 0 + + security_group_id = local.this_sg_id + type = "egress" + + cidr_blocks = var.egress_cidr_blocks + ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + prefix_list_ids = var.egress_prefix_list_ids + description = var.rules[var.computed_egress_rules[count.index]][3] + + from_port = var.rules[var.computed_egress_rules[count.index]][0] + to_port = var.rules[var.computed_egress_rules[count.index]][1] + protocol = var.rules[var.computed_egress_rules[count.index]][2] +} + +######################### +# Egress - Maps of rules +######################### +# Security group rules with "source_security_group_id", but without "cidr_blocks" and "self" +resource "aws_security_group_rule" "egress_with_source_security_group_id" { + count = local.create ? length(var.egress_with_source_security_group_id) : 0 + + security_group_id = local.this_sg_id + type = "egress" + + source_security_group_id = var.egress_with_source_security_group_id[count.index]["source_security_group_id"] + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.egress_with_source_security_group_id[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.egress_with_source_security_group_id[count.index], + "from_port", + var.rules[lookup( + var.egress_with_source_security_group_id[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.egress_with_source_security_group_id[count.index], + "to_port", + var.rules[lookup( + var.egress_with_source_security_group_id[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.egress_with_source_security_group_id[count.index], + "protocol", + var.rules[lookup( + var.egress_with_source_security_group_id[count.index], + "rule", + "_", + )][2], + ) +} + +# Computed - Security group rules with "source_security_group_id", but without "cidr_blocks" and "self" +resource "aws_security_group_rule" "computed_egress_with_source_security_group_id" { + count = local.create ? var.number_of_computed_egress_with_source_security_group_id : 0 + + security_group_id = local.this_sg_id + type = "egress" + + source_security_group_id = var.computed_egress_with_source_security_group_id[count.index]["source_security_group_id"] + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.computed_egress_with_source_security_group_id[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.computed_egress_with_source_security_group_id[count.index], + "from_port", + var.rules[lookup( + var.computed_egress_with_source_security_group_id[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_egress_with_source_security_group_id[count.index], + "to_port", + var.rules[lookup( + var.computed_egress_with_source_security_group_id[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_egress_with_source_security_group_id[count.index], + "protocol", + var.rules[lookup( + var.computed_egress_with_source_security_group_id[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "cidr_blocks", but without "ipv6_cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "egress_with_cidr_blocks" { + count = local.create ? length(var.egress_with_cidr_blocks) : 0 + + security_group_id = local.this_sg_id + type = "egress" + + cidr_blocks = split( + ",", + lookup( + var.egress_with_cidr_blocks[count.index], + "cidr_blocks", + join(",", var.egress_cidr_blocks), + ), + ) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.egress_with_cidr_blocks[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.egress_with_cidr_blocks[count.index], + "from_port", + var.rules[lookup(var.egress_with_cidr_blocks[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.egress_with_cidr_blocks[count.index], + "to_port", + var.rules[lookup(var.egress_with_cidr_blocks[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.egress_with_cidr_blocks[count.index], + "protocol", + var.rules[lookup(var.egress_with_cidr_blocks[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "cidr_blocks", but without "ipv6_cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "computed_egress_with_cidr_blocks" { + count = local.create ? var.number_of_computed_egress_with_cidr_blocks : 0 + + security_group_id = local.this_sg_id + type = "egress" + + cidr_blocks = split( + ",", + lookup( + var.computed_egress_with_cidr_blocks[count.index], + "cidr_blocks", + join(",", var.egress_cidr_blocks), + ), + ) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.computed_egress_with_cidr_blocks[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.computed_egress_with_cidr_blocks[count.index], + "from_port", + var.rules[lookup( + var.computed_egress_with_cidr_blocks[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_egress_with_cidr_blocks[count.index], + "to_port", + var.rules[lookup( + var.computed_egress_with_cidr_blocks[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_egress_with_cidr_blocks[count.index], + "protocol", + var.rules[lookup( + var.computed_egress_with_cidr_blocks[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "ipv6_cidr_blocks", but without "cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "egress_with_ipv6_cidr_blocks" { + count = local.create ? length(var.egress_with_ipv6_cidr_blocks) : 0 + + security_group_id = local.this_sg_id + type = "egress" + + ipv6_cidr_blocks = split( + ",", + lookup( + var.egress_with_ipv6_cidr_blocks[count.index], + "ipv6_cidr_blocks", + join(",", var.egress_ipv6_cidr_blocks), + ), + ) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.egress_with_ipv6_cidr_blocks[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.egress_with_ipv6_cidr_blocks[count.index], + "from_port", + var.rules[lookup(var.egress_with_ipv6_cidr_blocks[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.egress_with_ipv6_cidr_blocks[count.index], + "to_port", + var.rules[lookup(var.egress_with_ipv6_cidr_blocks[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.egress_with_ipv6_cidr_blocks[count.index], + "protocol", + var.rules[lookup(var.egress_with_ipv6_cidr_blocks[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "ipv6_cidr_blocks", but without "cidr_blocks", "source_security_group_id" and "self" +resource "aws_security_group_rule" "computed_egress_with_ipv6_cidr_blocks" { + count = local.create ? var.number_of_computed_egress_with_ipv6_cidr_blocks : 0 + + security_group_id = local.this_sg_id + type = "egress" + + ipv6_cidr_blocks = split( + ",", + lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "ipv6_cidr_blocks", + join(",", var.egress_ipv6_cidr_blocks), + ), + ) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "from_port", + var.rules[lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][0], + ) + to_port = lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "to_port", + var.rules[lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][1], + ) + protocol = lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "protocol", + var.rules[lookup( + var.computed_egress_with_ipv6_cidr_blocks[count.index], + "rule", + "_", + )][2], + ) +} + +# Security group rules with "self", but without "cidr_blocks" and "source_security_group_id" +resource "aws_security_group_rule" "egress_with_self" { + count = local.create ? length(var.egress_with_self) : 0 + + security_group_id = local.this_sg_id + type = "egress" + + self = lookup(var.egress_with_self[count.index], "self", true) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.egress_with_self[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.egress_with_self[count.index], + "from_port", + var.rules[lookup(var.egress_with_self[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.egress_with_self[count.index], + "to_port", + var.rules[lookup(var.egress_with_self[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.egress_with_self[count.index], + "protocol", + var.rules[lookup(var.egress_with_self[count.index], "rule", "_")][2], + ) +} + +# Computed - Security group rules with "self", but without "cidr_blocks" and "source_security_group_id" +resource "aws_security_group_rule" "computed_egress_with_self" { + count = local.create ? var.number_of_computed_egress_with_self : 0 + + security_group_id = local.this_sg_id + type = "egress" + + self = lookup(var.computed_egress_with_self[count.index], "self", true) + prefix_list_ids = var.egress_prefix_list_ids + description = lookup( + var.computed_egress_with_self[count.index], + "description", + "Egress Rule", + ) + + from_port = lookup( + var.computed_egress_with_self[count.index], + "from_port", + var.rules[lookup(var.computed_egress_with_self[count.index], "rule", "_")][0], + ) + to_port = lookup( + var.computed_egress_with_self[count.index], + "to_port", + var.rules[lookup(var.computed_egress_with_self[count.index], "rule", "_")][1], + ) + protocol = lookup( + var.computed_egress_with_self[count.index], + "protocol", + var.rules[lookup(var.computed_egress_with_self[count.index], "rule", "_")][2], + ) +} + +################ +# End of egress +################ diff --git a/modules/aws-security-group/modules/README.md b/modules/aws-security-group/modules/README.md new file mode 100644 index 0000000..fbfc411 --- /dev/null +++ b/modules/aws-security-group/modules/README.md @@ -0,0 +1,58 @@ +List of Security Groups implemented as Terraform modules +======================================================== + + +* [activemq](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/activemq) +* [alertmanager](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/alertmanager) +* [carbon-relay-ng](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/carbon-relay-ng) +* [cassandra](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/cassandra) +* [consul](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/consul) +* [docker-swarm](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/docker-swarm) +* [elasticsearch](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/elasticsearch) +* [etcd](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/etcd) +* [grafana](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/grafana) +* [graphite-statsd](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/graphite-statsd) +* [http-80](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/http-80) +* [http-8080](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/http-8080) +* [https-443](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/https-443) +* [https-8443](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/https-8443) +* [ipsec-4500](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ipsec-4500) +* [ipsec-500](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ipsec-500) +* [kafka](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/kafka) +* [kibana](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/kibana) +* [kubernetes-api](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/kubernetes-api) +* [ldap](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ldap) +* [ldaps](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ldaps) +* [logstash](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/logstash) +* [memcached](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/memcached) +* [minio](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/minio) +* [mongodb](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/mongodb) +* [mssql](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/mssql) +* [mysql](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/mysql) +* [nfs](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/nfs) +* [nomad](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/nomad) +* [ntp](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ntp) +* [openvpn](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/openvpn) +* [oracle-db](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/oracle-db) +* [postgresql](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/postgresql) +* [prometheus](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/prometheus) +* [puppet](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/puppet) +* [rabbitmq](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/rabbitmq) +* [rdp](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/rdp) +* [redis](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/redis) +* [redshift](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/redshift) +* [smtp](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/smtp) +* [smtp-submission](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/smtp-submission) +* [smtps](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/smtps) +* [solr](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/solr) +* [splunk](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/splunk) +* [squid](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/squid) +* [ssh](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/ssh) +* [storm](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/storm) +* [wazuh](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/wazuh) +* [web](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/web) +* [winrm](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/winrm) +* [zabbix](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/zabbix) +* [zipkin](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/zipkin) +* [zookeeper](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/zookeeper) +* [_templates](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/_templates) - Source templates for all other modules. Change carefully, test thoughtfully! diff --git a/modules/aws-security-group/modules/_templates/main.tf b/modules/aws-security-group/modules/_templates/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/_templates/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/_templates/outputs.tf b/modules/aws-security-group/modules/_templates/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/_templates/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/_templates/variables.tf b/modules/aws-security-group/modules/_templates/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/_templates/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/_templates/versions.tf b/modules/aws-security-group/modules/_templates/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/_templates/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/activemq/README.md b/modules/aws-security-group/modules/activemq/README.md new file mode 100644 index 0000000..e98f4e7 --- /dev/null +++ b/modules/aws-security-group/modules/activemq/README.md @@ -0,0 +1,120 @@ +# activemq - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "activemq_security_group" { + source = "terraform-modules/security-group/aws//modules/activemq" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **activemq module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/activemq/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"activemq-5671-tcp",
"activemq-8883-tcp",
"activemq-61614-tcp",
"activemq-61617-tcp",
"activemq-61619-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/activemq/auto_values.tf b/modules/aws-security-group/modules/activemq/auto_values.tf new file mode 100644 index 0000000..f125c23 --- /dev/null +++ b/modules/aws-security-group/modules/activemq/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["activemq-5671-tcp", "activemq-8883-tcp", "activemq-61614-tcp", "activemq-61617-tcp", "activemq-61619-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/activemq/main.tf b/modules/aws-security-group/modules/activemq/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/activemq/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/activemq/outputs.tf b/modules/aws-security-group/modules/activemq/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/activemq/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/activemq/variables.tf b/modules/aws-security-group/modules/activemq/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/activemq/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/activemq/versions.tf b/modules/aws-security-group/modules/activemq/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/activemq/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/alertmanager/README.md b/modules/aws-security-group/modules/alertmanager/README.md new file mode 100644 index 0000000..94554d0 --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/README.md @@ -0,0 +1,120 @@ +# alertmanager - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "alertmanager_security_group" { + source = "terraform-modules/security-group/aws//modules/alertmanager" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **alertmanager module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/alertmanager/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"alertmanager-9093-tcp",
"alertmanager-9094-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/alertmanager/auto_values.tf b/modules/aws-security-group/modules/alertmanager/auto_values.tf new file mode 100644 index 0000000..f73768c --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["alertmanager-9093-tcp", "alertmanager-9094-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/alertmanager/main.tf b/modules/aws-security-group/modules/alertmanager/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/alertmanager/outputs.tf b/modules/aws-security-group/modules/alertmanager/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/alertmanager/variables.tf b/modules/aws-security-group/modules/alertmanager/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/alertmanager/versions.tf b/modules/aws-security-group/modules/alertmanager/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/alertmanager/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/carbon-relay-ng/README.md b/modules/aws-security-group/modules/carbon-relay-ng/README.md new file mode 100644 index 0000000..80a5a85 --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/README.md @@ -0,0 +1,120 @@ +# carbon-relay-ng - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "carbon_relay-ng_security_group" { + source = "terraform-modules/security-group/aws//modules/carbon-relay-ng" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **carbon-relay-ng module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/carbon-relay-ng/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"carbon-line-in-tcp",
"carbon-line-in-udp",
"carbon-pickle-tcp",
"carbon-pickle-udp",
"carbon-gui-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/carbon-relay-ng/auto_values.tf b/modules/aws-security-group/modules/carbon-relay-ng/auto_values.tf new file mode 100644 index 0000000..6d04463 --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["carbon-line-in-tcp", "carbon-line-in-udp", "carbon-pickle-tcp", "carbon-pickle-udp", "carbon-gui-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/carbon-relay-ng/main.tf b/modules/aws-security-group/modules/carbon-relay-ng/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/carbon-relay-ng/outputs.tf b/modules/aws-security-group/modules/carbon-relay-ng/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/carbon-relay-ng/variables.tf b/modules/aws-security-group/modules/carbon-relay-ng/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/carbon-relay-ng/versions.tf b/modules/aws-security-group/modules/carbon-relay-ng/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/carbon-relay-ng/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/cassandra/README.md b/modules/aws-security-group/modules/cassandra/README.md new file mode 100644 index 0000000..d6d6aee --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/README.md @@ -0,0 +1,120 @@ +# cassandra - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "cassandra_security_group" { + source = "terraform-modules/security-group/aws//modules/cassandra" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **cassandra module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/cassandra/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"cassandra-clients-tcp",
"cassandra-thrift-clients-tcp",
"cassandra-jmx-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/cassandra/auto_values.tf b/modules/aws-security-group/modules/cassandra/auto_values.tf new file mode 100644 index 0000000..cf41218 --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["cassandra-clients-tcp", "cassandra-thrift-clients-tcp", "cassandra-jmx-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/cassandra/main.tf b/modules/aws-security-group/modules/cassandra/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/cassandra/outputs.tf b/modules/aws-security-group/modules/cassandra/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/cassandra/variables.tf b/modules/aws-security-group/modules/cassandra/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/cassandra/versions.tf b/modules/aws-security-group/modules/cassandra/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/cassandra/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/consul/README.md b/modules/aws-security-group/modules/consul/README.md new file mode 100644 index 0000000..868f554 --- /dev/null +++ b/modules/aws-security-group/modules/consul/README.md @@ -0,0 +1,120 @@ +# consul - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "consul_security_group" { + source = "terraform-modules/security-group/aws//modules/consul" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **consul module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/consul/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"consul-tcp",
"consul-grpc-tcp",
"consul-webui-http-tcp",
"consul-webui-https-tcp",
"consul-dns-tcp",
"consul-dns-udp",
"consul-serf-lan-tcp",
"consul-serf-lan-udp",
"consul-serf-wan-tcp",
"consul-serf-wan-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/consul/auto_values.tf b/modules/aws-security-group/modules/consul/auto_values.tf new file mode 100644 index 0000000..e3385db --- /dev/null +++ b/modules/aws-security-group/modules/consul/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["consul-tcp", "consul-grpc-tcp", "consul-webui-http-tcp", "consul-webui-https-tcp", "consul-dns-tcp", "consul-dns-udp", "consul-serf-lan-tcp", "consul-serf-lan-udp", "consul-serf-wan-tcp", "consul-serf-wan-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/consul/main.tf b/modules/aws-security-group/modules/consul/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/consul/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/consul/outputs.tf b/modules/aws-security-group/modules/consul/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/consul/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/consul/variables.tf b/modules/aws-security-group/modules/consul/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/consul/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/consul/versions.tf b/modules/aws-security-group/modules/consul/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/consul/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/docker-swarm/README.md b/modules/aws-security-group/modules/docker-swarm/README.md new file mode 100644 index 0000000..3ba25e6 --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/README.md @@ -0,0 +1,120 @@ +# docker-swarm - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "docker_swarm_security_group" { + source = "terraform-modules/security-group/aws//modules/docker-swarm" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **docker-swarm module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/docker-swarm/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"docker-swarm-mngmt-tcp",
"docker-swarm-node-tcp",
"docker-swarm-node-udp",
"docker-swarm-overlay-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/docker-swarm/auto_values.tf b/modules/aws-security-group/modules/docker-swarm/auto_values.tf new file mode 100644 index 0000000..eedae24 --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["docker-swarm-mngmt-tcp", "docker-swarm-node-tcp", "docker-swarm-node-udp", "docker-swarm-overlay-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/docker-swarm/main.tf b/modules/aws-security-group/modules/docker-swarm/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/docker-swarm/outputs.tf b/modules/aws-security-group/modules/docker-swarm/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/docker-swarm/variables.tf b/modules/aws-security-group/modules/docker-swarm/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/docker-swarm/versions.tf b/modules/aws-security-group/modules/docker-swarm/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/docker-swarm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/elasticsearch/README.md b/modules/aws-security-group/modules/elasticsearch/README.md new file mode 100644 index 0000000..59dcc5e --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/README.md @@ -0,0 +1,120 @@ +# elasticsearch - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "elasticsearch_security_group" { + source = "terraform-modules/security-group/aws//modules/elasticsearch" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **elasticsearch module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/elasticsearch/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"elasticsearch-rest-tcp",
"elasticsearch-java-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/elasticsearch/auto_values.tf b/modules/aws-security-group/modules/elasticsearch/auto_values.tf new file mode 100644 index 0000000..e8c9275 --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["elasticsearch-rest-tcp", "elasticsearch-java-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/elasticsearch/main.tf b/modules/aws-security-group/modules/elasticsearch/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/elasticsearch/outputs.tf b/modules/aws-security-group/modules/elasticsearch/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/elasticsearch/variables.tf b/modules/aws-security-group/modules/elasticsearch/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/elasticsearch/versions.tf b/modules/aws-security-group/modules/elasticsearch/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/elasticsearch/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/etcd/README.md b/modules/aws-security-group/modules/etcd/README.md new file mode 100644 index 0000000..ae45681 --- /dev/null +++ b/modules/aws-security-group/modules/etcd/README.md @@ -0,0 +1,120 @@ +# etcd - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "etcd_security_group" { + source = "terraform-modules/security-group/aws//modules/etcd" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **etcd module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/etcd/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"etcd-client-tcp",
"etcd-peer-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/etcd/auto_values.tf b/modules/aws-security-group/modules/etcd/auto_values.tf new file mode 100644 index 0000000..e8c0412 --- /dev/null +++ b/modules/aws-security-group/modules/etcd/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["etcd-client-tcp", "etcd-peer-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/etcd/main.tf b/modules/aws-security-group/modules/etcd/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/etcd/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/etcd/outputs.tf b/modules/aws-security-group/modules/etcd/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/etcd/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/etcd/variables.tf b/modules/aws-security-group/modules/etcd/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/etcd/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/etcd/versions.tf b/modules/aws-security-group/modules/etcd/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/etcd/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/grafana/README.md b/modules/aws-security-group/modules/grafana/README.md new file mode 100644 index 0000000..1ea3d9e --- /dev/null +++ b/modules/aws-security-group/modules/grafana/README.md @@ -0,0 +1,120 @@ +# grafana - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "grafana_security_group" { + source = "terraform-modules/security-group/aws//modules/grafana" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **grafana module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/grafana/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"grafana-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/grafana/auto_values.tf b/modules/aws-security-group/modules/grafana/auto_values.tf new file mode 100644 index 0000000..ee39b60 --- /dev/null +++ b/modules/aws-security-group/modules/grafana/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["grafana-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/grafana/main.tf b/modules/aws-security-group/modules/grafana/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/grafana/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/grafana/outputs.tf b/modules/aws-security-group/modules/grafana/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/grafana/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/grafana/variables.tf b/modules/aws-security-group/modules/grafana/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/grafana/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/grafana/versions.tf b/modules/aws-security-group/modules/grafana/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/grafana/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/graphite-statsd/README.md b/modules/aws-security-group/modules/graphite-statsd/README.md new file mode 100644 index 0000000..6a951f0 --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/README.md @@ -0,0 +1,120 @@ +# graphite-statsd - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "graphite_statsd_security_group" { + source = "terraform-modules/security-group/aws//modules/graphite-statsd" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **graphite-statsd module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/graphite-statsd/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"graphite-webui",
"graphite-2003-tcp",
"graphite-2004-tcp",
"graphite-2023-tcp",
"graphite-2024-tcp",
"graphite-8080-tcp",
"graphite-8125-tcp",
"graphite-8125-udp",
"graphite-8126-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/graphite-statsd/auto_values.tf b/modules/aws-security-group/modules/graphite-statsd/auto_values.tf new file mode 100644 index 0000000..04167ba --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["graphite-webui", "graphite-2003-tcp", "graphite-2004-tcp", "graphite-2023-tcp", "graphite-2024-tcp", "graphite-8080-tcp", "graphite-8125-tcp", "graphite-8125-udp", "graphite-8126-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/graphite-statsd/main.tf b/modules/aws-security-group/modules/graphite-statsd/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/graphite-statsd/outputs.tf b/modules/aws-security-group/modules/graphite-statsd/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/graphite-statsd/variables.tf b/modules/aws-security-group/modules/graphite-statsd/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/graphite-statsd/versions.tf b/modules/aws-security-group/modules/graphite-statsd/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/graphite-statsd/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/http-80/README.md b/modules/aws-security-group/modules/http-80/README.md new file mode 100644 index 0000000..d45e5b4 --- /dev/null +++ b/modules/aws-security-group/modules/http-80/README.md @@ -0,0 +1,120 @@ +# http-80 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "http_80_security_group" { + source = "terraform-modules/security-group/aws//modules/http-80" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **http-80 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/http-80/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"http-80-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/http-80/auto_values.tf b/modules/aws-security-group/modules/http-80/auto_values.tf new file mode 100644 index 0000000..0fee81d --- /dev/null +++ b/modules/aws-security-group/modules/http-80/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["http-80-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/http-80/main.tf b/modules/aws-security-group/modules/http-80/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/http-80/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/http-80/outputs.tf b/modules/aws-security-group/modules/http-80/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/http-80/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/http-80/variables.tf b/modules/aws-security-group/modules/http-80/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/http-80/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/http-80/versions.tf b/modules/aws-security-group/modules/http-80/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/http-80/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/http-8080/README.md b/modules/aws-security-group/modules/http-8080/README.md new file mode 100644 index 0000000..5215df0 --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/README.md @@ -0,0 +1,120 @@ +# http-8080 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "http_8080_security_group" { + source = "terraform-modules/security-group/aws//modules/http-8080" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **http-8080 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/http-8080/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"http-8080-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/http-8080/auto_values.tf b/modules/aws-security-group/modules/http-8080/auto_values.tf new file mode 100644 index 0000000..9577559 --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["http-8080-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/http-8080/main.tf b/modules/aws-security-group/modules/http-8080/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/http-8080/outputs.tf b/modules/aws-security-group/modules/http-8080/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/http-8080/variables.tf b/modules/aws-security-group/modules/http-8080/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/http-8080/versions.tf b/modules/aws-security-group/modules/http-8080/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/http-8080/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/https-443/README.md b/modules/aws-security-group/modules/https-443/README.md new file mode 100644 index 0000000..f10c04e --- /dev/null +++ b/modules/aws-security-group/modules/https-443/README.md @@ -0,0 +1,120 @@ +# https-443 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "https_443_security_group" { + source = "terraform-modules/security-group/aws//modules/https-443" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **https-443 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/https-443/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"https-443-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/https-443/auto_values.tf b/modules/aws-security-group/modules/https-443/auto_values.tf new file mode 100644 index 0000000..3bd14e0 --- /dev/null +++ b/modules/aws-security-group/modules/https-443/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["https-443-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/https-443/main.tf b/modules/aws-security-group/modules/https-443/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/https-443/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/https-443/outputs.tf b/modules/aws-security-group/modules/https-443/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/https-443/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/https-443/variables.tf b/modules/aws-security-group/modules/https-443/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/https-443/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/https-443/versions.tf b/modules/aws-security-group/modules/https-443/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/https-443/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/https-8443/README.md b/modules/aws-security-group/modules/https-8443/README.md new file mode 100644 index 0000000..274b7b8 --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/README.md @@ -0,0 +1,120 @@ +# https-8443 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "https_8443_security_group" { + source = "terraform-modules/security-group/aws//modules/https-8443" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **https-8443 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/https-8443/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"https-8443-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/https-8443/auto_values.tf b/modules/aws-security-group/modules/https-8443/auto_values.tf new file mode 100644 index 0000000..1ca3d58 --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["https-8443-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/https-8443/main.tf b/modules/aws-security-group/modules/https-8443/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/https-8443/outputs.tf b/modules/aws-security-group/modules/https-8443/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/https-8443/variables.tf b/modules/aws-security-group/modules/https-8443/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/https-8443/versions.tf b/modules/aws-security-group/modules/https-8443/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/https-8443/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ipsec-4500/README.md b/modules/aws-security-group/modules/ipsec-4500/README.md new file mode 100644 index 0000000..df517bd --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/README.md @@ -0,0 +1,120 @@ +# ipsec-4500 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ipsec_4500_security_group" { + source = "terraform-modules/security-group/aws//modules/ipsec-4500" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ipsec-4500 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ipsec-4500/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ipsec-4500-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ipsec-4500/auto_values.tf b/modules/aws-security-group/modules/ipsec-4500/auto_values.tf new file mode 100644 index 0000000..1b4cad0 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ipsec-4500-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ipsec-4500/main.tf b/modules/aws-security-group/modules/ipsec-4500/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ipsec-4500/outputs.tf b/modules/aws-security-group/modules/ipsec-4500/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ipsec-4500/variables.tf b/modules/aws-security-group/modules/ipsec-4500/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ipsec-4500/versions.tf b/modules/aws-security-group/modules/ipsec-4500/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-4500/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ipsec-500/README.md b/modules/aws-security-group/modules/ipsec-500/README.md new file mode 100644 index 0000000..980f43d --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/README.md @@ -0,0 +1,120 @@ +# ipsec-500 - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ipsec_500_security_group" { + source = "terraform-modules/security-group/aws//modules/ipsec-500" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ipsec-500 module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ipsec-500/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ipsec-500-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ipsec-500/auto_values.tf b/modules/aws-security-group/modules/ipsec-500/auto_values.tf new file mode 100644 index 0000000..1d276c8 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ipsec-500-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ipsec-500/main.tf b/modules/aws-security-group/modules/ipsec-500/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ipsec-500/outputs.tf b/modules/aws-security-group/modules/ipsec-500/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ipsec-500/variables.tf b/modules/aws-security-group/modules/ipsec-500/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ipsec-500/versions.tf b/modules/aws-security-group/modules/ipsec-500/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ipsec-500/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/kafka/README.md b/modules/aws-security-group/modules/kafka/README.md new file mode 100644 index 0000000..a21e962 --- /dev/null +++ b/modules/aws-security-group/modules/kafka/README.md @@ -0,0 +1,120 @@ +# kafka - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "kafka_security_group" { + source = "terraform-modules/security-group/aws//modules/kafka" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **kafka module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/kafka/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"kafka-broker-tcp",
"kafka-broker-tls-tcp",
"kafka-broker-tls-public-tcp",
"kafka-broker-sasl-scram-tcp",
"kafka-broker-sasl-scram-tcp",
"kafka-broker-sasl-iam-tcp",
"kafka-broker-sasl-iam-public-tcp",
"kafka-jmx-exporter-tcp",
"kafka-node-exporter-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/kafka/auto_values.tf b/modules/aws-security-group/modules/kafka/auto_values.tf new file mode 100644 index 0000000..181bbb8 --- /dev/null +++ b/modules/aws-security-group/modules/kafka/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["kafka-broker-tcp", "kafka-broker-tls-tcp", "kafka-broker-tls-public-tcp", "kafka-broker-sasl-scram-tcp", "kafka-broker-sasl-scram-tcp", "kafka-broker-sasl-iam-tcp", "kafka-broker-sasl-iam-public-tcp", "kafka-jmx-exporter-tcp", "kafka-node-exporter-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kafka/main.tf b/modules/aws-security-group/modules/kafka/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/kafka/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/kafka/outputs.tf b/modules/aws-security-group/modules/kafka/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/kafka/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/kafka/variables.tf b/modules/aws-security-group/modules/kafka/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/kafka/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kafka/versions.tf b/modules/aws-security-group/modules/kafka/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/kafka/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/kibana/README.md b/modules/aws-security-group/modules/kibana/README.md new file mode 100644 index 0000000..7db6774 --- /dev/null +++ b/modules/aws-security-group/modules/kibana/README.md @@ -0,0 +1,120 @@ +# kibana - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "kibana_security_group" { + source = "terraform-modules/security-group/aws//modules/kibana" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **kibana module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/kibana/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"kibana-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/kibana/auto_values.tf b/modules/aws-security-group/modules/kibana/auto_values.tf new file mode 100644 index 0000000..fc6d8e2 --- /dev/null +++ b/modules/aws-security-group/modules/kibana/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["kibana-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kibana/main.tf b/modules/aws-security-group/modules/kibana/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/kibana/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/kibana/outputs.tf b/modules/aws-security-group/modules/kibana/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/kibana/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/kibana/variables.tf b/modules/aws-security-group/modules/kibana/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/kibana/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kibana/versions.tf b/modules/aws-security-group/modules/kibana/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/kibana/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/kubernetes-api/README.md b/modules/aws-security-group/modules/kubernetes-api/README.md new file mode 100644 index 0000000..20419b8 --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/README.md @@ -0,0 +1,120 @@ +# kubernetes-api - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "kubernetes_api_security_group" { + source = "terraform-modules/security-group/aws//modules/kubernetes-api" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **kubernetes-api module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/kubernetes-api/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"kubernetes-api-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/kubernetes-api/auto_values.tf b/modules/aws-security-group/modules/kubernetes-api/auto_values.tf new file mode 100644 index 0000000..0cd8c9b --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["kubernetes-api-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kubernetes-api/main.tf b/modules/aws-security-group/modules/kubernetes-api/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/kubernetes-api/outputs.tf b/modules/aws-security-group/modules/kubernetes-api/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/kubernetes-api/variables.tf b/modules/aws-security-group/modules/kubernetes-api/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/kubernetes-api/versions.tf b/modules/aws-security-group/modules/kubernetes-api/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/kubernetes-api/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ldap/README.md b/modules/aws-security-group/modules/ldap/README.md new file mode 100644 index 0000000..483bab2 --- /dev/null +++ b/modules/aws-security-group/modules/ldap/README.md @@ -0,0 +1,120 @@ +# ldap - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ldap_security_group" { + source = "terraform-modules/security-group/aws//modules/ldap" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ldap module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ldap/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ldap-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ldap/auto_values.tf b/modules/aws-security-group/modules/ldap/auto_values.tf new file mode 100644 index 0000000..c437562 --- /dev/null +++ b/modules/aws-security-group/modules/ldap/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ldap-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ldap/main.tf b/modules/aws-security-group/modules/ldap/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ldap/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ldap/outputs.tf b/modules/aws-security-group/modules/ldap/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ldap/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ldap/variables.tf b/modules/aws-security-group/modules/ldap/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ldap/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ldap/versions.tf b/modules/aws-security-group/modules/ldap/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ldap/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ldaps/README.md b/modules/aws-security-group/modules/ldaps/README.md new file mode 100644 index 0000000..d1317eb --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/README.md @@ -0,0 +1,120 @@ +# ldaps - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ldaps_security_group" { + source = "terraform-modules/security-group/aws//modules/ldaps" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ldaps module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ldaps/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ldaps-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ldaps/auto_values.tf b/modules/aws-security-group/modules/ldaps/auto_values.tf new file mode 100644 index 0000000..451b538 --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ldaps-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ldaps/main.tf b/modules/aws-security-group/modules/ldaps/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ldaps/outputs.tf b/modules/aws-security-group/modules/ldaps/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ldaps/variables.tf b/modules/aws-security-group/modules/ldaps/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ldaps/versions.tf b/modules/aws-security-group/modules/ldaps/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ldaps/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/logstash/README.md b/modules/aws-security-group/modules/logstash/README.md new file mode 100644 index 0000000..ac69998 --- /dev/null +++ b/modules/aws-security-group/modules/logstash/README.md @@ -0,0 +1,120 @@ +# logstash - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "logstash_security_group" { + source = "terraform-modules/security-group/aws//modules/logstash" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **logstash module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/logstash/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"logstash-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/logstash/auto_values.tf b/modules/aws-security-group/modules/logstash/auto_values.tf new file mode 100644 index 0000000..10e573d --- /dev/null +++ b/modules/aws-security-group/modules/logstash/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["logstash-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/logstash/main.tf b/modules/aws-security-group/modules/logstash/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/logstash/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/logstash/outputs.tf b/modules/aws-security-group/modules/logstash/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/logstash/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/logstash/variables.tf b/modules/aws-security-group/modules/logstash/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/logstash/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/logstash/versions.tf b/modules/aws-security-group/modules/logstash/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/logstash/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/memcached/README.md b/modules/aws-security-group/modules/memcached/README.md new file mode 100644 index 0000000..443e6ac --- /dev/null +++ b/modules/aws-security-group/modules/memcached/README.md @@ -0,0 +1,120 @@ +# memcached - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "memcached_security_group" { + source = "terraform-modules/security-group/aws//modules/memcached" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **memcached module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/memcached/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"memcached-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/memcached/auto_values.tf b/modules/aws-security-group/modules/memcached/auto_values.tf new file mode 100644 index 0000000..bdf4e45 --- /dev/null +++ b/modules/aws-security-group/modules/memcached/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["memcached-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/memcached/main.tf b/modules/aws-security-group/modules/memcached/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/memcached/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/memcached/outputs.tf b/modules/aws-security-group/modules/memcached/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/memcached/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/memcached/variables.tf b/modules/aws-security-group/modules/memcached/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/memcached/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/memcached/versions.tf b/modules/aws-security-group/modules/memcached/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/memcached/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/minio/README.md b/modules/aws-security-group/modules/minio/README.md new file mode 100644 index 0000000..8f530cb --- /dev/null +++ b/modules/aws-security-group/modules/minio/README.md @@ -0,0 +1,120 @@ +# minio - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "minio_security_group" { + source = "terraform-modules/security-group/aws//modules/minio" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **minio module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/minio/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"minio-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/minio/auto_values.tf b/modules/aws-security-group/modules/minio/auto_values.tf new file mode 100644 index 0000000..3afb82e --- /dev/null +++ b/modules/aws-security-group/modules/minio/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["minio-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/minio/main.tf b/modules/aws-security-group/modules/minio/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/minio/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/minio/outputs.tf b/modules/aws-security-group/modules/minio/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/minio/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/minio/variables.tf b/modules/aws-security-group/modules/minio/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/minio/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/minio/versions.tf b/modules/aws-security-group/modules/minio/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/minio/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/mongodb/README.md b/modules/aws-security-group/modules/mongodb/README.md new file mode 100644 index 0000000..8f252e8 --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/README.md @@ -0,0 +1,120 @@ +# mongodb - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "mongodb_security_group" { + source = "terraform-modules/security-group/aws//modules/mongodb" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **mongodb module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/mongodb/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"mongodb-27017-tcp",
"mongodb-27018-tcp",
"mongodb-27019-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/mongodb/auto_values.tf b/modules/aws-security-group/modules/mongodb/auto_values.tf new file mode 100644 index 0000000..b6d2436 --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["mongodb-27017-tcp", "mongodb-27018-tcp", "mongodb-27019-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mongodb/main.tf b/modules/aws-security-group/modules/mongodb/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/mongodb/outputs.tf b/modules/aws-security-group/modules/mongodb/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/mongodb/variables.tf b/modules/aws-security-group/modules/mongodb/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mongodb/versions.tf b/modules/aws-security-group/modules/mongodb/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/mongodb/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/mssql/README.md b/modules/aws-security-group/modules/mssql/README.md new file mode 100644 index 0000000..fb47633 --- /dev/null +++ b/modules/aws-security-group/modules/mssql/README.md @@ -0,0 +1,120 @@ +# mssql - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "mssql_security_group" { + source = "terraform-modules/security-group/aws//modules/mssql" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **mssql module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/mssql/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"mssql-tcp",
"mssql-udp",
"mssql-analytics-tcp",
"mssql-broker-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/mssql/auto_values.tf b/modules/aws-security-group/modules/mssql/auto_values.tf new file mode 100644 index 0000000..af370a5 --- /dev/null +++ b/modules/aws-security-group/modules/mssql/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["mssql-tcp", "mssql-udp", "mssql-analytics-tcp", "mssql-broker-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mssql/main.tf b/modules/aws-security-group/modules/mssql/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/mssql/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/mssql/outputs.tf b/modules/aws-security-group/modules/mssql/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/mssql/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/mssql/variables.tf b/modules/aws-security-group/modules/mssql/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/mssql/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mssql/versions.tf b/modules/aws-security-group/modules/mssql/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/mssql/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/mysql/README.md b/modules/aws-security-group/modules/mysql/README.md new file mode 100644 index 0000000..cba81eb --- /dev/null +++ b/modules/aws-security-group/modules/mysql/README.md @@ -0,0 +1,120 @@ +# mysql - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "mysql_security_group" { + source = "terraform-modules/security-group/aws//modules/mysql" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **mysql module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/mysql/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"mysql-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/mysql/auto_values.tf b/modules/aws-security-group/modules/mysql/auto_values.tf new file mode 100644 index 0000000..ad231a8 --- /dev/null +++ b/modules/aws-security-group/modules/mysql/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["mysql-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mysql/main.tf b/modules/aws-security-group/modules/mysql/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/mysql/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/mysql/outputs.tf b/modules/aws-security-group/modules/mysql/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/mysql/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/mysql/variables.tf b/modules/aws-security-group/modules/mysql/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/mysql/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/mysql/versions.tf b/modules/aws-security-group/modules/mysql/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/mysql/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/nfs/README.md b/modules/aws-security-group/modules/nfs/README.md new file mode 100644 index 0000000..1df5b24 --- /dev/null +++ b/modules/aws-security-group/modules/nfs/README.md @@ -0,0 +1,120 @@ +# nfs - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "nfs_security_group" { + source = "terraform-modules/security-group/aws//modules/nfs" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **nfs module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/nfs/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"nfs-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/nfs/auto_values.tf b/modules/aws-security-group/modules/nfs/auto_values.tf new file mode 100644 index 0000000..fb87606 --- /dev/null +++ b/modules/aws-security-group/modules/nfs/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["nfs-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/nfs/main.tf b/modules/aws-security-group/modules/nfs/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/nfs/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/nfs/outputs.tf b/modules/aws-security-group/modules/nfs/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/nfs/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/nfs/variables.tf b/modules/aws-security-group/modules/nfs/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/nfs/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/nfs/versions.tf b/modules/aws-security-group/modules/nfs/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/nfs/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/nomad/README.md b/modules/aws-security-group/modules/nomad/README.md new file mode 100644 index 0000000..fef0fa2 --- /dev/null +++ b/modules/aws-security-group/modules/nomad/README.md @@ -0,0 +1,120 @@ +# nomad - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "nomad_security_group" { + source = "terraform-modules/security-group/aws//modules/nomad" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **nomad module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/nomad/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"nomad-http-tcp",
"nomad-rpc-tcp",
"nomad-serf-tcp",
"nomad-serf-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/nomad/auto_values.tf b/modules/aws-security-group/modules/nomad/auto_values.tf new file mode 100644 index 0000000..08df67f --- /dev/null +++ b/modules/aws-security-group/modules/nomad/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["nomad-http-tcp", "nomad-rpc-tcp", "nomad-serf-tcp", "nomad-serf-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/nomad/main.tf b/modules/aws-security-group/modules/nomad/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/nomad/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/nomad/outputs.tf b/modules/aws-security-group/modules/nomad/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/nomad/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/nomad/variables.tf b/modules/aws-security-group/modules/nomad/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/nomad/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/nomad/versions.tf b/modules/aws-security-group/modules/nomad/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/nomad/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ntp/README.md b/modules/aws-security-group/modules/ntp/README.md new file mode 100644 index 0000000..2769103 --- /dev/null +++ b/modules/aws-security-group/modules/ntp/README.md @@ -0,0 +1,120 @@ +# ntp - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ntp_security_group" { + source = "terraform-modules/security-group/aws//modules/ntp" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ntp module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ntp/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ntp-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ntp/auto_values.tf b/modules/aws-security-group/modules/ntp/auto_values.tf new file mode 100644 index 0000000..cf4b735 --- /dev/null +++ b/modules/aws-security-group/modules/ntp/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ntp-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ntp/main.tf b/modules/aws-security-group/modules/ntp/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ntp/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ntp/outputs.tf b/modules/aws-security-group/modules/ntp/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ntp/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ntp/variables.tf b/modules/aws-security-group/modules/ntp/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ntp/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ntp/versions.tf b/modules/aws-security-group/modules/ntp/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ntp/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/openvpn/README.md b/modules/aws-security-group/modules/openvpn/README.md new file mode 100644 index 0000000..3c217aa --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/README.md @@ -0,0 +1,120 @@ +# openvpn - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "openvpn_security_group" { + source = "terraform-modules/security-group/aws//modules/openvpn" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **openvpn module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/openvpn/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"openvpn-udp",
"openvpn-tcp",
"openvpn-https-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/openvpn/auto_values.tf b/modules/aws-security-group/modules/openvpn/auto_values.tf new file mode 100644 index 0000000..d6f7bdc --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["openvpn-udp", "openvpn-tcp", "openvpn-https-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/openvpn/main.tf b/modules/aws-security-group/modules/openvpn/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/openvpn/outputs.tf b/modules/aws-security-group/modules/openvpn/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/openvpn/variables.tf b/modules/aws-security-group/modules/openvpn/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/openvpn/versions.tf b/modules/aws-security-group/modules/openvpn/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/openvpn/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/oracle-db/README.md b/modules/aws-security-group/modules/oracle-db/README.md new file mode 100644 index 0000000..4dbde5f --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/README.md @@ -0,0 +1,120 @@ +# oracle-db - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "oracle_db_security_group" { + source = "terraform-modules/security-group/aws//modules/oracle-db" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **oracle-db module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/oracle-db/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"oracle-db-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/oracle-db/auto_values.tf b/modules/aws-security-group/modules/oracle-db/auto_values.tf new file mode 100644 index 0000000..b74c717 --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["oracle-db-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/oracle-db/main.tf b/modules/aws-security-group/modules/oracle-db/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/oracle-db/outputs.tf b/modules/aws-security-group/modules/oracle-db/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/oracle-db/variables.tf b/modules/aws-security-group/modules/oracle-db/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/oracle-db/versions.tf b/modules/aws-security-group/modules/oracle-db/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/oracle-db/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/postgresql/README.md b/modules/aws-security-group/modules/postgresql/README.md new file mode 100644 index 0000000..43fef67 --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/README.md @@ -0,0 +1,120 @@ +# postgresql - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "postgresql_security_group" { + source = "terraform-modules/security-group/aws//modules/postgresql" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **postgresql module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/postgresql/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"postgresql-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/postgresql/auto_values.tf b/modules/aws-security-group/modules/postgresql/auto_values.tf new file mode 100644 index 0000000..338998e --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["postgresql-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/postgresql/main.tf b/modules/aws-security-group/modules/postgresql/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/postgresql/outputs.tf b/modules/aws-security-group/modules/postgresql/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/postgresql/variables.tf b/modules/aws-security-group/modules/postgresql/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/postgresql/versions.tf b/modules/aws-security-group/modules/postgresql/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/postgresql/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/prometheus/README.md b/modules/aws-security-group/modules/prometheus/README.md new file mode 100644 index 0000000..2f529c3 --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/README.md @@ -0,0 +1,120 @@ +# prometheus - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "prometheus_security_group" { + source = "terraform-modules/security-group/aws//modules/prometheus" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **prometheus module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/prometheus/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"prometheus-http-tcp",
"prometheus-pushgateway-http-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/prometheus/auto_values.tf b/modules/aws-security-group/modules/prometheus/auto_values.tf new file mode 100644 index 0000000..89ebe86 --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["prometheus-http-tcp", "prometheus-pushgateway-http-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/prometheus/main.tf b/modules/aws-security-group/modules/prometheus/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/prometheus/outputs.tf b/modules/aws-security-group/modules/prometheus/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/prometheus/variables.tf b/modules/aws-security-group/modules/prometheus/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/prometheus/versions.tf b/modules/aws-security-group/modules/prometheus/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/prometheus/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/puppet/README.md b/modules/aws-security-group/modules/puppet/README.md new file mode 100644 index 0000000..0c11d40 --- /dev/null +++ b/modules/aws-security-group/modules/puppet/README.md @@ -0,0 +1,120 @@ +# puppet - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "puppet_security_group" { + source = "terraform-modules/security-group/aws//modules/puppet" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **puppet module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/puppet/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"puppet-tcp",
"puppetdb-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/puppet/auto_values.tf b/modules/aws-security-group/modules/puppet/auto_values.tf new file mode 100644 index 0000000..afe6d4d --- /dev/null +++ b/modules/aws-security-group/modules/puppet/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["puppet-tcp", "puppetdb-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/puppet/main.tf b/modules/aws-security-group/modules/puppet/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/puppet/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/puppet/outputs.tf b/modules/aws-security-group/modules/puppet/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/puppet/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/puppet/variables.tf b/modules/aws-security-group/modules/puppet/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/puppet/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/puppet/versions.tf b/modules/aws-security-group/modules/puppet/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/puppet/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/rabbitmq/README.md b/modules/aws-security-group/modules/rabbitmq/README.md new file mode 100644 index 0000000..6bcd846 --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/README.md @@ -0,0 +1,120 @@ +# rabbitmq - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "rabbitmq_security_group" { + source = "terraform-modules/security-group/aws//modules/rabbitmq" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **rabbitmq module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/rabbitmq/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"rabbitmq-4369-tcp",
"rabbitmq-5671-tcp",
"rabbitmq-5672-tcp",
"rabbitmq-15672-tcp",
"rabbitmq-25672-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/rabbitmq/auto_values.tf b/modules/aws-security-group/modules/rabbitmq/auto_values.tf new file mode 100644 index 0000000..30a7676 --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["rabbitmq-4369-tcp", "rabbitmq-5671-tcp", "rabbitmq-5672-tcp", "rabbitmq-15672-tcp", "rabbitmq-25672-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/rabbitmq/main.tf b/modules/aws-security-group/modules/rabbitmq/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/rabbitmq/outputs.tf b/modules/aws-security-group/modules/rabbitmq/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/rabbitmq/variables.tf b/modules/aws-security-group/modules/rabbitmq/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/rabbitmq/versions.tf b/modules/aws-security-group/modules/rabbitmq/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/rabbitmq/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/rdp/README.md b/modules/aws-security-group/modules/rdp/README.md new file mode 100644 index 0000000..e2dee66 --- /dev/null +++ b/modules/aws-security-group/modules/rdp/README.md @@ -0,0 +1,120 @@ +# rdp - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "rdp_security_group" { + source = "terraform-modules/security-group/aws//modules/rdp" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **rdp module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/rdp/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"rdp-tcp",
"rdp-udp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/rdp/auto_values.tf b/modules/aws-security-group/modules/rdp/auto_values.tf new file mode 100644 index 0000000..dc55929 --- /dev/null +++ b/modules/aws-security-group/modules/rdp/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["rdp-tcp", "rdp-udp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/rdp/main.tf b/modules/aws-security-group/modules/rdp/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/rdp/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/rdp/outputs.tf b/modules/aws-security-group/modules/rdp/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/rdp/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/rdp/variables.tf b/modules/aws-security-group/modules/rdp/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/rdp/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/rdp/versions.tf b/modules/aws-security-group/modules/rdp/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/rdp/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/redis/README.md b/modules/aws-security-group/modules/redis/README.md new file mode 100644 index 0000000..56486c5 --- /dev/null +++ b/modules/aws-security-group/modules/redis/README.md @@ -0,0 +1,120 @@ +# redis - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "redis_security_group" { + source = "terraform-modules/security-group/aws//modules/redis" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **redis module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/redis/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"redis-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/redis/auto_values.tf b/modules/aws-security-group/modules/redis/auto_values.tf new file mode 100644 index 0000000..8563abf --- /dev/null +++ b/modules/aws-security-group/modules/redis/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["redis-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/redis/main.tf b/modules/aws-security-group/modules/redis/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/redis/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/redis/outputs.tf b/modules/aws-security-group/modules/redis/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/redis/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/redis/variables.tf b/modules/aws-security-group/modules/redis/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/redis/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/redis/versions.tf b/modules/aws-security-group/modules/redis/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/redis/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/redshift/README.md b/modules/aws-security-group/modules/redshift/README.md new file mode 100644 index 0000000..e25a86c --- /dev/null +++ b/modules/aws-security-group/modules/redshift/README.md @@ -0,0 +1,120 @@ +# redshift - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "redshift_security_group" { + source = "terraform-modules/security-group/aws//modules/redshift" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **redshift module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/redshift/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"redshift-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/redshift/auto_values.tf b/modules/aws-security-group/modules/redshift/auto_values.tf new file mode 100644 index 0000000..c4d26e7 --- /dev/null +++ b/modules/aws-security-group/modules/redshift/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["redshift-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/redshift/main.tf b/modules/aws-security-group/modules/redshift/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/redshift/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/redshift/outputs.tf b/modules/aws-security-group/modules/redshift/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/redshift/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/redshift/variables.tf b/modules/aws-security-group/modules/redshift/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/redshift/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/redshift/versions.tf b/modules/aws-security-group/modules/redshift/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/redshift/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/smtp-submission/README.md b/modules/aws-security-group/modules/smtp-submission/README.md new file mode 100644 index 0000000..d8cd856 --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/README.md @@ -0,0 +1,120 @@ +# smtp-submission - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "smtp_submission_security_group" { + source = "terraform-modules/security-group/aws//modules/smtp-submission" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **smtp-submission module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/smtp-submission/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"smtp-submission-587-tcp",
"smtp-submission-2587-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/smtp-submission/auto_values.tf b/modules/aws-security-group/modules/smtp-submission/auto_values.tf new file mode 100644 index 0000000..1b4902f --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["smtp-submission-587-tcp", "smtp-submission-2587-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtp-submission/main.tf b/modules/aws-security-group/modules/smtp-submission/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/smtp-submission/outputs.tf b/modules/aws-security-group/modules/smtp-submission/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/smtp-submission/variables.tf b/modules/aws-security-group/modules/smtp-submission/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtp-submission/versions.tf b/modules/aws-security-group/modules/smtp-submission/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/smtp-submission/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/smtp/README.md b/modules/aws-security-group/modules/smtp/README.md new file mode 100644 index 0000000..c25fbc1 --- /dev/null +++ b/modules/aws-security-group/modules/smtp/README.md @@ -0,0 +1,120 @@ +# smtp - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "smtp_security_group" { + source = "terraform-modules/security-group/aws//modules/smtp" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **smtp module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/smtp/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"smtp-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/smtp/auto_values.tf b/modules/aws-security-group/modules/smtp/auto_values.tf new file mode 100644 index 0000000..59fe543 --- /dev/null +++ b/modules/aws-security-group/modules/smtp/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["smtp-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtp/main.tf b/modules/aws-security-group/modules/smtp/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/smtp/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/smtp/outputs.tf b/modules/aws-security-group/modules/smtp/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/smtp/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/smtp/variables.tf b/modules/aws-security-group/modules/smtp/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/smtp/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtp/versions.tf b/modules/aws-security-group/modules/smtp/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/smtp/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/smtps/README.md b/modules/aws-security-group/modules/smtps/README.md new file mode 100644 index 0000000..04e544c --- /dev/null +++ b/modules/aws-security-group/modules/smtps/README.md @@ -0,0 +1,120 @@ +# smtps - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "smtps_security_group" { + source = "terraform-modules/security-group/aws//modules/smtps" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **smtps module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/smtps/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"smtps-465-tcp",
"smtps-2465-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/smtps/auto_values.tf b/modules/aws-security-group/modules/smtps/auto_values.tf new file mode 100644 index 0000000..5c43085 --- /dev/null +++ b/modules/aws-security-group/modules/smtps/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["smtps-465-tcp", "smtps-2465-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtps/main.tf b/modules/aws-security-group/modules/smtps/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/smtps/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/smtps/outputs.tf b/modules/aws-security-group/modules/smtps/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/smtps/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/smtps/variables.tf b/modules/aws-security-group/modules/smtps/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/smtps/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/smtps/versions.tf b/modules/aws-security-group/modules/smtps/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/smtps/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/solr/README.md b/modules/aws-security-group/modules/solr/README.md new file mode 100644 index 0000000..9221b7d --- /dev/null +++ b/modules/aws-security-group/modules/solr/README.md @@ -0,0 +1,120 @@ +# solr - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "solr_security_group" { + source = "terraform-modules/security-group/aws//modules/solr" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **solr module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/solr/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"solr-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/solr/auto_values.tf b/modules/aws-security-group/modules/solr/auto_values.tf new file mode 100644 index 0000000..78c11b4 --- /dev/null +++ b/modules/aws-security-group/modules/solr/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["solr-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/solr/main.tf b/modules/aws-security-group/modules/solr/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/solr/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/solr/outputs.tf b/modules/aws-security-group/modules/solr/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/solr/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/solr/variables.tf b/modules/aws-security-group/modules/solr/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/solr/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/solr/versions.tf b/modules/aws-security-group/modules/solr/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/solr/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/splunk/README.md b/modules/aws-security-group/modules/splunk/README.md new file mode 100644 index 0000000..2993c01 --- /dev/null +++ b/modules/aws-security-group/modules/splunk/README.md @@ -0,0 +1,120 @@ +# splunk - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "splunk_security_group" { + source = "terraform-modules/security-group/aws//modules/splunk" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **splunk module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/splunk/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"splunk-indexer-tcp",
"splunk-clients-tcp",
"splunk-splunkd-tcp",
"splunk-hec-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/splunk/auto_values.tf b/modules/aws-security-group/modules/splunk/auto_values.tf new file mode 100644 index 0000000..8c6468b --- /dev/null +++ b/modules/aws-security-group/modules/splunk/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["splunk-indexer-tcp", "splunk-clients-tcp", "splunk-splunkd-tcp", "splunk-hec-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/splunk/main.tf b/modules/aws-security-group/modules/splunk/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/splunk/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/splunk/outputs.tf b/modules/aws-security-group/modules/splunk/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/splunk/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/splunk/variables.tf b/modules/aws-security-group/modules/splunk/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/splunk/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/splunk/versions.tf b/modules/aws-security-group/modules/splunk/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/splunk/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/squid/README.md b/modules/aws-security-group/modules/squid/README.md new file mode 100644 index 0000000..e740eaf --- /dev/null +++ b/modules/aws-security-group/modules/squid/README.md @@ -0,0 +1,120 @@ +# squid - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "squid_security_group" { + source = "terraform-modules/security-group/aws//modules/squid" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **squid module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/squid/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"squid-proxy-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/squid/auto_values.tf b/modules/aws-security-group/modules/squid/auto_values.tf new file mode 100644 index 0000000..fa95425 --- /dev/null +++ b/modules/aws-security-group/modules/squid/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["squid-proxy-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/squid/main.tf b/modules/aws-security-group/modules/squid/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/squid/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/squid/outputs.tf b/modules/aws-security-group/modules/squid/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/squid/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/squid/variables.tf b/modules/aws-security-group/modules/squid/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/squid/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/squid/versions.tf b/modules/aws-security-group/modules/squid/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/squid/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/ssh/README.md b/modules/aws-security-group/modules/ssh/README.md new file mode 100644 index 0000000..b2fbfb5 --- /dev/null +++ b/modules/aws-security-group/modules/ssh/README.md @@ -0,0 +1,120 @@ +# ssh - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "ssh_security_group" { + source = "terraform-modules/security-group/aws//modules/ssh" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **ssh module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/ssh/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"ssh-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/ssh/auto_values.tf b/modules/aws-security-group/modules/ssh/auto_values.tf new file mode 100644 index 0000000..da270c3 --- /dev/null +++ b/modules/aws-security-group/modules/ssh/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["ssh-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ssh/main.tf b/modules/aws-security-group/modules/ssh/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/ssh/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/ssh/outputs.tf b/modules/aws-security-group/modules/ssh/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/ssh/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/ssh/variables.tf b/modules/aws-security-group/modules/ssh/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/ssh/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/ssh/versions.tf b/modules/aws-security-group/modules/ssh/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/ssh/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/storm/README.md b/modules/aws-security-group/modules/storm/README.md new file mode 100644 index 0000000..0155a5c --- /dev/null +++ b/modules/aws-security-group/modules/storm/README.md @@ -0,0 +1,120 @@ +# storm - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "storm_security_group" { + source = "terraform-modules/security-group/aws//modules/storm" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **storm module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/storm/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"storm-nimbus-tcp",
"storm-ui-tcp",
"storm-supervisor-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/storm/auto_values.tf b/modules/aws-security-group/modules/storm/auto_values.tf new file mode 100644 index 0000000..7a049c6 --- /dev/null +++ b/modules/aws-security-group/modules/storm/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["storm-nimbus-tcp", "storm-ui-tcp", "storm-supervisor-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/storm/main.tf b/modules/aws-security-group/modules/storm/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/storm/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/storm/outputs.tf b/modules/aws-security-group/modules/storm/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/storm/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/storm/variables.tf b/modules/aws-security-group/modules/storm/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/storm/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/storm/versions.tf b/modules/aws-security-group/modules/storm/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/storm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/wazuh/README.md b/modules/aws-security-group/modules/wazuh/README.md new file mode 100644 index 0000000..8a8f75e --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/README.md @@ -0,0 +1,120 @@ +# wazuh - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "wazuh_security_group" { + source = "terraform-modules/security-group/aws//modules/wazuh" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **wazuh module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/wazuh/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"wazuh-server-agent-connection-tcp",
"wazuh-server-agent-connection-udp",
"wazuh-server-agent-enrollment",
"wazuh-server-agent-cluster-daemon",
"wazuh-server-syslog-collector-tcp",
"wazuh-server-syslog-collector-udp",
"wazuh-server-restful-api",
"wazuh-indexer-restful-api",
"wazuh-dashboard"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/wazuh/auto_values.tf b/modules/aws-security-group/modules/wazuh/auto_values.tf new file mode 100644 index 0000000..56dbf96 --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["wazuh-server-agent-connection-tcp", "wazuh-server-agent-connection-udp", "wazuh-server-agent-enrollment", "wazuh-server-agent-cluster-daemon", "wazuh-server-syslog-collector-tcp", "wazuh-server-syslog-collector-udp", "wazuh-server-restful-api", "wazuh-indexer-restful-api", "wazuh-dashboard"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/wazuh/main.tf b/modules/aws-security-group/modules/wazuh/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/wazuh/outputs.tf b/modules/aws-security-group/modules/wazuh/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/wazuh/variables.tf b/modules/aws-security-group/modules/wazuh/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/wazuh/versions.tf b/modules/aws-security-group/modules/wazuh/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/wazuh/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/web/README.md b/modules/aws-security-group/modules/web/README.md new file mode 100644 index 0000000..738e9b9 --- /dev/null +++ b/modules/aws-security-group/modules/web/README.md @@ -0,0 +1,120 @@ +# web - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "web_security_group" { + source = "terraform-modules/security-group/aws//modules/web" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **web module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/web/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"http-80-tcp",
"http-8080-tcp",
"https-443-tcp",
"web-jmx-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/web/auto_values.tf b/modules/aws-security-group/modules/web/auto_values.tf new file mode 100644 index 0000000..03bdb52 --- /dev/null +++ b/modules/aws-security-group/modules/web/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["http-80-tcp", "http-8080-tcp", "https-443-tcp", "web-jmx-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/web/main.tf b/modules/aws-security-group/modules/web/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/web/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/web/outputs.tf b/modules/aws-security-group/modules/web/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/web/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/web/variables.tf b/modules/aws-security-group/modules/web/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/web/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/web/versions.tf b/modules/aws-security-group/modules/web/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/web/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/winrm/README.md b/modules/aws-security-group/modules/winrm/README.md new file mode 100644 index 0000000..d17d90a --- /dev/null +++ b/modules/aws-security-group/modules/winrm/README.md @@ -0,0 +1,120 @@ +# winrm - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "winrm_security_group" { + source = "terraform-modules/security-group/aws//modules/winrm" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **winrm module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/winrm/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"winrm-http-tcp",
"winrm-https-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/winrm/auto_values.tf b/modules/aws-security-group/modules/winrm/auto_values.tf new file mode 100644 index 0000000..8ea1ff5 --- /dev/null +++ b/modules/aws-security-group/modules/winrm/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["winrm-http-tcp", "winrm-https-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/winrm/main.tf b/modules/aws-security-group/modules/winrm/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/winrm/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/winrm/outputs.tf b/modules/aws-security-group/modules/winrm/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/winrm/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/winrm/variables.tf b/modules/aws-security-group/modules/winrm/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/winrm/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/winrm/versions.tf b/modules/aws-security-group/modules/winrm/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/winrm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/zabbix/README.md b/modules/aws-security-group/modules/zabbix/README.md new file mode 100644 index 0000000..ef17da5 --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/README.md @@ -0,0 +1,120 @@ +# zabbix - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "zabbix_security_group" { + source = "terraform-modules/security-group/aws//modules/zabbix" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **zabbix module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/zabbix/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"zabbix-server",
"zabbix-proxy",
"zabbix-agent"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/zabbix/auto_values.tf b/modules/aws-security-group/modules/zabbix/auto_values.tf new file mode 100644 index 0000000..0869c7a --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["zabbix-server", "zabbix-proxy", "zabbix-agent"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zabbix/main.tf b/modules/aws-security-group/modules/zabbix/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/zabbix/outputs.tf b/modules/aws-security-group/modules/zabbix/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/zabbix/variables.tf b/modules/aws-security-group/modules/zabbix/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zabbix/versions.tf b/modules/aws-security-group/modules/zabbix/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/zabbix/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/zipkin/README.md b/modules/aws-security-group/modules/zipkin/README.md new file mode 100644 index 0000000..188deaf --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/README.md @@ -0,0 +1,120 @@ +# zipkin - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "zipkin_security_group" { + source = "terraform-modules/security-group/aws//modules/zipkin" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **zipkin module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/zipkin/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"zipkin-admin-tcp",
"zipkin-admin-query-tcp",
"zipkin-admin-web-tcp",
"zipkin-query-tcp",
"zipkin-web-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/zipkin/auto_values.tf b/modules/aws-security-group/modules/zipkin/auto_values.tf new file mode 100644 index 0000000..d5abf3e --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["zipkin-admin-tcp", "zipkin-admin-query-tcp", "zipkin-admin-web-tcp", "zipkin-query-tcp", "zipkin-web-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zipkin/main.tf b/modules/aws-security-group/modules/zipkin/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/zipkin/outputs.tf b/modules/aws-security-group/modules/zipkin/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/zipkin/variables.tf b/modules/aws-security-group/modules/zipkin/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zipkin/versions.tf b/modules/aws-security-group/modules/zipkin/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/zipkin/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/modules/zookeeper/README.md b/modules/aws-security-group/modules/zookeeper/README.md new file mode 100644 index 0000000..fdd12cc --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/README.md @@ -0,0 +1,120 @@ +# zookeeper - AWS EC2-VPC Security Group Terraform module + +## Usage + +```hcl +module "zookeeper_security_group" { + source = "terraform-modules/security-group/aws//modules/zookeeper" + version = "~> 4.0" + + # omitted... +} +``` + +All automatic values **zookeeper module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/blob/main/modules/zookeeper/auto_values.tf). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.29 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sg](#module\_sg) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auto\_computed\_egress\_rules](#input\_auto\_computed\_egress\_rules) | List of computed egress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_egress\_with\_self](#input\_auto\_computed\_egress\_with\_self) | List of maps defining computed egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_computed\_ingress\_rules](#input\_auto\_computed\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` | `[]` | no | +| [auto\_computed\_ingress\_with\_self](#input\_auto\_computed\_ingress\_with\_self) | List of maps defining computed ingress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_egress\_rules](#input\_auto\_egress\_rules) | List of egress rules to add automatically | `list(string)` |
[
"all-all"
]
| no | +| [auto\_egress\_with\_self](#input\_auto\_egress\_with\_self) | List of maps defining egress rules with self to add automatically | `list(map(string))` | `[]` | no | +| [auto\_ingress\_rules](#input\_auto\_ingress\_rules) | List of ingress rules to add automatically | `list(string)` |
[
"zookeeper-2181-tcp",
"zookeeper-2182-tls-tcp",
"zookeeper-2888-tcp",
"zookeeper-3888-tcp",
"zookeeper-jmx-tcp"
]
| no | +| [auto\_ingress\_with\_self](#input\_auto\_ingress\_with\_self) | List of maps defining ingress rules with self to add automatically | `list(map(string))` |
[
{
"rule": "all-all"
}
]
| no | +| [auto\_number\_of\_computed\_egress\_rules](#input\_auto\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_egress\_with\_self](#input\_auto\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_rules](#input\_auto\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [auto\_number\_of\_computed\_ingress\_with\_self](#input\_auto\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [computed\_egress\_cidr\_blocks](#input\_computed\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [computed\_egress\_ipv6\_cidr\_blocks](#input\_computed\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed egress rules | `list(string)` |
[
"::/0"
]
| no | +| [computed\_egress\_prefix\_list\_ids](#input\_computed\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `list(string)` | `[]` | no | +| [computed\_egress\_rules](#input\_computed\_egress\_rules) | List of computed egress rules to create by name | `list(string)` | `[]` | no | +| [computed\_egress\_with\_cidr\_blocks](#input\_computed\_egress\_with\_cidr\_blocks) | List of computed egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_computed\_egress\_with\_ipv6\_cidr\_blocks) | List of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_self](#input\_computed\_egress\_with\_self) | List of computed egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_egress\_with\_source\_security\_group\_id](#input\_computed\_egress\_with\_source\_security\_group\_id) | List of computed egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_cidr\_blocks](#input\_computed\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_prefix\_list\_ids](#input\_computed\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `list(string)` | `[]` | no | +| [computed\_ingress\_rules](#input\_computed\_ingress\_rules) | List of computed ingress rules to create by name | `list(string)` | `[]` | no | +| [computed\_ingress\_with\_cidr\_blocks](#input\_computed\_ingress\_with\_cidr\_blocks) | List of computed ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | List of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_self](#input\_computed\_ingress\_with\_self) | List of computed ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [computed\_ingress\_with\_source\_security\_group\_id](#input\_computed\_ingress\_with\_source\_security\_group\_id) | List of computed ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [create](#input\_create) | Whether to create security group and all rules | `bool` | `true` | no | +| [description](#input\_description) | Description of security group | `string` | `"Security Group managed by Terraform"` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all egress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [egress\_ipv6\_cidr\_blocks](#input\_egress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all egress rules | `list(string)` |
[
"::/0"
]
| no | +| [egress\_prefix\_list\_ids](#input\_egress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules | `list(string)` | `[]` | no | +| [egress\_rules](#input\_egress\_rules) | List of egress rules to create by name | `list(string)` | `[]` | no | +| [egress\_with\_cidr\_blocks](#input\_egress\_with\_cidr\_blocks) | List of egress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_ipv6\_cidr\_blocks](#input\_egress\_with\_ipv6\_cidr\_blocks) | List of egress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [egress\_with\_self](#input\_egress\_with\_self) | List of egress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [egress\_with\_source\_security\_group\_id](#input\_egress\_with\_source\_security\_group\_id) | List of egress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [ingress\_cidr\_blocks](#input\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_ipv6\_cidr\_blocks](#input\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_prefix\_list\_ids](#input\_ingress\_prefix\_list\_ids) | List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules | `list(string)` | `[]` | no | +| [ingress\_rules](#input\_ingress\_rules) | List of ingress rules to create by name | `list(string)` | `[]` | no | +| [ingress\_with\_cidr\_blocks](#input\_ingress\_with\_cidr\_blocks) | List of ingress rules to create where 'cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_ipv6\_cidr\_blocks](#input\_ingress\_with\_ipv6\_cidr\_blocks) | List of ingress rules to create where 'ipv6\_cidr\_blocks' is used | `list(map(string))` | `[]` | no | +| [ingress\_with\_self](#input\_ingress\_with\_self) | List of ingress rules to create where 'self' is defined | `list(map(string))` | `[]` | no | +| [ingress\_with\_source\_security\_group\_id](#input\_ingress\_with\_source\_security\_group\_id) | List of ingress rules to create where 'source\_security\_group\_id' is used | `list(map(string))` | `[]` | no | +| [name](#input\_name) | Name of security group | `string` | n/a | yes | +| [number\_of\_computed\_egress\_cidr\_blocks](#input\_number\_of\_computed\_egress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_prefix\_list\_ids](#input\_number\_of\_computed\_egress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules | `number` | `0` | no | +| [number\_of\_computed\_egress\_rules](#input\_number\_of\_computed\_egress\_rules) | Number of computed egress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_cidr\_blocks) | Number of computed egress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_egress\_with\_ipv6\_cidr\_blocks) | Number of computed egress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_self](#input\_number\_of\_computed\_egress\_with\_self) | Number of computed egress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_egress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_egress\_with\_source\_security\_group\_id) | Number of computed egress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_cidr\_blocks) | Number of IPv4 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_ipv6\_cidr\_blocks) | Number of IPv6 CIDR ranges to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_prefix\_list\_ids](#input\_number\_of\_computed\_ingress\_prefix\_list\_ids) | Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules | `number` | `0` | no | +| [number\_of\_computed\_ingress\_rules](#input\_number\_of\_computed\_ingress\_rules) | Number of computed ingress rules to create by name | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_cidr\_blocks) | Number of computed ingress rules to create where 'cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks](#input\_number\_of\_computed\_ingress\_with\_ipv6\_cidr\_blocks) | Number of computed ingress rules to create where 'ipv6\_cidr\_blocks' is used | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_self](#input\_number\_of\_computed\_ingress\_with\_self) | Number of computed ingress rules to create where 'self' is defined | `number` | `0` | no | +| [number\_of\_computed\_ingress\_with\_source\_security\_group\_id](#input\_number\_of\_computed\_ingress\_with\_source\_security\_group\_id) | Number of computed ingress rules to create where 'source\_security\_group\_id' is used | `number` | `0` | no | +| [revoke\_rules\_on\_delete](#input\_revoke\_rules\_on\_delete) | Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR. | `bool` | `false` | no | +| [tags](#input\_tags) | A mapping of tags to assign to security group | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Whether to use name\_prefix or fixed name. Should be true to able to update security group name after initial creation | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where to create security group | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_description](#output\_security\_group\_description) | The description of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [security\_group\_name](#output\_security\_group\_name) | The name of the security group | +| [security\_group\_owner\_id](#output\_security\_group\_owner\_id) | The owner ID | +| [security\_group\_vpc\_id](#output\_security\_group\_vpc\_id) | The VPC ID | + diff --git a/modules/aws-security-group/modules/zookeeper/auto_values.tf b/modules/aws-security-group/modules/zookeeper/auto_values.tf new file mode 100644 index 0000000..fad05d7 --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/auto_values.tf @@ -0,0 +1,78 @@ +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = ["zookeeper-2181-tcp", "zookeeper-2182-tls-tcp", "zookeeper-2888-tcp", "zookeeper-3888-tcp", "zookeeper-jmx-tcp"] +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = [{ "rule" = "all-all" }] +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = ["all-all"] +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = [] +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = [] +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zookeeper/main.tf b/modules/aws-security-group/modules/zookeeper/main.tf new file mode 100644 index 0000000..758bb7b --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/main.tf @@ -0,0 +1,115 @@ +module "sg" { + source = "../../" + + create = var.create + name = var.name + use_name_prefix = var.use_name_prefix + description = var.description + vpc_id = var.vpc_id + revoke_rules_on_delete = var.revoke_rules_on_delete + tags = var.tags + + ########## + # Ingress + ########## + # Rules by names - open for default CIDR + ingress_rules = sort(compact(distinct(concat(var.auto_ingress_rules, var.ingress_rules, [""])))) + + # Open for self + ingress_with_self = concat(var.auto_ingress_with_self, var.ingress_with_self) + + # Open to IPv4 cidr blocks + ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + ingress_with_ipv6_cidr_blocks = var.ingress_with_ipv6_cidr_blocks + + # Open for security group id + ingress_with_source_security_group_id = var.ingress_with_source_security_group_id + + # Default ingress CIDR blocks + ingress_cidr_blocks = var.ingress_cidr_blocks + ingress_ipv6_cidr_blocks = var.ingress_ipv6_cidr_blocks + + # Default prefix list ids + ingress_prefix_list_ids = var.ingress_prefix_list_ids + + ################### + # Computed Ingress + ################### + # Rules by names - open for default CIDR + computed_ingress_rules = sort(compact(distinct(concat(var.auto_computed_ingress_rules, var.computed_ingress_rules, [""])))) + + # Open for self + computed_ingress_with_self = concat(var.auto_computed_ingress_with_self, var.computed_ingress_with_self) + + # Open to IPv4 cidr blocks + computed_ingress_with_cidr_blocks = var.computed_ingress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_ingress_with_ipv6_cidr_blocks = var.computed_ingress_with_ipv6_cidr_blocks + + # Open for security group id + computed_ingress_with_source_security_group_id = var.computed_ingress_with_source_security_group_id + + ############################# + # Number of computed ingress + ############################# + number_of_computed_ingress_rules = var.auto_number_of_computed_ingress_rules + var.number_of_computed_ingress_rules + number_of_computed_ingress_with_self = var.auto_number_of_computed_ingress_with_self + var.number_of_computed_ingress_with_self + number_of_computed_ingress_with_cidr_blocks = var.number_of_computed_ingress_with_cidr_blocks + number_of_computed_ingress_with_ipv6_cidr_blocks = var.number_of_computed_ingress_with_ipv6_cidr_blocks + number_of_computed_ingress_with_source_security_group_id = var.number_of_computed_ingress_with_source_security_group_id + + ######### + # Egress + ######### + # Rules by names - open for default CIDR + egress_rules = sort(compact(distinct(concat(var.auto_egress_rules, var.egress_rules, [""])))) + + # Open for self + egress_with_self = concat(var.auto_egress_with_self, var.egress_with_self) + + # Open to IPv4 cidr blocks + egress_with_cidr_blocks = var.egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + egress_with_ipv6_cidr_blocks = var.egress_with_ipv6_cidr_blocks + + # Open for security group id + egress_with_source_security_group_id = var.egress_with_source_security_group_id + + # Default egress CIDR blocks + egress_cidr_blocks = var.egress_cidr_blocks + egress_ipv6_cidr_blocks = var.egress_ipv6_cidr_blocks + + # Default prefix list ids + egress_prefix_list_ids = var.egress_prefix_list_ids + + ################## + # Computed Egress + ################## + # Rules by names - open for default CIDR + computed_egress_rules = sort(compact(distinct(concat(var.auto_computed_egress_rules, var.computed_egress_rules, [""])))) + + # Open for self + computed_egress_with_self = concat(var.auto_computed_egress_with_self, var.computed_egress_with_self) + + # Open to IPv4 cidr blocks + computed_egress_with_cidr_blocks = var.computed_egress_with_cidr_blocks + + # Open to IPv6 cidr blocks + computed_egress_with_ipv6_cidr_blocks = var.computed_egress_with_ipv6_cidr_blocks + + # Open for security group id + computed_egress_with_source_security_group_id = var.computed_egress_with_source_security_group_id + + ############################# + # Number of computed egress + ############################# + number_of_computed_egress_rules = var.auto_number_of_computed_egress_rules + var.number_of_computed_egress_rules + number_of_computed_egress_with_self = var.auto_number_of_computed_egress_with_self + var.number_of_computed_egress_with_self + number_of_computed_egress_with_cidr_blocks = var.number_of_computed_egress_with_cidr_blocks + number_of_computed_egress_with_ipv6_cidr_blocks = var.number_of_computed_egress_with_ipv6_cidr_blocks + number_of_computed_egress_with_source_security_group_id = var.number_of_computed_egress_with_source_security_group_id +} diff --git a/modules/aws-security-group/modules/zookeeper/outputs.tf b/modules/aws-security-group/modules/zookeeper/outputs.tf new file mode 100644 index 0000000..481dd14 --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = module.sg.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.sg.security_group_id +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = module.sg.security_group_vpc_id +} + +output "security_group_owner_id" { + description = "The owner ID" + value = module.sg.security_group_owner_id +} + +output "security_group_name" { + description = "The name of the security group" + value = module.sg.security_group_name +} + +output "security_group_description" { + description = "The description of the security group" + value = module.sg.security_group_description +} diff --git a/modules/aws-security-group/modules/zookeeper/variables.tf b/modules/aws-security-group/modules/zookeeper/variables.tf new file mode 100644 index 0000000..4d33156 --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/variables.tf @@ -0,0 +1,348 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string +} + +variable "name" { + description = "Name of security group" + type = string +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed ingress rules" + type = list(string) + default = [] +} + +variable "computed_ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = list(string) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed ingress rules" + type = number + default = 0 +} + +variable "number_of_computed_ingress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed ingress rules" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "computed_egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all computed egress rules" + type = list(string) + default = ["::/0"] +} + +variable "computed_egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = list(string) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_cidr_blocks" { + description = "Number of IPv4 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_ipv6_cidr_blocks" { + description = "Number of IPv6 CIDR ranges to use on all computed egress rules" + type = number + default = 0 +} + +variable "number_of_computed_egress_prefix_list_ids" { + description = "Number of prefix list IDs (for allowing access to VPC endpoints) to use on all computed egress rules" + type = number + default = 0 +} diff --git a/modules/aws-security-group/modules/zookeeper/versions.tf b/modules/aws-security-group/modules/zookeeper/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/modules/zookeeper/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-group/outputs.tf b/modules/aws-security-group/outputs.tf new file mode 100644 index 0000000..f9ffe98 --- /dev/null +++ b/modules/aws-security-group/outputs.tf @@ -0,0 +1,29 @@ +output "security_group_arn" { + description = "The ARN of the security group" + value = try(aws_security_group.this[0].arn, aws_security_group.this_name_prefix[0].arn, "") +} + +output "security_group_id" { + description = "The ID of the security group" + value = try(aws_security_group.this[0].id, aws_security_group.this_name_prefix[0].id, "") +} + +output "security_group_vpc_id" { + description = "The VPC ID" + value = try(aws_security_group.this[0].vpc_id, aws_security_group.this_name_prefix[0].vpc_id, "") +} + +output "security_group_owner_id" { + description = "The owner ID" + value = try(aws_security_group.this[0].owner_id, aws_security_group.this_name_prefix[0].owner_id, "") +} + +output "security_group_name" { + description = "The name of the security group" + value = try(aws_security_group.this[0].name, aws_security_group.this_name_prefix[0].name, "") +} + +output "security_group_description" { + description = "The description of the security group" + value = try(aws_security_group.this[0].description, aws_security_group.this_name_prefix[0].description, "") +} diff --git a/modules/aws-security-group/rules.tf b/modules/aws-security-group/rules.tf new file mode 100644 index 0000000..21fe7af --- /dev/null +++ b/modules/aws-security-group/rules.tf @@ -0,0 +1,484 @@ +variable "rules" { + description = "Map of known security group rules (define as 'name' = ['from port', 'to port', 'protocol', 'description'])" + type = map(list(any)) + + # Protocols (tcp, udp, icmp, all - are allowed keywords) or numbers (from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml): + # All = -1, IPV4-ICMP = 1, TCP = 6, UDP = 17, IPV6-ICMP = 58 + default = { + # ActiveMQ + activemq-5671-tcp = [5671, 5671, "tcp", "ActiveMQ AMQP"] + activemq-8883-tcp = [8883, 8883, "tcp", "ActiveMQ MQTT"] + activemq-61614-tcp = [61614, 61614, "tcp", "ActiveMQ STOMP"] + activemq-61617-tcp = [61617, 61617, "tcp", "ActiveMQ OpenWire"] + activemq-61619-tcp = [61619, 61619, "tcp", "ActiveMQ WebSocket"] + # Alert Manager + alertmanager-9093-tcp = [9093, 9093, "tcp", "Alert Manager"] + alertmanager-9094-tcp = [9094, 9094, "tcp", "Alert Manager Cluster"] + # Carbon relay + carbon-line-in-tcp = [2003, 2003, "tcp", "Carbon line-in"] + carbon-line-in-udp = [2003, 2003, "udp", "Carbon line-in"] + carbon-pickle-tcp = [2013, 2013, "tcp", "Carbon pickle"] + carbon-pickle-udp = [2013, 2013, "udp", "Carbon pickle"] + carbon-admin-tcp = [2004, 2004, "tcp", "Carbon admin"] + carbon-gui-udp = [8081, 8081, "tcp", "Carbon GUI"] + # Cassandra + cassandra-clients-tcp = [9042, 9042, "tcp", "Cassandra clients"] + cassandra-thrift-clients-tcp = [9160, 9160, "tcp", "Cassandra Thrift clients"] + cassandra-jmx-tcp = [7199, 7199, "tcp", "JMX"] + # Consul + consul-tcp = [8300, 8300, "tcp", "Consul server"] + consul-grpc-tcp = [8502, 8502, "tcp", "Consul gRPC"] + consul-webui-http-tcp = [8500, 8500, "tcp", "Consul web UI HTTP"] + consul-webui-https-tcp = [8501, 8501, "tcp", "Consul web UI HTTPS"] + consul-dns-tcp = [8600, 8600, "tcp", "Consul DNS"] + consul-dns-udp = [8600, 8600, "udp", "Consul DNS"] + consul-serf-lan-tcp = [8301, 8301, "tcp", "Serf LAN"] + consul-serf-lan-udp = [8301, 8301, "udp", "Serf LAN"] + consul-serf-wan-tcp = [8302, 8302, "tcp", "Serf WAN"] + consul-serf-wan-udp = [8302, 8302, "udp", "Serf WAN"] + # Docker Swarm + docker-swarm-mngmt-tcp = [2377, 2377, "tcp", "Docker Swarm cluster management"] + docker-swarm-node-tcp = [7946, 7946, "tcp", "Docker Swarm node"] + docker-swarm-node-udp = [7946, 7946, "udp", "Docker Swarm node"] + docker-swarm-overlay-udp = [4789, 4789, "udp", "Docker Swarm Overlay Network Traffic"] + # DNS + dns-udp = [53, 53, "udp", "DNS"] + dns-tcp = [53, 53, "tcp", "DNS"] + # Etcd + etcd-client-tcp = [2379, 2379, "tcp", "Etcd Client"] + etcd-peer-tcp = [2380, 2380, "tcp", "Etcd Peer"] + # NTP - Network Time Protocol + ntp-udp = [123, 123, "udp", "NTP"] + # Elasticsearch + elasticsearch-rest-tcp = [9200, 9200, "tcp", "Elasticsearch REST interface"] + elasticsearch-java-tcp = [9300, 9300, "tcp", "Elasticsearch Java interface"] + # Grafana + grafana-tcp = [3000, 3000, "tcp", "Grafana Dashboard"] + # Graphite Statsd + graphite-webui = [80, 80, "tcp", "Graphite admin interface"] + graphite-2003-tcp = [2003, 2003, "tcp", "Carbon receiver plain text"] + graphite-2004-tcp = [2004, 2004, "tcp", "Carbon receiver pickle"] + graphite-2023-tcp = [2023, 2023, "tcp", "Carbon aggregator plaintext"] + graphite-2024-tcp = [2024, 2024, "tcp", "Carbon aggregator pickle"] + graphite-8080-tcp = [8080, 8080, "tcp", "Graphite gunicorn port"] + graphite-8125-tcp = [8125, 8125, "tcp", "Statsd TCP"] + graphite-8125-udp = [8125, 8125, "udp", "Statsd UDP default"] + graphite-8126-tcp = [8126, 8126, "tcp", "Statsd admin"] + # HTTP + http-80-tcp = [80, 80, "tcp", "HTTP"] + http-8080-tcp = [8080, 8080, "tcp", "HTTP"] + # HTTPS + https-443-tcp = [443, 443, "tcp", "HTTPS"] + https-8443-tcp = [8443, 8443, "tcp", "HTTPS"] + # IPSEC + ipsec-500-udp = [500, 500, "udp", "IPSEC ISAKMP"] + ipsec-4500-udp = [4500, 4500, "udp", "IPSEC NAT-T"] + # Kafka + kafka-broker-tcp = [9092, 9092, "tcp", "Kafka PLAINTEXT enable broker 0.8.2+"] + kafka-broker-tls-tcp = [9094, 9094, "tcp", "Kafka TLS enabled broker 0.8.2+"] + kafka-broker-tls-public-tcp = [9194, 9194, "tcp", "Kafka TLS Public enabled broker 0.8.2+ (MSK specific)"] + kafka-broker-sasl-scram-tcp = [9096, 9096, "tcp", "Kafka SASL/SCRAM enabled broker (MSK specific)"] + kafka-broker-sasl-scram-public-tcp = [9196, 9196, "tcp", "Kafka SASL/SCRAM Public enabled broker (MSK specific)"] + kafka-broker-sasl-iam-tcp = [9098, 9098, "tcp", "Kafka SASL/IAM access control enabled (MSK specific)"] + kafka-broker-sasl-iam-public-tcp = [9198, 9198, "tcp", "Kafka SASL/IAM Public access control enabled (MSK specific)"] + kafka-jmx-exporter-tcp = [11001, 11001, "tcp", "Kafka JMX Exporter"] + kafka-node-exporter-tcp = [11002, 11002, "tcp", "Kafka Node Exporter"] + # Kibana + kibana-tcp = [5601, 5601, "tcp", "Kibana Web Interface"] + # Kubernetes + kubernetes-api-tcp = [6443, 6443, "tcp", "Kubernetes API Server"] + # LDAP + ldap-tcp = [389, 389, "tcp", "LDAP"] + # LDAPS + ldaps-tcp = [636, 636, "tcp", "LDAPS"] + # Logstash + logstash-tcp = [5044, 5044, "tcp", "Logstash"] + # Memcached + memcached-tcp = [11211, 11211, "tcp", "Memcached"] + # MinIO + minio-tcp = [9000, 9000, "tcp", "MinIO"] + # MongoDB + mongodb-27017-tcp = [27017, 27017, "tcp", "MongoDB"] + mongodb-27018-tcp = [27018, 27018, "tcp", "MongoDB shard"] + mongodb-27019-tcp = [27019, 27019, "tcp", "MongoDB config server"] + # MySQL + mysql-tcp = [3306, 3306, "tcp", "MySQL/Aurora"] + # MSSQL Server + mssql-tcp = [1433, 1433, "tcp", "MSSQL Server"] + mssql-udp = [1434, 1434, "udp", "MSSQL Browser"] + mssql-analytics-tcp = [2383, 2383, "tcp", "MSSQL Analytics"] + mssql-broker-tcp = [4022, 4022, "tcp", "MSSQL Broker"] + # NFS/EFS + nfs-tcp = [2049, 2049, "tcp", "NFS/EFS"] + # Nomad + nomad-http-tcp = [4646, 4646, "tcp", "Nomad HTTP"] + nomad-rpc-tcp = [4647, 4647, "tcp", "Nomad RPC"] + nomad-serf-tcp = [4648, 4648, "tcp", "Serf"] + nomad-serf-udp = [4648, 4648, "udp", "Serf"] + # OpenVPN + openvpn-udp = [1194, 1194, "udp", "OpenVPN"] + openvpn-tcp = [943, 943, "tcp", "OpenVPN"] + openvpn-https-tcp = [443, 443, "tcp", "OpenVPN"] + # PostgreSQL + postgresql-tcp = [5432, 5432, "tcp", "PostgreSQL"] + # Puppet + puppet-tcp = [8140, 8140, "tcp", "Puppet"] + puppetdb-tcp = [8081, 8081, "tcp", "PuppetDB"] + # Prometheus + prometheus-http-tcp = [9090, 9090, "tcp", "Prometheus"] + prometheus-pushgateway-http-tcp = [9091, 9091, "tcp", "Prometheus Pushgateway"] + # Oracle Database + oracle-db-tcp = [1521, 1521, "tcp", "Oracle"] + # Octopus Tentacles + octopus-tentacle-tcp = [10933, 10933, "tcp", "Octopus Tentacle"] + # RabbitMQ + rabbitmq-4369-tcp = [4369, 4369, "tcp", "RabbitMQ epmd"] + rabbitmq-5671-tcp = [5671, 5671, "tcp", "RabbitMQ"] + rabbitmq-5672-tcp = [5672, 5672, "tcp", "RabbitMQ"] + rabbitmq-15672-tcp = [15672, 15672, "tcp", "RabbitMQ"] + rabbitmq-25672-tcp = [25672, 25672, "tcp", "RabbitMQ"] + # RDP + rdp-tcp = [3389, 3389, "tcp", "Remote Desktop"] + rdp-udp = [3389, 3389, "udp", "Remote Desktop"] + # Redis + redis-tcp = [6379, 6379, "tcp", "Redis"] + # Redshift + redshift-tcp = [5439, 5439, "tcp", "Redshift"] + # SaltStack + saltstack-tcp = [4505, 4506, "tcp", "SaltStack"] + # SMTP + smtp-tcp = [25, 25, "tcp", "SMTP"] + smtp-submission-587-tcp = [587, 587, "tcp", "SMTP Submission"] + smtp-submission-2587-tcp = [2587, 2587, "tcp", "SMTP Submission"] + smtps-465-tcp = [465, 465, "tcp", "SMTPS"] + smtps-2456-tcp = [2465, 2465, "tcp", "SMTPS"] + # Solr + solr-tcp = [8983, 8987, "tcp", "Solr"] + # Splunk + splunk-indexer-tcp = [9997, 9997, "tcp", "Splunk indexer"] + splunk-web-tcp = [8000, 8000, "tcp", "Splunk Web"] + splunk-splunkd-tcp = [8089, 8089, "tcp", "Splunkd"] + splunk-hec-tcp = [8088, 8088, "tcp", "Splunk HEC"] + # Squid + squid-proxy-tcp = [3128, 3128, "tcp", "Squid default proxy"] + # SSH + ssh-tcp = [22, 22, "tcp", "SSH"] + # Storm + storm-nimbus-tcp = [6627, 6627, "tcp", "Nimbus"] + storm-ui-tcp = [8080, 8080, "tcp", "Storm UI"] + storm-supervisor-tcp = [6700, 6703, "tcp", "Supervisor"] + # Wazuh + wazuh-server-agent-connection-tcp = [1514, 1514, "tcp", "Agent connection service(TCP)"] + wazuh-server-agent-connection-udp = [1514, 1514, "udp", "Agent connection service(UDP)"] + wazuh-server-agent-enrollment = [1515, 1515, "tcp", "Agent enrollment service"] + wazuh-server-agent-cluster-daemon = [1516, 1516, "tcp", "Wazuh cluster daemon"] + wazuh-server-syslog-collector-tcp = [514, 514, "tcp", "Wazuh Syslog collector(TCP)"] + wazuh-server-syslog-collector-udp = [514, 514, "udp", "Wazuh Syslog collector(UDP)"] + wazuh-server-restful-api = [55000, 55000, "tcp", "Wazuh server RESTful API"] + wazuh-indexer-restful-api = [9200, 9200, "tcp", "Wazuh indexer RESTful API"] + wazuh-dashboard = [443, 443, "tcp", "Wazuh web user interface"] + # Web + web-jmx-tcp = [1099, 1099, "tcp", "JMX"] + # WinRM + winrm-http-tcp = [5985, 5985, "tcp", "WinRM HTTP"] + winrm-https-tcp = [5986, 5986, "tcp", "WinRM HTTPS"] + # Zabbix + zabbix-server = [10051, 10051, "tcp", "Zabbix Server"] + zabbix-proxy = [10051, 10051, "tcp", "Zabbix Proxy"] + zabbix-agent = [10050, 10050, "tcp", "Zabbix Agent"] + # Zipkin + zipkin-admin-tcp = [9990, 9990, "tcp", "Zipkin Admin port collector"] + zipkin-admin-query-tcp = [9901, 9901, "tcp", "Zipkin Admin port query"] + zipkin-admin-web-tcp = [9991, 9991, "tcp", "Zipkin Admin port web"] + zipkin-query-tcp = [9411, 9411, "tcp", "Zipkin query port"] + zipkin-web-tcp = [8080, 8080, "tcp", "Zipkin web port"] + # Zookeeper + zookeeper-2181-tcp = [2181, 2181, "tcp", "Zookeeper"] + zookeeper-2182-tls-tcp = [2182, 2182, "tcp", "Zookeeper TLS (MSK specific)"] + zookeeper-2888-tcp = [2888, 2888, "tcp", "Zookeeper"] + zookeeper-3888-tcp = [3888, 3888, "tcp", "Zookeeper"] + zookeeper-jmx-tcp = [7199, 7199, "tcp", "JMX"] + # Open all ports & protocols + all-all = [-1, -1, "-1", "All protocols"] + all-tcp = [0, 65535, "tcp", "All TCP ports"] + all-udp = [0, 65535, "udp", "All UDP ports"] + all-icmp = [-1, -1, "icmp", "All IPV4 ICMP"] + all-ipv6-icmp = [-1, -1, 58, "All IPV6 ICMP"] + # This is a fallback rule to pass to lookup() as default. It does not open anything, because it should never be used. + _ = ["", "", ""] + } +} + +variable "auto_groups" { + description = "Map of groups of security group rules to use to generate modules (see update_groups.sh)" + type = map(map(list(string))) + + # Valid keys - ingress_rules, egress_rules, ingress_with_self, egress_with_self + default = { + activemq = { + ingress_rules = ["activemq-5671-tcp", "activemq-8883-tcp", "activemq-61614-tcp", "activemq-61617-tcp", "activemq-61619-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + alertmanager = { + ingress_rules = ["alertmanager-9093-tcp", "alertmanager-9094-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + carbon-relay-ng = { + ingress_rules = ["carbon-line-in-tcp", "carbon-line-in-udp", "carbon-pickle-tcp", "carbon-pickle-udp", "carbon-gui-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + cassandra = { + ingress_rules = ["cassandra-clients-tcp", "cassandra-thrift-clients-tcp", "cassandra-jmx-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + consul = { + ingress_rules = ["consul-tcp", "consul-grpc-tcp", "consul-webui-http-tcp", "consul-webui-https-tcp", "consul-dns-tcp", "consul-dns-udp", "consul-serf-lan-tcp", "consul-serf-lan-udp", "consul-serf-wan-tcp", "consul-serf-wan-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + docker-swarm = { + ingress_rules = ["docker-swarm-mngmt-tcp", "docker-swarm-node-tcp", "docker-swarm-node-udp", "docker-swarm-overlay-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + etcd = { + ingress_rules = ["etcd-client-tcp", "etcd-peer-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + elasticsearch = { + ingress_rules = ["elasticsearch-rest-tcp", "elasticsearch-java-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + grafana = { + ingress_rules = ["grafana-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + graphite-statsd = { + ingress_rules = ["graphite-webui", "graphite-2003-tcp", "graphite-2004-tcp", "graphite-2023-tcp", "graphite-2024-tcp", "graphite-8080-tcp", "graphite-8125-tcp", "graphite-8125-udp", "graphite-8126-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + http-80 = { + ingress_rules = ["http-80-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + http-8080 = { + ingress_rules = ["http-8080-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + https-443 = { + ingress_rules = ["https-443-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + https-8443 = { + ingress_rules = ["https-8443-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ipsec-500 = { + ingress_rules = ["ipsec-500-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ipsec-4500 = { + ingress_rules = ["ipsec-4500-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + kafka = { + ingress_rules = ["kafka-broker-tcp", "kafka-broker-tls-tcp", "kafka-broker-tls-public-tcp", "kafka-broker-sasl-scram-tcp", "kafka-broker-sasl-scram-tcp", "kafka-broker-sasl-iam-tcp", "kafka-broker-sasl-iam-public-tcp", "kafka-jmx-exporter-tcp", "kafka-node-exporter-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + kubernetes-api = { + ingress_rules = ["kubernetes-api-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + kibana = { + ingress_rules = ["kibana-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ldap = { + ingress_rules = ["ldap-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ldaps = { + ingress_rules = ["ldaps-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + logstash = { + ingress_rules = ["logstash-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + memcached = { + ingress_rules = ["memcached-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + minio = { + ingress_rules = ["minio-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + mongodb = { + ingress_rules = ["mongodb-27017-tcp", "mongodb-27018-tcp", "mongodb-27019-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + mysql = { + ingress_rules = ["mysql-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + mssql = { + ingress_rules = ["mssql-tcp", "mssql-udp", "mssql-analytics-tcp", "mssql-broker-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + nfs = { + ingress_rules = ["nfs-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + nomad = { + ingress_rules = ["nomad-http-tcp", "nomad-rpc-tcp", "nomad-serf-tcp", "nomad-serf-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + openvpn = { + ingress_rules = ["openvpn-udp", "openvpn-tcp", "openvpn-https-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + postgresql = { + ingress_rules = ["postgresql-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + oracle-db = { + ingress_rules = ["oracle-db-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ntp = { + ingress_rules = ["ntp-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + puppet = { + ingress_rules = ["puppet-tcp", "puppetdb-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + prometheus = { + ingress_rules = ["prometheus-http-tcp", "prometheus-pushgateway-http-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + rabbitmq = { + ingress_rules = ["rabbitmq-4369-tcp", "rabbitmq-5671-tcp", "rabbitmq-5672-tcp", "rabbitmq-15672-tcp", "rabbitmq-25672-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + rdp = { + ingress_rules = ["rdp-tcp", "rdp-udp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + redis = { + ingress_rules = ["redis-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + redshift = { + ingress_rules = ["redshift-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + smtp = { + ingress_rules = ["smtp-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + smtp-submission = { + ingress_rules = ["smtp-submission-587-tcp", "smtp-submission-2587-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + smtps = { + ingress_rules = ["smtps-465-tcp", "smtps-2465-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + solr = { + ingress_rules = ["solr-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + splunk = { + ingress_rules = ["splunk-indexer-tcp", "splunk-clients-tcp", "splunk-splunkd-tcp", "splunk-hec-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + squid = { + ingress_rules = ["squid-proxy-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + ssh = { + ingress_rules = ["ssh-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + storm = { + ingress_rules = ["storm-nimbus-tcp", "storm-ui-tcp", "storm-supervisor-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + wazuh = { + ingress_rules = ["wazuh-server-agent-connection-tcp", "wazuh-server-agent-connection-udp", "wazuh-server-agent-enrollment", "wazuh-server-agent-cluster-daemon", "wazuh-server-syslog-collector-tcp", "wazuh-server-syslog-collector-udp", "wazuh-server-restful-api", "wazuh-indexer-restful-api", "wazuh-dashboard", ] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + web = { + ingress_rules = ["http-80-tcp", "http-8080-tcp", "https-443-tcp", "web-jmx-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + winrm = { + ingress_rules = ["winrm-http-tcp", "winrm-https-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + zabbix = { + ingress_rules = ["zabbix-server", "zabbix-proxy", "zabbix-agent"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + zipkin = { + ingress_rules = ["zipkin-admin-tcp", "zipkin-admin-query-tcp", "zipkin-admin-web-tcp", "zipkin-query-tcp", "zipkin-web-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + zookeeper = { + ingress_rules = ["zookeeper-2181-tcp", "zookeeper-2182-tls-tcp", "zookeeper-2888-tcp", "zookeeper-3888-tcp", "zookeeper-jmx-tcp"] + ingress_with_self = ["all-all"] + egress_rules = ["all-all"] + } + } +} diff --git a/modules/aws-security-group/update_groups.sh b/modules/aws-security-group/update_groups.sh new file mode 100644 index 0000000..2d65420 --- /dev/null +++ b/modules/aws-security-group/update_groups.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash + +# This script generates each public module (eg, "http-80", "ssh") and specify rules required for each group. +# This script should be run after rules.tf is changed to refresh all related modules. +# outputs.tf and variables.tf for all group modules are the same for all + +set -e + +# Change location to the directory where this script it located +cd "$(dirname "${BASH_SOURCE[0]}")" + +check_dependencies() { + if [[ ! $(command -v sed) ]]; then + echo "ERROR: The binary 'sed' is required by this script but is not installed or in the system's PATH." + echo "Check documentation: https://www.gnu.org/software/sed/" + exit 1 + fi + + if [[ ! $(command -v hcl2json) ]]; then + echo "ERROR: The binary 'hcl2json' is required by this script but is not installed or in the system's PATH." + echo "Check documentation: https://github.com/tmccombs/hcl2json" + exit 1 + fi + + if [[ ! $(command -v jq) ]]; then + echo "ERROR: The binary 'jq' is required by this script but is not installed or in the system's PATH." + echo "Check documentation: https://github.com/stedolan/jq" + exit 1 + fi +} + +auto_groups_data() { + hcl2json rules.tf | jq -r '..|.auto_groups?|values|.default' +} + +auto_groups_keys() { + local data=$1 + + echo "$data" | jq -r ".|keys|@sh" | tr -d "'" +} + +get_auto_value() { + local data=$1 + local group=$2 + local var=$3 + + echo "$data" | jq -rc '.[$group][$var]' --arg group "$group" --arg var "$var" +} + +set_list_if_null() { + if [[ "null" == "$1" ]]; then + echo "[]" + else + echo "$1" + fi +} + +set_zero_if_null() { + if [[ "null" == "$1" ]]; then + echo 0 + else + echo "$1" + fi +} + +main() { + check_dependencies + + readonly local auto_groups_data="$(auto_groups_data)" + + if [[ -z "$(auto_groups_data)" ]]; then + echo "There are no modules to update. Check values of auto_groups inside rules.tf" + exit 0 + fi + + readonly local auto_groups_keys=($(auto_groups_keys "$auto_groups_data")) + + local ingress_rules="" + local ingress_with_self="" + local egress_rules="" + local egress_with_self="" + local list_of_modules="" + + for group in "${auto_groups_keys[@]}"; do + + echo "Making group: $group" + + mkdir -p "modules/$group" + cp modules/_templates/{main,outputs,variables,versions}.tf "modules/$group" + + # Get group values + ingress_rules=$(get_auto_value "$auto_groups_data" "$group" "ingress_rules") + ingress_with_self=$(get_auto_value "$auto_groups_data" "$group" "ingress_with_self") + egress_rules=$(get_auto_value "$auto_groups_data" "$group" "egress_rules") + egress_with_self=$(get_auto_value "$auto_groups_data" "$group" "egress_with_self") + + # Computed values + computed_ingress_rules=$(get_auto_value "$auto_groups_data" "$group" "computed_ingress_rules") + computed_ingress_with_self=$(get_auto_value "$auto_groups_data" "$group" "computed_ingress_with_self") + computed_egress_rules=$(get_auto_value "$auto_groups_data" "$group" "computed_egress_rules") + computed_egress_with_self=$(get_auto_value "$auto_groups_data" "$group" "computed_egress_with_self") + + # Number of computed values + number_of_computed_ingress_rules=$(get_auto_value "$auto_groups_data" "$group" "number_of_computed_ingress_rules") + number_of_computed_ingress_with_self=$(get_auto_value "$auto_groups_data" "$group" "number_of_computed_ingress_with_self") + number_of_computed_egress_rules=$(get_auto_value "$auto_groups_data" "$group" "number_of_computed_egress_rules") + number_of_computed_egress_with_self=$(get_auto_value "$auto_groups_data" "$group" "number_of_computed_egress_with_self") + + # Set to empty lists, if no value was specified + ingress_rules=$(set_list_if_null "$ingress_rules") + ingress_with_self=$(set_list_if_null "$ingress_with_self") + egress_rules=$(set_list_if_null "$egress_rules") + egress_with_self=$(set_list_if_null "$egress_with_self") + + # Set to empty lists, if no computed value was specified + computed_ingress_rules=$(set_list_if_null "$computed_ingress_rules") + computed_ingress_with_self=$(set_list_if_null "$computed_ingress_with_self") + computed_egress_rules=$(set_list_if_null "$computed_egress_rules") + computed_egress_with_self=$(set_list_if_null "$computed_egress_with_self") + + # Set to zero, if no value was specified + number_of_computed_ingress_rules=$(set_zero_if_null "$number_of_computed_ingress_rules") + number_of_computed_ingress_with_self=$(set_zero_if_null "$number_of_computed_ingress_with_self") + number_of_computed_egress_rules=$(set_zero_if_null "$number_of_computed_egress_rules") + number_of_computed_egress_with_self=$(set_zero_if_null "$number_of_computed_egress_with_self") + + # ingress_with_self and egress_with_self are stored as simple lists (like this - ["all-all","all-tcp"]), + # so we make map (like this - [{"rule"="all-all"},{"rule"="all-tcp"}]) + ingress_with_self=$(echo "$ingress_with_self" | jq -rc "[{rule:.[]}]" | tr ':' '=') + egress_with_self=$(echo "$egress_with_self" | jq -rc "[{rule:.[]}]" | tr ':' '=') + + cat < "modules/$group/auto_values.tf" +# This file was generated from values defined in rules.tf using update_groups.sh. +################################### +# DO NOT CHANGE THIS FILE MANUALLY +################################### + +variable "auto_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = $ingress_rules +} + +variable "auto_ingress_with_self" { + description = "List of maps defining ingress rules with self to add automatically" + type = list(map(string)) + default = $ingress_with_self +} + +variable "auto_egress_rules" { + description = "List of egress rules to add automatically" + type = list(string) + default = $egress_rules +} + +variable "auto_egress_with_self" { + description = "List of maps defining egress rules with self to add automatically" + type = list(map(string)) + default = $egress_with_self +} + +# Computed +variable "auto_computed_ingress_rules" { + description = "List of ingress rules to add automatically" + type = list(string) + default = $computed_ingress_rules +} + +variable "auto_computed_ingress_with_self" { + description = "List of maps defining computed ingress rules with self to add automatically" + type = list(map(string)) + default = $computed_ingress_with_self +} + +variable "auto_computed_egress_rules" { + description = "List of computed egress rules to add automatically" + type = list(string) + default = $computed_egress_rules +} + +variable "auto_computed_egress_with_self" { + description = "List of maps defining computed egress rules with self to add automatically" + type = list(map(string)) + default = $computed_egress_with_self +} + +# Number of computed rules +variable "auto_number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = $number_of_computed_ingress_rules +} + +variable "auto_number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = $number_of_computed_ingress_with_self +} + +variable "auto_number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = $number_of_computed_egress_rules +} + +variable "auto_number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = $number_of_computed_egress_with_self +} +EOF + + cat < "modules/$group/README.md" +# $group - AWS EC2-VPC Security Group Terraform module + +## Usage + +\`\`\`hcl +module "${group/-/_}_security_group" { + source = "terraform-modules/security-group/aws//modules/${group}" + version = "~> 4.0" + + # omitted... +} +\`\`\` + +All automatic values **${group} module** is using are available [here](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/${group}/auto_values.tf). + + + +EOF + + list_of_modules=$(echo "$list_of_modules"; echo "* [$group](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/$group)") + + terraform fmt "modules/$group" + done + + + echo "Updating list of security group modules" + + cat < modules/README.md +List of Security Groups implemented as Terraform modules +======================================================== + +$list_of_modules +* [_templates](https://github.com/kloia/terraform-modules/terraform-aws-security-group/tree/main/modules/_templates) - Source templates for all other modules. Change carefully, test thoughtfully! + +EOF + + echo "Done!" +} + +main diff --git a/modules/aws-security-group/variables.tf b/modules/aws-security-group/variables.tf new file mode 100644 index 0000000..4e604d6 --- /dev/null +++ b/modules/aws-security-group/variables.tf @@ -0,0 +1,304 @@ +################# +# Security group +################# +variable "create" { + description = "Whether to create security group and all rules" + type = bool + default = true +} + +variable "create_sg" { + description = "Whether to create security group" + type = bool + default = true +} + +variable "security_group_id" { + description = "ID of existing security group whose rules we will manage" + type = string + default = null +} + +variable "vpc_id" { + description = "ID of the VPC where to create security group" + type = string + default = null +} + +variable "name" { + description = "Name of security group - not required if create_sg is false" + type = string + default = null +} + +variable "use_name_prefix" { + description = "Whether to use name_prefix or fixed name. Should be true to able to update security group name after initial creation" + type = bool + default = true +} + +variable "description" { + description = "Description of security group" + type = string + default = "Security Group managed by Terraform" +} + +variable "revoke_rules_on_delete" { + description = "Instruct Terraform to revoke all of the Security Groups attached ingress and egress rules before deleting the rule itself. Enable for EMR." + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to security group" + type = map(string) + default = {} +} + +variable "create_timeout" { + description = "Time to wait for a security group to be created" + type = string + default = "10m" +} + +variable "delete_timeout" { + description = "Time to wait for a security group to be deleted" + type = string + default = "15m" +} + +########## +# Ingress +########## +variable "ingress_rules" { + description = "List of ingress rules to create by name" + type = list(string) + default = [] +} + +variable "ingress_with_self" { + description = "List of ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "ingress_with_cidr_blocks" { + description = "List of ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_ipv6_cidr_blocks" { + description = "List of ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_with_source_security_group_id" { + description = "List of ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "ingress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all ingress rules" + type = list(string) + default = [] +} + +variable "ingress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all ingress rules" + type = list(string) + default = [] +} + +################### +# Computed Ingress +################### +variable "computed_ingress_rules" { + description = "List of computed ingress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_ingress_with_self" { + description = "List of computed ingress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_cidr_blocks" { + description = "List of computed ingress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_ipv6_cidr_blocks" { + description = "List of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_ingress_with_source_security_group_id" { + description = "List of computed ingress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +################################### +# Number of computed ingress rules +################################### +variable "number_of_computed_ingress_rules" { + description = "Number of computed ingress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_self" { + description = "Number of computed ingress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_cidr_blocks" { + description = "Number of computed ingress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_ipv6_cidr_blocks" { + description = "Number of computed ingress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_ingress_with_source_security_group_id" { + description = "Number of computed ingress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + +######### +# Egress +######### +variable "egress_rules" { + description = "List of egress rules to create by name" + type = list(string) + default = [] +} + +variable "egress_with_self" { + description = "List of egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "egress_with_cidr_blocks" { + description = "List of egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_ipv6_cidr_blocks" { + description = "List of egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "egress_with_source_security_group_id" { + description = "List of egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of IPv4 CIDR ranges to use on all egress rules" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "egress_ipv6_cidr_blocks" { + description = "List of IPv6 CIDR ranges to use on all egress rules" + type = list(string) + default = ["::/0"] +} + +variable "egress_prefix_list_ids" { + description = "List of prefix list IDs (for allowing access to VPC endpoints) to use on all egress rules" + type = list(string) + default = [] +} + +################## +# Computed Egress +################## +variable "computed_egress_rules" { + description = "List of computed egress rules to create by name" + type = list(string) + default = [] +} + +variable "computed_egress_with_self" { + description = "List of computed egress rules to create where 'self' is defined" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_cidr_blocks" { + description = "List of computed egress rules to create where 'cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_ipv6_cidr_blocks" { + description = "List of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = list(map(string)) + default = [] +} + +variable "computed_egress_with_source_security_group_id" { + description = "List of computed egress rules to create where 'source_security_group_id' is used" + type = list(map(string)) + default = [] +} + +################################## +# Number of computed egress rules +################################## +variable "number_of_computed_egress_rules" { + description = "Number of computed egress rules to create by name" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_self" { + description = "Number of computed egress rules to create where 'self' is defined" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_cidr_blocks" { + description = "Number of computed egress rules to create where 'cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_ipv6_cidr_blocks" { + description = "Number of computed egress rules to create where 'ipv6_cidr_blocks' is used" + type = number + default = 0 +} + +variable "number_of_computed_egress_with_source_security_group_id" { + description = "Number of computed egress rules to create where 'source_security_group_id' is used" + type = number + default = 0 +} + + diff --git a/modules/aws-security-group/versions.tf b/modules/aws-security-group/versions.tf new file mode 100644 index 0000000..088a433 --- /dev/null +++ b/modules/aws-security-group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29" + } + } +} diff --git a/modules/aws-security-hub/admin_account.tf b/modules/aws-security-hub/admin_account.tf new file mode 100644 index 0000000..3306b73 --- /dev/null +++ b/modules/aws-security-hub/admin_account.tf @@ -0,0 +1,22 @@ +################################################## +# Security Hub Delegated Admin +################################################## +resource "aws_securityhub_organization_admin_account" "this" { + provider = aws.security_account + count = var.admin_account_id == null ? 0 : 1 + admin_account_id = var.admin_account_id +} + +resource "aws_organizations_delegated_administrator" "this" { + provider = aws.organization + account_id = var.admin_account_id + service_principal = "securityhub.amazonaws.com" +} + +resource "aws_securityhub_organization_configuration" "this" { + provider = aws.organization + auto_enable = false + auto_enable_standards = var.auto_enable_standards + + depends_on = [aws_securityhub_organization_admin_account.this] +} \ No newline at end of file diff --git a/modules/aws-security-hub/main.tf b/modules/aws-security-hub/main.tf new file mode 100644 index 0000000..6cc902e --- /dev/null +++ b/modules/aws-security-hub/main.tf @@ -0,0 +1,90 @@ +data "aws_region" "current" {} + +locals { + standards_subscription = { + aws_foundational_security_best_practices = var.standards_config != null ? merge(var.standards_config.aws_foundational_security_best_practices, + { + arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/aws-foundational-security-best-practices/v/1.0.0" + }) : null + cis_aws_foundations_benchmark_v120 = var.standards_config != null ? merge(var.standards_config.cis_aws_foundations_benchmark_v120, + { + arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" + }) : null + cis_aws_foundations_benchmark_v140 = var.standards_config != null ? merge(var.standards_config.cis_aws_foundations_benchmark_v140, + { + arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/cis-aws-foundations-benchmark/v/1.4.0" + }) : null + nist_sp_800_53_rev5 = var.standards_config != null ? merge(var.standards_config.nist_sp_800_53_rev5, + { + arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/nist-800-53/v/5.0.0" + }) : null + pci_dss = var.standards_config != null ? merge(var.standards_config.pci_dss, + { + arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/pci-dss/v/3.2.1" + }) : null + } +} + +################################################## +# Security Hub +################################################## +resource "aws_securityhub_account" "this" { + provider = aws.security_account + enable_default_standards = var.enable_default_standards + control_finding_generator = var.control_finding_generator + auto_enable_controls = var.auto_enable_controls +} + +resource "time_sleep" "wait_securityhub_enable" { + + create_duration = "10s" + + depends_on = [aws_securityhub_account.this] +} + +resource "aws_securityhub_finding_aggregator" "this" { + linking_mode = var.linking_mode + specified_regions = var.specified_regions + + provider = aws.security_account + + depends_on = [time_sleep.wait_securityhub_enable] +} + +################################################## +# Security Hub Subscriptions +################################################## +resource "aws_securityhub_product_subscription" "this" { + for_each = var.product_config != null ? { for product in var.product_config : product.arn => product } : {} + + provider = aws.security_account + + product_arn = each.value.arn + + depends_on = [time_sleep.wait_securityhub_enable] +} + +resource "aws_securityhub_standards_subscription" "this" { + for_each = var.standards_config != null ? { for standards in local.standards_subscription : standards.arn => standards if standards.enable } : {} + + provider = aws.security_account + + standards_arn = each.value.arn + + depends_on = [time_sleep.wait_securityhub_enable] +} + +################################################## +# Security Hub Action Targets +################################################## +resource "aws_securityhub_action_target" "this" { + for_each = var.action_target != null ? { for target in var.action_target : target.identifier => target } : {} + + provider = aws.security_account + + name = each.value.name + identifier = each.value.identifier + description = each.value.description + + depends_on = [time_sleep.wait_securityhub_enable] +} diff --git a/modules/aws-security-hub/member_account.tf b/modules/aws-security-hub/member_account.tf new file mode 100644 index 0000000..9262cbc --- /dev/null +++ b/modules/aws-security-hub/member_account.tf @@ -0,0 +1,25 @@ +################################################## +# Security Hub Account Member +################################################## +resource "aws_securityhub_member" "this" { + for_each = var.member_config != null ? { for member in var.member_config : member.account_id => member } : {} + + account_id = each.value.account_id + email = each.value.email + invite = each.value.invite +} + +resource "aws_securityhub_account" "member" { + for_each = var.member_config != null ? { for member in var.member_config : member.account_id => member } : {} +} + +################################################## +# Security Hub Invite +################################################## +resource "aws_securityhub_invite_accepter" "member" { + for_each = var.member_config != null ? { for member in var.member_config : member.account_id => member if member.invite } : {} + + master_id = aws_securityhub_member.this[each.key].master_id + + depends_on = [aws_securityhub_account.member] +} \ No newline at end of file diff --git a/modules/aws-security-hub/outputs.tf b/modules/aws-security-hub/outputs.tf new file mode 100644 index 0000000..526ccc0 --- /dev/null +++ b/modules/aws-security-hub/outputs.tf @@ -0,0 +1,67 @@ +################################################## +# Security Hub +################################################## +output "securityhub_account" { + description = "Security Hub AWS account configuration." + value = aws_securityhub_account.this +} + +output "finding_aggregator" { + description = "Security Hub finding aggregator configuration." + value = aws_securityhub_finding_aggregator.this +} + +################################################## +# Security Hub Subscriptions +################################################## +output "product_subscription" { + description = "Security Hub products subscriptions." + value = aws_securityhub_product_subscription.this +} + +output "standards_subscription" { + description = "Security Hub compliance standards subscriptions." + value = aws_securityhub_standards_subscription.this +} + +################################################## +# Security Hub Action Targets +################################################## +output "action_target" { + description = "Security Hub custome action targets." + value = aws_securityhub_action_target.this +} + +################################################## +# Security Hub Delegated Admin +################################################## +output "securityhub_delegated_admin_account" { + description = "AWS Security Hub Delegated Admin account." + value = aws_securityhub_organization_admin_account.this +} + +# output "securityhub_organization_configuration" { +# description = "AWS Security Hub Organizations configuration." +# value = aws_securityhub_organization_configuration.this +# } + +################################################## +# Security Hub Account Member +################################################## +output "securityhub_member" { + description = "AWS Security Hub member configuration." + value = aws_securityhub_member.this +} + +output "securityhub_member_account" { + description = "AWS Security Hub member account configuration." + value = aws_securityhub_account.member +} + +################################################## +# Security Hub Organizations Invite +################################################## +output "securityhub_member_invite" { + description = "AWS Security Hub organizations invite." + value = aws_securityhub_invite_accepter.member +} \ No newline at end of file diff --git a/modules/aws-security-hub/variables.tf b/modules/aws-security-hub/variables.tf new file mode 100644 index 0000000..6ea26f3 --- /dev/null +++ b/modules/aws-security-hub/variables.tf @@ -0,0 +1,151 @@ +################################################## +# Security Hub +################################################## +variable "enable_default_standards" { + description = "Whether to enable the security standards that Security Hub has designated as automatically enabled including: AWS Foundational Security Best Practices v1.0.0 and CIS AWS Foundations Benchmark v1.2.0. Defaults to `true`." + type = bool + default = true +} + +variable "control_finding_generator" { + description = "Updates whether the calling account has consolidated control findings turned on. If the value for this field is set to SECURITY_CONTROL, Security Hub generates a single finding for a control check even when the check applies to multiple enabled standards. If the value for this field is set to STANDARD_CONTROL, Security Hub generates separate findings for a control check when the check applies to multiple enabled standards. For accounts that are part of an organization, this value can only be updated in the administrator account." + type = string + default = "STANDARD_CONTROL" +} + +variable "linking_mode" { + description = "Indicates whether to aggregate findings from all of the available Regions or from a specified list. The options are ALL_REGIONS, ALL_REGIONS_EXCEPT_SPECIFIED or SPECIFIED_REGIONS. When ALL_REGIONS or ALL_REGIONS_EXCEPT_SPECIFIED are used, Security Hub will automatically aggregate findings from new Regions as Security Hub supports them and you opt into them." + type = string + default = "ALL_REGIONS" +} + +variable "auto_enable_controls" { + description = "Whether to automatically enable new controls when they are added to standards that are enabled. By default, this is set to true, and new controls are enabled automatically. To not automatically enable new controls, set this to false." + type = bool + default = true +} + +variable "specified_regions" { + description = "List of regions to include or exclude (required if linking_mode is set to ALL_REGIONS_EXCEPT_SPECIFIED or SPECIFIED_REGIONS)" + type = list(string) + default = null +} + +################################################## +# Security Hub Subscriptions +################################################## +variable "standards_config" { + description = < 0 ? var.certificate_backend_kms_key_id : null + ssm_enabled = local.certificate_backends_enabled && contains(var.certificate_backends, "SSM") + acm_enabled = local.certificate_backends_enabled && contains(var.certificate_backends, "ACM") + asm_enabled = local.certificate_backends_enabled && contains(var.certificate_backends, "ASM") + tls_certificate = try(tls_self_signed_cert.default[0].cert_pem, tls_locally_signed_cert.default[0].cert_pem, null) + tls_key = try(tls_private_key.default[0].private_key_pem, var.private_key_contents) +} + +resource "tls_private_key" "default" { + count = local.create_private_key ? 1 : 0 + + algorithm = var.private_key_algorithm + ecdsa_curve = var.private_key_algorithm == "ECDSA" ? var.private_key_ecdsa_curve : null + rsa_bits = var.private_key_algorithm == "RSA" ? var.private_key_rsa_bits : null +} + +resource "tls_cert_request" "default" { + count = var.use_locally_signed ? 1 : 0 + + private_key_pem = coalesce(join("", tls_private_key.default.*.private_key_pem), var.private_key_contents) + + subject { + common_name = lookup(var.subject, "common_name", null) + organization = lookup(var.subject, "organization", null) + organizational_unit = lookup(var.subject, "organizational_unit", null) + street_address = lookup(var.subject, "street_address", null) + locality = lookup(var.subject, "locality", null) + province = lookup(var.subject, "province", null) + country = lookup(var.subject, "country", null) + postal_code = lookup(var.subject, "postal_code", null) + serial_number = lookup(var.subject, "serial_number", null) + } +} + +resource "tls_locally_signed_cert" "default" { + count = var.use_locally_signed ? 1 : 0 + + is_ca_certificate = var.basic_constraints.ca + + cert_request_pem = join("", tls_cert_request.default.*.cert_request_pem) + ca_private_key_pem = var.certificate_chain.private_key_pem + ca_cert_pem = var.certificate_chain.cert_pem + + validity_period_hours = var.validity.duration_hours + early_renewal_hours = var.validity.early_renewal_hours + + allowed_uses = var.allowed_uses + set_subject_key_id = var.skid_enabled +} + +resource "tls_self_signed_cert" "default" { + count = !var.use_locally_signed ? 1 : 0 + + is_ca_certificate = var.basic_constraints.ca + + private_key_pem = coalesce(join("", tls_private_key.default.*.private_key_pem), var.private_key_contents) + + validity_period_hours = var.validity.duration_hours + early_renewal_hours = var.validity.early_renewal_hours + + allowed_uses = var.allowed_uses + + subject { + common_name = lookup(var.subject, "common_name", null) + organization = lookup(var.subject, "organization", null) + organizational_unit = lookup(var.subject, "organizational_unit", null) + street_address = lookup(var.subject, "street_address", null) + locality = lookup(var.subject, "locality", null) + province = lookup(var.subject, "province", null) + country = lookup(var.subject, "country", null) + postal_code = lookup(var.subject, "postal_code", null) + serial_number = lookup(var.subject, "serial_number", null) + } + + dns_names = var.subject_alt_names.dns_names + ip_addresses = var.subject_alt_names.ip_addresses + uris = var.subject_alt_names.uris + + set_subject_key_id = var.skid_enabled +} diff --git a/modules/aws-self-signed-cert/outputs.tf b/modules/aws-self-signed-cert/outputs.tf new file mode 100644 index 0000000..5f9206d --- /dev/null +++ b/modules/aws-self-signed-cert/outputs.tf @@ -0,0 +1,25 @@ +output "certificate_key_path" { + description = "Secrets store path containing the certificate private key file." + value = local.asm_enabled || local.ssm_enabled ? coalesce(join("", aws_ssm_parameter.private_key.*.name), join("", aws_secretsmanager_secret.private_key.*.name)) : null +} + +output "certificate_pem_path" { + description = "Secrets store path containing the certificate PEM file." + value = local.asm_enabled || local.ssm_enabled ? coalesce(join("", aws_ssm_parameter.certificate.*.name), join("", aws_secretsmanager_secret.certificate.*.name)) : null +} + +output "certificate_pem" { + description = "Contents of the certificate PEM." + value = local.tls_certificate +} + +output "certificate_key" { + description = "Contents of the certificate PEM." + value = local.tls_key + sensitive = true +} + +output "certificate_arn" { + description = "ARN of certificate stored in ACM that other services may need to refer to. This is useful when the certificate is stored in ACM." + value = join("", aws_acm_certificate.default.*.arn) +} diff --git a/modules/aws-self-signed-cert/ssm.tf b/modules/aws-self-signed-cert/ssm.tf new file mode 100644 index 0000000..3e0d53b --- /dev/null +++ b/modules/aws-self-signed-cert/ssm.tf @@ -0,0 +1,17 @@ +resource "aws_ssm_parameter" "certificate" { + count = local.ssm_enabled ? 1 : 0 + + name = format(var.secret_path_format, lookup(var.subject, "common_name", null), var.secret_extensions.certificate) + type = "SecureString" + key_id = local.certificate_backend_kms_key_id + value = var.certificate_backends_base64_enabled ? base64encode(local.tls_certificate) : local.tls_certificate +} + +resource "aws_ssm_parameter" "private_key" { + count = local.ssm_enabled ? 1 : 0 + + name = format(var.secret_path_format, lookup(var.subject, "common_name", null), var.secret_extensions.private_key) + type = "SecureString" + key_id = local.certificate_backend_kms_key_id + value = var.certificate_backends_base64_enabled ? base64encode(local.tls_key) : local.tls_key +} diff --git a/modules/aws-self-signed-cert/variables.tf b/modules/aws-self-signed-cert/variables.tf new file mode 100644 index 0000000..ea71ea4 --- /dev/null +++ b/modules/aws-self-signed-cert/variables.tf @@ -0,0 +1,240 @@ +variable "allowed_uses" { + description = <<-EOT + List of keywords each describing a use that is permitted for the issued certificate. + Must be one of of the values outlined in [self_signed_cert.allowed_uses](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert#allowed_uses). + EOT + type = list(string) +} + +variable "basic_constraints" { + description = <<-EOT + The [basic constraints](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9) of the issued certificate. + Currently, only the `CA` constraint (which identifies whether the subject of the certificate is a CA) can be set. + + Defaults to this certificate not being a CA. + EOT + type = object({ + ca = bool + }) + default = { + ca = false + } +} + +variable "private_key_algorithm" { + description = <<-EOT + The name of the algorithm for the private key of the certificate. Currently only RSA and ECDSA are supported. + + If a preexisting private key is supplied via `var.private_key_contents`, this value must match that key's algorithm. + + Defaults to RSA as it is a more widely adopted algorithm, although ECDSA provides the same level of security and with shorter keys. + EOT + type = string + default = "RSA" + validation { + condition = contains(["RSA", "ECDSA"], var.private_key_algorithm) + error_message = "Algorithm must be one of: RSA, ECDSA." + } +} + +variable "private_key_contents" { + description = <<-EOT + The contents of the private key to use for the certificate. + If supplied, this module will not create a private key and use these contents instead for the private key. + + Defaults to `null`, which means a private key will be created. + EOT + type = string + default = null +} + +variable "private_key_rsa_bits" { + description = <<-EOT + When `var.cert_key_algorithm` is `RSA`, the size of the generated RSA key in bits. + + Ignored if `var.cert_key_algorithm` is not `RSA`, or if a preexisting private key is supplied via `var.private_key_contents`. + + Defaults to the `tls` provider default. + EOT + type = number + default = 2048 +} + +variable "private_key_ecdsa_curve" { + description = <<-EOT + When `var.cert_key_algorithm` is `ECDSA`, the name of the elliptic curve to use. May be any one of `P224`, `P256`, `P384` or `P521`. + + Ignored if `var.cert_key_algorithm` is not `ECDSA`, or if a preexisting private key is supplied via `var.private_key_contents`. + + Defaults to the `tls` provider default. + EOT + type = string + default = "P224" +} + +variable "validity" { + description = <<-EOT + Validity settings for the issued certificate: + + `duration_hours`: The number of hours from issuing the certificate until it becomes invalid. + `early_renewal_hours`: If set, the resource will consider the certificate to have expired the given number of hours before its actual expiry time (see: [self_signed_cert.early_renewal_hours](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert#early_renewal_hours)). + + Defaults to 10 years and no early renewal hours. + EOT + type = object({ + duration_hours = number + early_renewal_hours = number + }) + default = { + duration_hours = 87600 + early_renewal_hours = null + } +} + +variable "skid_enabled" { + description = "Whether or not the subject key identifier (SKID) should be included in the certificate." + type = bool + default = false +} + +variable "subject" { + description = <<-EOT + The subject configuration for the certificate. + This should be a map that is compatible with [tls_cert_request.subject](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/cert_request#subject). + + If `common_name` is omitted, it will be set as `module.this.id`. + EOT + type = any + default = {} +} + +variable "subject_alt_names" { + description = <<-EOT + The subject alternative name (SAN) configuration for the certificate. This configuration consists of several lists, each of which can also be set to `null` or `[]`. + + `dns_names`: List of DNS names for which a certificate is being requested. + `ip_addresses`: List of IP addresses for which a certificate is being requested. + `uris`: List of URIs for which a certificate is being requested. + + Defaults to no SANs. + EOT + type = object({ + dns_names = list(string) + ip_addresses = list(string) + uris = list(string) + }) + default = { + dns_names = null + ip_addresses = null + uris = null + } +} + +// Certificate Backend Variables + +variable "asm_recovery_window_in_days" { + description = <<-EOT + Number of days that AWS Secrets Manager waits before it can delete the secret. This value can be `0` to force deletion without recovery or range from `7` to `30` days. + + This value is ignored if `var.certificate_backends` is not `ASM`, or if `var.certificate_backend_enabled` is `false`. + EOT + type = number + default = 30 +} + +variable "secret_extensions" { + description = <<-EOT + The extensions use when writing secrets to the certificate backend. + + Please refer to `var.secret_path_format` for information on how secret paths are computed. + EOT + type = object({ + certificate = string + private_key = string + }) + default = { + certificate = "pem" + private_key = "key" + } +} + +variable "secret_path_format" { + description = <<-EOT + The path format to use when writing secrets to the certificate backend. + + The certificate secret path will be computed as `format(var.secret_path_format, var.name, var.secret_extensions.certificate)` + and the private key path as `format(var.secret_path_format, var.name, var.secret_extensions.private_key)`. + + Thus by default, if `var.name`=`example-self-signed-cert`, then the resulting secret paths for the self-signed certificate's + PEM file and private key will be `/example-self-signed-cert.pem` and `/example-self-signed-cert.key`, respectively. + + This variable can be overridden in order to create more specific certificate backend paths. + EOT + type = string + default = "/%s.%s" + + validation { + condition = can(substr(var.secret_path_format, 0, 1) == "/") + error_message = "The secret path format must contain a leading slash." + } +} + +variable "certificate_backends_base64_enabled" { + description = "Enable or disable base64 encoding of secrets before writing them to the secrets store." + type = bool + default = false +} + +variable "certificate_backends_enabled" { + description = "Enable or disable writing to the secrets store." + type = bool + default = true +} + +variable "certificate_backend_kms_key_id" { + description = <<-EOT + The KMD Key ID (ARN or ID) to use when encrypting either the AWS SSM Parameters or AWS Secrets Manager Secrets relating to the certificate. + + If not specified, the Amazon-managed Key `alias/aws/ssm` will be used if `var.certificate_backends` contains `SSM`, + and `alias/aws/secretsmanager` will be used if `var.certificate_backends` is `ASM`. + EOT + type = string + default = null +} + +variable "certificate_backends" { + description = <<-EOT + The certificate backend to use when writing secrets related to the self-signed certificate. + The value specified can either be `SSM` (AWS Systems Manager Parameter Store), `ASM` (AWS Secrets Manager), + and/or `ACM` (AWS Certificate Manager). + + Defaults to `SSM`. + EOT + type = set(string) + default = ["SSM"] + validation { + condition = length(setintersection(["SSM", "ASM", "ACM"], var.certificate_backends)) > 0 + error_message = "Certificate backend must be one be one of: SSM, ASM, ACM." + } +} + +variable "certificate_chain" { + description = <<-EOT + When using ACM as a certificate backend, some certificates store a certificate chain from a CA. This CA will come from another resource. + EOT + + type = object({ + cert_pem = string + private_key_pem = string + }) + default = null +} + +variable "use_locally_signed" { + description = <<-EOT + Create a locally signed certificate/key pair instead of a self-signed one. This is useful it a previously created certificate chain is to be used to sign a certificate. + EOT + + type = bool + default = false +} diff --git a/modules/aws-self-signed-cert/versions.tf b/modules/aws-self-signed-cert/versions.tf new file mode 100644 index 0000000..2785126 --- /dev/null +++ b/modules/aws-self-signed-cert/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + tls = { + source = "hashicorp/tls" + version = ">= 4.0.0" + } + } +} diff --git a/modules/aws-shield/examples/main.tf b/modules/aws-shield/examples/main.tf new file mode 100644 index 0000000..d5cb808 --- /dev/null +++ b/modules/aws-shield/examples/main.tf @@ -0,0 +1,54 @@ +provider "aws" { + default_tags { + tags = { + name = "example" + environment = "dev" + terraform = "true" + } + } +} + +module "group_all" { + source = "../group_all" + group_id = "example" + aggregation = "SUM" +} + +module "group_arbitrary" { + source = "../group_arbitrary" + name = "example" + aggregation = "SUM" + members = ["arn:aws:ec2:eu-west-1:2131241241241:eip-allocation/"] +} + +module "resource_type_protection_group" { + source = "../group_resource_type" + resource_type_protection_group = [ + { + group_id = "cloudfront_distributions" + aggregation = "SUM" + members = ["arn:aws:ec2:eu-west-1:2131241241241:eip-allocation/"] + resource_type = "CLOUDFRONT_DISTRIBUTION" + }, + { + group_id = "route53_hosted_zones" + aggregation = "SUM" + members = ["arn:aws:ec2:eu-west-1:2131241241241:eip-allocation/"] + resource_type = "ROUTE_53_HOSTED_ZONE" + } + ] +} + +module "not_group" { + source = "../" + name_resource_arn_map = { + cloudfront_x = { + arn = "xyz" + cross_account_shield = false + }, + route53_y = { + arn = "xyz" + cross_account_shield = true + } + } +} \ No newline at end of file diff --git a/modules/aws-shield/group_all/README.md b/modules/aws-shield/group_all/README.md new file mode 100644 index 0000000..801ef9c --- /dev/null +++ b/modules/aws-shield/group_all/README.md @@ -0,0 +1,36 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14 | +| [aws](#requirement\_aws) | >= 4.22 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.22 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_shield_protection_group.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/shield_protection_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aggregation](#input\_aggregation) | Defines how AWS Shield combines resource data for the group in order to detect, mitigate, and report events. | `string` | `"SUM"` | no | +| [group\_id](#input\_group\_id) | The name of the protection group. | `string` | `"aws_shield_all"` | no | +| [tags](#input\_tags) | Key-value map of resource tags. If configured with a provider default\_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level. | `map(string)` | `{}` | no | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/modules/aws-shield/group_all/locals.tf b/modules/aws-shield/group_all/locals.tf new file mode 100644 index 0000000..f74b546 --- /dev/null +++ b/modules/aws-shield/group_all/locals.tf @@ -0,0 +1,3 @@ +locals { + tags = var.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_all/main.tf b/modules/aws-shield/group_all/main.tf new file mode 100644 index 0000000..c0f50fd --- /dev/null +++ b/modules/aws-shield/group_all/main.tf @@ -0,0 +1,7 @@ +resource "aws_shield_protection_group" "all" { + protection_group_id = var.group_id + aggregation = var.aggregation + pattern = "ALL" + + tags = local.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_all/provider.tf b/modules/aws-shield/group_all/provider.tf new file mode 100644 index 0000000..c142005 --- /dev/null +++ b/modules/aws-shield/group_all/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.22" + } + } +} \ No newline at end of file diff --git a/modules/aws-shield/group_all/variables.tf b/modules/aws-shield/group_all/variables.tf new file mode 100644 index 0000000..127ae09 --- /dev/null +++ b/modules/aws-shield/group_all/variables.tf @@ -0,0 +1,25 @@ +variable "tags" { + type = map(string) + description = "Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + default = {} +} + +variable "group_id" { + type = string + description = "The name of the protection group." + default = "aws_shield_all" +} + +variable "aggregation" { + type = string + description = "Defines how AWS Shield combines resource data for the group in order to detect, mitigate, and report events." + default = "SUM" + validation { + condition = contains([ + "SUM", + "MEAN", + "MAX", + ], var.aggregation) + error_message = "Valid values are limited to (SUM,MEAN,MAX)." + } +} \ No newline at end of file diff --git a/modules/aws-shield/group_arbitrary/README.md b/modules/aws-shield/group_arbitrary/README.md new file mode 100644 index 0000000..c470efb --- /dev/null +++ b/modules/aws-shield/group_arbitrary/README.md @@ -0,0 +1,37 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14 | +| [aws](#requirement\_aws) | >= 4.22 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.22 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_shield_protection_group.arbitrary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/shield_protection_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aggregation](#input\_aggregation) | Defines how AWS Shield combines resource data for the group in order to detect, mitigate, and report events. | `string` | `"SUM"` | no | +| [group\_id](#input\_group\_id) | The name of the protection group. | `string` | `"aws_shield_all"` | no | +| [members](#input\_members) | The Amazon Resource Names (ARNs) of the resources to include in the protection group. You must set this when you set pattern to ARBITRARY and you must not set it for any other pattern setting. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Key-value map of resource tags. If configured with a provider default\_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level. | `map(string)` | `{}` | no | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/modules/aws-shield/group_arbitrary/locals.tf b/modules/aws-shield/group_arbitrary/locals.tf new file mode 100644 index 0000000..f74b546 --- /dev/null +++ b/modules/aws-shield/group_arbitrary/locals.tf @@ -0,0 +1,3 @@ +locals { + tags = var.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_arbitrary/main.tf b/modules/aws-shield/group_arbitrary/main.tf new file mode 100644 index 0000000..565ac59 --- /dev/null +++ b/modules/aws-shield/group_arbitrary/main.tf @@ -0,0 +1,8 @@ +resource "aws_shield_protection_group" "arbitrary" { + protection_group_id = var.group_id + aggregation = var.aggregation + pattern = "ARBITRARY" + members = var.members + + tags = local.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_arbitrary/provider.tf b/modules/aws-shield/group_arbitrary/provider.tf new file mode 100644 index 0000000..c142005 --- /dev/null +++ b/modules/aws-shield/group_arbitrary/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.22" + } + } +} \ No newline at end of file diff --git a/modules/aws-shield/group_arbitrary/variables.tf b/modules/aws-shield/group_arbitrary/variables.tf new file mode 100644 index 0000000..799faad --- /dev/null +++ b/modules/aws-shield/group_arbitrary/variables.tf @@ -0,0 +1,30 @@ +variable "tags" { + type = map(string) + description = "Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + default = {} +} + +variable "group_id" { + type = string + description = "The name of the protection group." + default = "aws_shield_all" +} + +variable "aggregation" { + type = string + description = "Defines how AWS Shield combines resource data for the group in order to detect, mitigate, and report events." + default = "SUM" + validation { + condition = contains([ + "SUM", + "MEAN", + "MAX", + ], var.aggregation) + error_message = "Valid values are limited to (SUM,MEAN,MAX)." + } +} + +variable "members" { + type = list(string) + description = "The Amazon Resource Names (ARNs) of the resources to include in the protection group. You must set this when you set pattern to ARBITRARY and you must not set it for any other pattern setting." +} \ No newline at end of file diff --git a/modules/aws-shield/group_resource_type/README.md b/modules/aws-shield/group_resource_type/README.md new file mode 100644 index 0000000..9768882 --- /dev/null +++ b/modules/aws-shield/group_resource_type/README.md @@ -0,0 +1,35 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14 | +| [aws](#requirement\_aws) | >= 4.22 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.22 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_shield_protection_group.resource_type_protection_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/shield_protection_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [resource\_type\_protection\_group](#input\_resource\_type\_protection\_group) | all shield protection group values list as resource type |
list(object({
group_id = string
aggregation = string
members = list(string)
resource_type = string
}))
| n/a | yes | +| [tags](#input\_tags) | Key-value map of resource tags. If configured with a provider default\_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level. | `map(string)` | `{}` | no | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/modules/aws-shield/group_resource_type/locals.tf b/modules/aws-shield/group_resource_type/locals.tf new file mode 100644 index 0000000..f74b546 --- /dev/null +++ b/modules/aws-shield/group_resource_type/locals.tf @@ -0,0 +1,3 @@ +locals { + tags = var.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_resource_type/main.tf b/modules/aws-shield/group_resource_type/main.tf new file mode 100644 index 0000000..cfbefe6 --- /dev/null +++ b/modules/aws-shield/group_resource_type/main.tf @@ -0,0 +1,9 @@ +resource "aws_shield_protection_group" "resource_type_protection_group" { + count = length(var.resource_type_protection_group) + protection_group_id = var.resource_type_protection_group.group_id + aggregation = var.resource_type_protection_group.aggregation + pattern = "BY_RESOURCE_TYPE" + resource_type = var.resource_type_protection_group.resource_type + + tags = local.tags +} \ No newline at end of file diff --git a/modules/aws-shield/group_resource_type/provider.tf b/modules/aws-shield/group_resource_type/provider.tf new file mode 100644 index 0000000..c142005 --- /dev/null +++ b/modules/aws-shield/group_resource_type/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.22" + } + } +} \ No newline at end of file diff --git a/modules/aws-shield/group_resource_type/variables.tf b/modules/aws-shield/group_resource_type/variables.tf new file mode 100644 index 0000000..cfb708a --- /dev/null +++ b/modules/aws-shield/group_resource_type/variables.tf @@ -0,0 +1,26 @@ +variable "tags" { + type = map(string) + description = "Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + default = {} +} + +variable "resource_type_protection_group" { + type = list(object({ + group_id = string + aggregation = string + members = list(string) + resource_type = string + })) + validation { + condition = contains([ + "CLOUDFRONT_DISTRIBUTION", + "ROUTE_53_HOSTED_ZONE", + "GLOBAL_ACCELERATOR", + "APPLICATION_LOAD_BALANCER", + "CLASSIC_LOAD_BALANCER", + "ELASTIC_IP_ALLOCATION", + ], var.resource_type) + error_message = "Valid values are limited in validation section." + } + description = "all shield protection group values list as resource type" +} diff --git a/modules/aws-shield/main.tf b/modules/aws-shield/main.tf new file mode 100644 index 0000000..e9bdf15 --- /dev/null +++ b/modules/aws-shield/main.tf @@ -0,0 +1,15 @@ +resource "aws_shield_protection" "shield" { + for_each = { for k, v in var.name_resource_arn_map : k => v if !lookup(v, "cross_account_shield", false) } + name = each.key + resource_arn = lookup(each.value, "arn", null) + tags = var.tags +} + +resource "aws_shield_protection" "cross_account_shield" { + provider = aws.shared_infra + for_each = { for k, v in var.name_resource_arn_map : k => v if lookup(v, "cross_account_shield", false) } + name = each.key + resource_arn = lookup(each.value, "arn", null) + + tags = var.tags +} \ No newline at end of file diff --git a/modules/aws-shield/outputs.tf b/modules/aws-shield/outputs.tf new file mode 100644 index 0000000..2cc5303 --- /dev/null +++ b/modules/aws-shield/outputs.tf @@ -0,0 +1,9 @@ +output "shield" { + value = { for key, value in aws_shield_protection.shield : key => value } + description = "A map of properties for the created AWS Shield protection." +} + +output "cross_account_shield" { + value = { for key, value in aws_shield_protection.cross_account_shield : key => value } + description = "A map of properties for the created AWS Shield protection." +} \ No newline at end of file diff --git a/modules/aws-shield/variables.tf b/modules/aws-shield/variables.tf new file mode 100644 index 0000000..dc43314 --- /dev/null +++ b/modules/aws-shield/variables.tf @@ -0,0 +1,11 @@ +variable "name_resource_arn_map" { + type = any + description = "A map of names and ARNs of resources to be protected. The name will be used as the name of the resource in the AWS console." + default = {} +} + +variable "tags" { + type = map(string) + description = "A map of tag names and values for tags to apply to all taggable resources created by the module. Default value is a blank map to allow for using Default Tags in the provider." + default = {} +} \ No newline at end of file diff --git a/modules/aws-shield/versions.tf b/modules/aws-shield/versions.tf new file mode 100644 index 0000000..c142005 --- /dev/null +++ b/modules/aws-shield/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.22" + } + } +} \ No newline at end of file diff --git a/modules/aws-sns-master/README.md b/modules/aws-sns-master/README.md new file mode 100644 index 0000000..d216e3d --- /dev/null +++ b/modules/aws-sns-master/README.md @@ -0,0 +1,90 @@ +# AWS SNS Topic Terraform module + +Terraform module which creates SNS resources on AWS + +## Usage + +```hcl +module "sns_topic" { + source = "terraform-aws-modules/sns/aws" + version = "~> 3.0" + + name = "my-topic" +} +``` + +## Examples + +- [Complete SNS topics](https://github.com/terraform-aws-modules/terraform-aws-sns/tree/master/examples/complete) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_sns_topic.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [application\_failure\_feedback\_role\_arn](#input\_application\_failure\_feedback\_role\_arn) | IAM role for failure feedback | `string` | `null` | no | +| [application\_success\_feedback\_role\_arn](#input\_application\_success\_feedback\_role\_arn) | The IAM role permitted to receive success feedback for this topic | `string` | `null` | no | +| [application\_success\_feedback\_sample\_rate](#input\_application\_success\_feedback\_sample\_rate) | Percentage of success to sample | `string` | `null` | no | +| [content\_based\_deduplication](#input\_content\_based\_deduplication) | Boolean indicating whether or not to enable content-based deduplication for FIFO topics. | `bool` | `false` | no | +| [create\_sns\_topic](#input\_create\_sns\_topic) | Whether to create the SNS topic | `bool` | `true` | no | +| [delivery\_policy](#input\_delivery\_policy) | The SNS delivery policy | `string` | `null` | no | +| [display\_name](#input\_display\_name) | The display name for the SNS topic | `string` | `null` | no | +| [fifo\_topic](#input\_fifo\_topic) | Boolean indicating whether or not to create a FIFO (first-in-first-out) topic | `bool` | `false` | no | +| [firehose\_failure\_feedback\_role\_arn](#input\_firehose\_failure\_feedback\_role\_arn) | IAM role for failure feedback | `string` | `null` | no | +| [firehose\_success\_feedback\_role\_arn](#input\_firehose\_success\_feedback\_role\_arn) | The IAM role permitted to receive success feedback for this topic | `string` | `null` | no | +| [firehose\_success\_feedback\_sample\_rate](#input\_firehose\_success\_feedback\_sample\_rate) | Percentage of success to sample | `number` | `null` | no | +| [http\_failure\_feedback\_role\_arn](#input\_http\_failure\_feedback\_role\_arn) | IAM role for failure feedback | `string` | `null` | no | +| [http\_success\_feedback\_role\_arn](#input\_http\_success\_feedback\_role\_arn) | The IAM role permitted to receive success feedback for this topic | `string` | `null` | no | +| [http\_success\_feedback\_sample\_rate](#input\_http\_success\_feedback\_sample\_rate) | Percentage of success to sample | `string` | `null` | no | +| [kms\_master\_key\_id](#input\_kms\_master\_key\_id) | The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK | `string` | `null` | no | +| [lambda\_failure\_feedback\_role\_arn](#input\_lambda\_failure\_feedback\_role\_arn) | IAM role for failure feedback | `string` | `null` | no | +| [lambda\_success\_feedback\_role\_arn](#input\_lambda\_success\_feedback\_role\_arn) | The IAM role permitted to receive success feedback for this topic | `string` | `null` | no | +| [lambda\_success\_feedback\_sample\_rate](#input\_lambda\_success\_feedback\_sample\_rate) | Percentage of success to sample | `string` | `null` | no | +| [name](#input\_name) | The name of the SNS topic to create | `string` | `null` | no | +| [name\_prefix](#input\_name\_prefix) | The prefix name of the SNS topic to create | `string` | `null` | no | +| [policy](#input\_policy) | The fully-formed AWS policy as JSON | `string` | `null` | no | +| [sqs\_failure\_feedback\_role\_arn](#input\_sqs\_failure\_feedback\_role\_arn) | IAM role for failure feedback | `string` | `null` | no | +| [sqs\_success\_feedback\_role\_arn](#input\_sqs\_success\_feedback\_role\_arn) | The IAM role permitted to receive success feedback for this topic | `string` | `null` | no | +| [sqs\_success\_feedback\_sample\_rate](#input\_sqs\_success\_feedback\_sample\_rate) | Percentage of success to sample | `string` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [sns\_topic\_arn](#output\_sns\_topic\_arn) | ARN of SNS topic | +| [sns\_topic\_id](#output\_sns\_topic\_id) | ID of SNS topic | +| [sns\_topic\_name](#output\_sns\_topic\_name) | NAME of SNS topic | +| [sns\_topic\_owner](#output\_sns\_topic\_owner) | OWNER of SNS topic | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-sns/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-sns/tree/master/LICENSE) for full details. diff --git a/modules/aws-sns-master/examples/complete/README.md b/modules/aws-sns-master/examples/complete/README.md new file mode 100644 index 0000000..b696131 --- /dev/null +++ b/modules/aws-sns-master/examples/complete/README.md @@ -0,0 +1,54 @@ +# Complete SNS topic example + +Configuration in this directory creates SNS topics. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [users\_encrypted](#module\_users\_encrypted) | ../../ | n/a | +| [users\_unencrypted](#module\_users\_unencrypted) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [users\_encrypted\_sns\_topic\_arn](#output\_users\_encrypted\_sns\_topic\_arn) | The ARN of the SNS topic | +| [users\_unencrypted\_sns\_topic\_arn](#output\_users\_unencrypted\_sns\_topic\_arn) | The ARN of the SNS topic | + diff --git a/modules/aws-sns-master/examples/complete/main.tf b/modules/aws-sns-master/examples/complete/main.tf new file mode 100644 index 0000000..c418d1f --- /dev/null +++ b/modules/aws-sns-master/examples/complete/main.tf @@ -0,0 +1,27 @@ +provider "aws" { + region = "eu-west-1" +} + +resource "aws_kms_key" "this" {} + +module "users_unencrypted" { + source = "../../" + + name = "users-unencrypted" + + tags = { + Secure = "false" + } +} + +module "users_encrypted" { + source = "../../" + + name_prefix = "users-encrypted-" + display_name = "users-encrypted" + kms_master_key_id = aws_kms_key.this.id + + tags = { + Secure = "true" + } +} diff --git a/modules/aws-sns-master/examples/complete/outputs.tf b/modules/aws-sns-master/examples/complete/outputs.tf new file mode 100644 index 0000000..2568e14 --- /dev/null +++ b/modules/aws-sns-master/examples/complete/outputs.tf @@ -0,0 +1,9 @@ +output "users_unencrypted_sns_topic_arn" { + description = "The ARN of the SNS topic" + value = module.users_unencrypted.sns_topic_arn +} + +output "users_encrypted_sns_topic_arn" { + description = "The ARN of the SNS topic" + value = module.users_encrypted.sns_topic_arn +} diff --git a/modules/aws-sns-master/examples/complete/variables.tf b/modules/aws-sns-master/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-sns-master/examples/complete/versions.tf b/modules/aws-sns-master/examples/complete/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-sns-master/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-sns-master/main.tf b/modules/aws-sns-master/main.tf new file mode 100644 index 0000000..ae17a9a --- /dev/null +++ b/modules/aws-sns-master/main.tf @@ -0,0 +1,30 @@ +resource "aws_sns_topic" "this" { + count = var.create_sns_topic ? 1 : 0 + + name = var.name + name_prefix = var.name_prefix + + display_name = var.display_name + policy = var.policy + delivery_policy = var.delivery_policy + application_success_feedback_role_arn = var.application_success_feedback_role_arn + application_success_feedback_sample_rate = var.application_success_feedback_sample_rate + application_failure_feedback_role_arn = var.application_failure_feedback_role_arn + firehose_success_feedback_role_arn = var.firehose_success_feedback_role_arn + firehose_success_feedback_sample_rate = var.firehose_success_feedback_sample_rate + firehose_failure_feedback_role_arn = var.firehose_failure_feedback_role_arn + http_success_feedback_role_arn = var.http_success_feedback_role_arn + http_success_feedback_sample_rate = var.http_success_feedback_sample_rate + http_failure_feedback_role_arn = var.http_failure_feedback_role_arn + lambda_success_feedback_role_arn = var.lambda_success_feedback_role_arn + lambda_success_feedback_sample_rate = var.lambda_success_feedback_sample_rate + lambda_failure_feedback_role_arn = var.lambda_failure_feedback_role_arn + sqs_success_feedback_role_arn = var.sqs_success_feedback_role_arn + sqs_success_feedback_sample_rate = var.sqs_success_feedback_sample_rate + sqs_failure_feedback_role_arn = var.sqs_failure_feedback_role_arn + kms_master_key_id = var.kms_master_key_id + fifo_topic = var.fifo_topic + content_based_deduplication = var.content_based_deduplication + + tags = var.tags +} diff --git a/modules/aws-sns-master/outputs.tf b/modules/aws-sns-master/outputs.tf new file mode 100644 index 0000000..9c582f5 --- /dev/null +++ b/modules/aws-sns-master/outputs.tf @@ -0,0 +1,19 @@ +output "sns_topic_arn" { + description = "ARN of SNS topic" + value = try(aws_sns_topic.this[0].arn, "") +} + +output "sns_topic_name" { + description = "NAME of SNS topic" + value = try(aws_sns_topic.this[0].name, "") +} + +output "sns_topic_id" { + description = "ID of SNS topic" + value = try(aws_sns_topic.this[0].id, "") +} + +output "sns_topic_owner" { + description = "OWNER of SNS topic" + value = try(aws_sns_topic.this[0].owner, "") +} diff --git a/modules/aws-sns-master/variables.tf b/modules/aws-sns-master/variables.tf new file mode 100644 index 0000000..b3885a2 --- /dev/null +++ b/modules/aws-sns-master/variables.tf @@ -0,0 +1,149 @@ +variable "create_sns_topic" { + description = "Whether to create the SNS topic" + type = bool + default = true +} + +variable "name" { + description = "The name of the SNS topic to create" + type = string + default = null +} + +variable "name_prefix" { + description = "The prefix name of the SNS topic to create" + type = string + default = null +} + +variable "display_name" { + description = "The display name for the SNS topic" + type = string + default = null +} + +variable "policy" { + description = "The fully-formed AWS policy as JSON" + type = string + default = null +} + +variable "delivery_policy" { + description = "The SNS delivery policy" + type = string + default = null +} + +variable "application_success_feedback_role_arn" { + description = "The IAM role permitted to receive success feedback for this topic" + type = string + default = null +} + +variable "application_success_feedback_sample_rate" { + description = "Percentage of success to sample" + type = string + default = null +} + +variable "application_failure_feedback_role_arn" { + description = "IAM role for failure feedback" + type = string + default = null +} + +variable "firehose_success_feedback_role_arn" { + description = "The IAM role permitted to receive success feedback for this topic" + type = string + default = null +} + +variable "firehose_success_feedback_sample_rate" { + description = "Percentage of success to sample" + type = number + default = null +} + +variable "firehose_failure_feedback_role_arn" { + description = "IAM role for failure feedback" + type = string + default = null +} + +variable "http_success_feedback_role_arn" { + description = "The IAM role permitted to receive success feedback for this topic" + type = string + default = null +} + +variable "http_success_feedback_sample_rate" { + description = "Percentage of success to sample" + type = string + default = null +} + +variable "http_failure_feedback_role_arn" { + description = "IAM role for failure feedback" + type = string + default = null +} + +variable "lambda_success_feedback_role_arn" { + description = "The IAM role permitted to receive success feedback for this topic" + type = string + default = null +} + +variable "lambda_success_feedback_sample_rate" { + description = "Percentage of success to sample" + type = string + default = null +} + +variable "lambda_failure_feedback_role_arn" { + description = "IAM role for failure feedback" + type = string + default = null +} + +variable "sqs_success_feedback_role_arn" { + description = "The IAM role permitted to receive success feedback for this topic" + type = string + default = null +} + +variable "sqs_success_feedback_sample_rate" { + description = "Percentage of success to sample" + type = string + default = null +} + +variable "sqs_failure_feedback_role_arn" { + description = "IAM role for failure feedback" + type = string + default = null +} + +variable "kms_master_key_id" { + description = "The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK" + type = string + default = null +} + +variable "fifo_topic" { + description = "Boolean indicating whether or not to create a FIFO (first-in-first-out) topic" + type = bool + default = false +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = {} +} + +variable "content_based_deduplication" { + description = "Boolean indicating whether or not to enable content-based deduplication for FIFO topics." + type = bool + default = false +} diff --git a/modules/aws-sns-master/versions.tf b/modules/aws-sns-master/versions.tf new file mode 100644 index 0000000..d8dd1a4 --- /dev/null +++ b/modules/aws-sns-master/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/aws-sns/README.md b/modules/aws-sns/README.md new file mode 100644 index 0000000..416b6c5 --- /dev/null +++ b/modules/aws-sns/README.md @@ -0,0 +1,124 @@ +# AWS SNS Topic Terraform module + +Terraform module which creates SNS resources on AWS +## Usage + +### Simple Topic + +```hcl +module "sns_topic" { + source = "git::https://github.com/kloia/platform-modules//aws-sns?ref=main" + + name = "simple" + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### Topic w/ SQS Subscription + +```hcl +module "sns_topic" { + source = "git::https://github.com/kloia/platform-modules//aws-sns?ref=main" + + name = "pub-sub" + + topic_policy_statements = { + pub = { + actions = ["sns:Publish"] + principals = [{ + type = "AWS" + identifiers = ["arn:aws:iam::66666666666:role/publisher"] + }] + }, + + sub = { + actions = [ + "sns:Subscribe", + "sns:Receive", + ] + + principals = [{ + type = "AWS" + identifiers = ["*"] + }] + + conditions = [{ + test = "StringLike" + variable = "sns:Endpoint" + values = ["arn:aws:sqs:eu-west-1:11111111111:subscriber"] + }] + } + } + + subscriptions = { + sqs = { + protocol = "sqs" + endpoint = "arn:aws:sqs:eu-west-1:11111111111:subscriber" + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### FIFO Topic w/ FIFO SQS Subscription + +```hcl +module "sns_topic" { + source = "git::https://github.com/kloia/platform-modules//aws-sns?ref=main" + + name = "my-topic" + + # SQS queue must be FIFO as well + fifo_topic = true + content_based_deduplication = true + + topic_policy_statements = { + pub = { + actions = ["sns:Publish"] + principals = [{ + type = "AWS" + identifiers = ["arn:aws:iam::66666666666:role/publisher"] + }] + }, + + sub = { + actions = [ + "sns:Subscribe", + "sns:Receive", + ] + + principals = [{ + type = "AWS" + identifiers = ["*"] + }] + + conditions = [{ + test = "StringLike" + variable = "sns:Endpoint" + values = ["arn:aws:sqs:eu-west-1:11111111111:subscriber.fifo"] + }] + } + } + + subscriptions = { + sqs = { + protocol = "sqs" + endpoint = "arn:aws:sqs:eu-west-1:11111111111:subscriber.fifo" + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + diff --git a/modules/aws-sns/examples/complete/README.md b/modules/aws-sns/examples/complete/README.md new file mode 100644 index 0000000..712bdc1 --- /dev/null +++ b/modules/aws-sns/examples/complete/README.md @@ -0,0 +1,71 @@ +# Complete SNS topic example + +Configuration in this directory creates: +- A simple, default SNS topic +- A FIFO SNS topic with FIFO SQS subscription; shows most of the supported arguments +- A disabled SNS topic + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.25 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.25 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [complete\_sns](#module\_complete\_sns) | ../../ | n/a | +| [default\_sns](#module\_default\_sns) | ../../ | n/a | +| [disabled\_sns](#module\_disabled\_sns) | ../../ | n/a | +| [kms](#module\_kms) | https://github.com/kloia/platform-modules/tree/main/aws-kms | ~> 1.0 | +| [sqs](#module\_sqs) | https://github.com/kloia/platform-modules/tree/main/terraform-aws-sqs | ~> 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [complete\_sns\_subscriptions](#output\_complete\_sns\_subscriptions) | Map of subscriptions created and their attributes | +| [complete\_sns\_topic\_arn](#output\_complete\_sns\_topic\_arn) | The ARN of the SNS topic, as a more obvious property (clone of id) | +| [complete\_sns\_topic\_beginning\_archive\_time](#output\_complete\_sns\_topic\_beginning\_archive\_time) | The oldest timestamp at which a FIFO topic subscriber can start a replay | +| [complete\_sns\_topic\_id](#output\_complete\_sns\_topic\_id) | The ARN of the SNS topic | +| [complete\_sns\_topic\_name](#output\_complete\_sns\_topic\_name) | The name of the topic | +| [complete\_sns\_topic\_owner](#output\_complete\_sns\_topic\_owner) | The AWS Account ID of the SNS topic owner | +| [default\_sns\_subscriptions](#output\_default\_sns\_subscriptions) | Map of subscriptions created and their attributes | +| [default\_sns\_topic\_arn](#output\_default\_sns\_topic\_arn) | The ARN of the SNS topic, as a more obvious property (clone of id) | +| [default\_sns\_topic\_beginning\_archive\_time](#output\_default\_sns\_topic\_beginning\_archive\_time) | The oldest timestamp at which a FIFO topic subscriber can start a replay | +| [default\_sns\_topic\_id](#output\_default\_sns\_topic\_id) | The ARN of the SNS topic | +| [default\_sns\_topic\_name](#output\_default\_sns\_topic\_name) | The name of the topic | +| [default\_sns\_topic\_owner](#output\_default\_sns\_topic\_owner) | The AWS Account ID of the SNS topic owner | + diff --git a/modules/aws-sns/examples/complete/main.tf b/modules/aws-sns/examples/complete/main.tf new file mode 100644 index 0000000..b024b2d --- /dev/null +++ b/modules/aws-sns/examples/complete/main.tf @@ -0,0 +1,273 @@ +provider "aws" { + region = local.region +} + +data "aws_caller_identity" "current" {} + +locals { + region = "eu-west-1" + name = "sns-ex-${basename(path.cwd)}" + + tags = { + Name = local.name + Example = "complete" + Repository = "github.com/terraform-aws-modules/terraform-aws-sns" + } +} + +################################################################################ +# SNS Module +################################################################################ + +module "default_sns" { + source = "../../" + + name = "${local.name}-default" + signature_version = 2 + + data_protection_policy = jsonencode( + { + Description = "Deny Inbound Address" + Name = "DenyInboundEmailAdressPolicy" + Statement = [ + { + "DataDirection" = "Inbound" + "DataIdentifier" = [ + "arn:aws:dataprotection::aws:data-identifier/EmailAddress", + ] + "Operation" = { + "Deny" = {} + } + "Principal" = [ + "*", + ] + "Sid" = "DenyInboundEmailAddress" + }, + ] + Version = "2021-06-01" + } + ) + + tags = local.tags +} + +module "complete_sns" { + source = "../../" + + name = local.name + use_name_prefix = true + display_name = "complete" + kms_master_key_id = module.kms.key_id + tracing_config = "Active" + + # SQS queue must be FIFO as well + fifo_topic = true + content_based_deduplication = true + + delivery_policy = jsonencode({ + "http" : { + "defaultHealthyRetryPolicy" : { + "minDelayTarget" : 20, + "maxDelayTarget" : 20, + "numRetries" : 3, + "numMaxDelayRetries" : 0, + "numNoDelayRetries" : 0, + "numMinDelayRetries" : 0, + "backoffFunction" : "linear" + }, + "disableSubscriptionOverrides" : false, + "defaultThrottlePolicy" : { + "maxReceivesPerSecond" : 1 + } + } + }) + + # # Example config for archive_policy for SNS FIFO message archiving + # # You can not delete a topic with an active message archive policy + # # You must first deactivate the topic before it can be deleted + # # https://docs.aws.amazon.com/sns/latest/dg/message-archiving-and-replay-topic-owner.html + # archive_policy = jsonencode({ + # "MessageRetentionPeriod": 30 + # }) + + create_topic_policy = true + enable_default_topic_policy = true + topic_policy_statements = { + pub = { + actions = ["sns:Publish"] + principals = [{ + type = "AWS" + identifiers = [data.aws_caller_identity.current.arn] + }] + }, + + sub = { + actions = [ + "sns:Subscribe", + "sns:Receive", + ] + + principals = [{ + type = "AWS" + identifiers = ["*"] + }] + + conditions = [{ + test = "StringLike" + variable = "sns:Endpoint" + values = [module.sqs.queue_arn] + }] + } + } + + subscriptions = { + sqs = { + protocol = "sqs" + endpoint = module.sqs.queue_arn + + # # example of replay_policy for SNS FIFO message replay + # # https://docs.aws.amazon.com/sns/latest/dg/message-archiving-and-replay-subscriber.html + # replay_policy = jsonencode({ + # "PointType": "Timestamp" + # "StartingPoint": timestamp() + # }) + } + } + + # Feedback + application_feedback = { + failure_role_arn = aws_iam_role.this.arn + success_role_arn = aws_iam_role.this.arn + success_sample_rate = 100 + } + firehose_feedback = { + failure_role_arn = aws_iam_role.this.arn + success_role_arn = aws_iam_role.this.arn + success_sample_rate = 100 + } + http_feedback = { + failure_role_arn = aws_iam_role.this.arn + success_role_arn = aws_iam_role.this.arn + success_sample_rate = 100 + } + lambda_feedback = { + failure_role_arn = aws_iam_role.this.arn + success_role_arn = aws_iam_role.this.arn + success_sample_rate = 100 + } + sqs_feedback = { + failure_role_arn = aws_iam_role.this.arn + success_role_arn = aws_iam_role.this.arn + success_sample_rate = 100 + } + + tags = local.tags +} + +module "disabled_sns" { + source = "../../" + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "kms" { + source = "terraform-aws-modules/kms/aws" + version = "~> 1.0" + + aliases = ["sns/${local.name}"] + description = "KMS key to encrypt topic" + + # Policy + key_statements = [ + { + sid = "SNS" + actions = [ + "kms:GenerateDataKey*", + "kms:Decrypt" + ] + resources = ["*"] + principals = [{ + type = "Service" + identifiers = ["sns.amazonaws.com"] + }] + } + ] + + tags = local.tags +} + +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + version = "~> 4.0" + + name = local.name + fifo_queue = true + + create_queue_policy = true + queue_policy_statements = { + sns = { + sid = "SNS" + actions = ["sqs:SendMessage"] + + principals = [ + { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + ] + + condition = { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [module.complete_sns.topic_arn] + } + } + } + + tags = local.tags +} + +resource "aws_iam_role" "this" { + name = local.name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "SnsAssume" + Principal = { + Service = "sns.amazonaws.com" + } + }, + ] + }) + + inline_policy { + name = local.name + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutMetricFilter", + "logs:PutRetentionPolicy", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) + } + + tags = local.tags +} diff --git a/modules/aws-sns/examples/complete/outputs.tf b/modules/aws-sns/examples/complete/outputs.tf new file mode 100644 index 0000000..c086335 --- /dev/null +++ b/modules/aws-sns/examples/complete/outputs.tf @@ -0,0 +1,67 @@ +################################################################################ +# Default +################################################################################ + +output "default_sns_topic_arn" { + description = "The ARN of the SNS topic, as a more obvious property (clone of id)" + value = module.default_sns.topic_arn +} + +output "default_sns_topic_id" { + description = "The ARN of the SNS topic" + value = module.default_sns.topic_id +} + +output "default_sns_topic_name" { + description = "The name of the topic" + value = module.default_sns.topic_name +} + +output "default_sns_topic_owner" { + description = "The AWS Account ID of the SNS topic owner" + value = module.default_sns.topic_owner +} + +output "default_sns_topic_beginning_archive_time" { + description = "The oldest timestamp at which a FIFO topic subscriber can start a replay" + value = module.default_sns.topic_beginning_archive_time +} + +output "default_sns_subscriptions" { + description = "Map of subscriptions created and their attributes" + value = module.default_sns.subscriptions +} + +################################################################################ +# Complete +################################################################################ + +output "complete_sns_topic_arn" { + description = "The ARN of the SNS topic, as a more obvious property (clone of id)" + value = module.complete_sns.topic_arn +} + +output "complete_sns_topic_id" { + description = "The ARN of the SNS topic" + value = module.complete_sns.topic_id +} + +output "complete_sns_topic_name" { + description = "The name of the topic" + value = module.complete_sns.topic_name +} + +output "complete_sns_topic_owner" { + description = "The AWS Account ID of the SNS topic owner" + value = module.complete_sns.topic_owner +} + +output "complete_sns_topic_beginning_archive_time" { + description = "The oldest timestamp at which a FIFO topic subscriber can start a replay" + value = module.complete_sns.topic_beginning_archive_time +} + +output "complete_sns_subscriptions" { + description = "Map of subscriptions created and their attributes" + value = module.complete_sns.subscriptions +} diff --git a/modules/aws-sns/examples/complete/variables.tf b/modules/aws-sns/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-sns/examples/complete/versions.tf b/modules/aws-sns/examples/complete/versions.tf new file mode 100644 index 0000000..2c1a62c --- /dev/null +++ b/modules/aws-sns/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.25" + } + } +} diff --git a/modules/aws-sns/main.tf b/modules/aws-sns/main.tf new file mode 100644 index 0000000..0b0fe6c --- /dev/null +++ b/modules/aws-sns/main.tf @@ -0,0 +1,170 @@ +data "aws_caller_identity" "current" {} + +################################################################################ +# Topic +################################################################################ + +resource "aws_sns_topic" "this" { + count = var.create ? 1 : 0 + + name = var.use_name_prefix ? null : var.name + name_prefix = var.use_name_prefix ? var.name : null + + application_failure_feedback_role_arn = try(var.application_feedback.failure_role_arn, null) + application_success_feedback_role_arn = try(var.application_feedback.success_role_arn, null) + application_success_feedback_sample_rate = try(var.application_feedback.success_sample_rate, null) + + content_based_deduplication = var.content_based_deduplication + delivery_policy = var.delivery_policy + display_name = var.display_name + fifo_topic = var.fifo_topic + signature_version = var.fifo_topic ? null : var.signature_version + tracing_config = var.tracing_config + + firehose_failure_feedback_role_arn = try(var.firehose_feedback.failure_role_arn, null) + firehose_success_feedback_role_arn = try(var.firehose_feedback.success_role_arn, null) + firehose_success_feedback_sample_rate = try(var.firehose_feedback.success_sample_rate, null) + + http_failure_feedback_role_arn = try(var.http_feedback.failure_role_arn, null) + http_success_feedback_role_arn = try(var.http_feedback.success_role_arn, null) + http_success_feedback_sample_rate = try(var.http_feedback.success_sample_rate, null) + + kms_master_key_id = var.kms_master_key_id + + lambda_failure_feedback_role_arn = try(var.lambda_feedback.failure_role_arn, null) + lambda_success_feedback_role_arn = try(var.lambda_feedback.success_role_arn, null) + lambda_success_feedback_sample_rate = try(var.lambda_feedback.success_sample_rate, null) + + policy = var.create_topic_policy ? null : var.topic_policy + + sqs_failure_feedback_role_arn = try(var.sqs_feedback.failure_role_arn, null) + sqs_success_feedback_role_arn = try(var.sqs_feedback.success_role_arn, null) + sqs_success_feedback_sample_rate = try(var.sqs_feedback.success_sample_rate, null) + + archive_policy = try(var.archive_policy, null) + + tags = var.tags +} + +################################################################################ +# Topic Policy +################################################################################ + +data "aws_iam_policy_document" "this" { + count = var.create && var.create_topic_policy ? 1 : 0 + + source_policy_documents = var.source_topic_policy_documents + override_policy_documents = var.override_topic_policy_documents + + dynamic "statement" { + for_each = var.enable_default_topic_policy ? [1] : [] + + content { + sid = "__default_statement_ID" + actions = [ + "sns:Subscribe", + "sns:SetTopicAttributes", + "sns:RemovePermission", + "sns:Publish", + "sns:ListSubscriptionsByTopic", + "sns:GetTopicAttributes", + "sns:DeleteTopic", + "sns:AddPermission", + ] + effect = "Allow" + resources = [aws_sns_topic.this[0].arn] + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "StringEquals" + values = [data.aws_caller_identity.current.account_id] + variable = "AWS:SourceOwner" + } + } + } + + dynamic "statement" { + for_each = var.topic_policy_statements + + content { + sid = try(statement.value.sid, statement.key) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + # This avoids the chicken vs the egg scenario since its embedded and can reference the topic + resources = try(statement.value.resources, [aws_sns_topic.this[0].arn]) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_sns_topic_policy" "this" { + count = var.create && var.create_topic_policy ? 1 : 0 + + arn = aws_sns_topic.this[0].arn + policy = data.aws_iam_policy_document.this[0].json +} + +################################################################################ +# Subscription(s) +################################################################################ + +resource "aws_sns_topic_subscription" "this" { + for_each = { for k, v in var.subscriptions : k => v if var.create && var.create_subscription } + + confirmation_timeout_in_minutes = try(each.value.confirmation_timeout_in_minutes, null) + delivery_policy = try(each.value.delivery_policy, null) + endpoint = each.value.endpoint + endpoint_auto_confirms = try(each.value.endpoint_auto_confirms, null) + filter_policy = try(each.value.filter_policy, null) + filter_policy_scope = try(each.value.filter_policy_scope, null) + protocol = each.value.protocol + raw_message_delivery = try(each.value.raw_message_delivery, null) + redrive_policy = try(each.value.redrive_policy, null) + replay_policy = try(each.value.replay_policy, null) + subscription_role_arn = try(each.value.subscription_role_arn, null) + topic_arn = aws_sns_topic.this[0].arn +} + +################################################################################ +# Data Protection Policy +################################################################################ + +resource "aws_sns_topic_data_protection_policy" "this" { + count = var.create && var.data_protection_policy != null && !var.fifo_topic ? 1 : 0 + + arn = aws_sns_topic.this[0].arn + policy = var.data_protection_policy +} diff --git a/modules/aws-sns/outputs.tf b/modules/aws-sns/outputs.tf new file mode 100644 index 0000000..e564477 --- /dev/null +++ b/modules/aws-sns/outputs.tf @@ -0,0 +1,37 @@ +################################################################################ +# Topic +################################################################################ + +output "topic_arn" { + description = "The ARN of the SNS topic, as a more obvious property (clone of id)" + value = try(aws_sns_topic.this[0].arn, null) +} + +output "topic_id" { + description = "The ARN of the SNS topic" + value = try(aws_sns_topic.this[0].id, null) +} + +output "topic_name" { + description = "The name of the topic" + value = try(aws_sns_topic.this[0].name, null) +} + +output "topic_owner" { + description = "The AWS Account ID of the SNS topic owner" + value = try(aws_sns_topic.this[0].owner, null) +} + +output "topic_beginning_archive_time" { + description = "The oldest timestamp at which a FIFO topic subscriber can start a replay" + value = try(aws_sns_topic.this[0].beginning_archive_time, null) +} + +################################################################################ +# Subscription(s) +################################################################################ + +output "subscriptions" { + description = "Map of subscriptions created and their attributes" + value = aws_sns_topic_subscription.this +} diff --git a/modules/aws-sns/variables.tf b/modules/aws-sns/variables.tf new file mode 100644 index 0000000..64240e1 --- /dev/null +++ b/modules/aws-sns/variables.tf @@ -0,0 +1,201 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Topic +################################################################################ + +variable "name" { + description = "The name of the SNS topic to create" + type = string + default = null +} + +variable "use_name_prefix" { + description = "Determines whether `name` is used as a prefix" + type = bool + default = false +} + +variable "application_feedback" { + description = "Map of IAM role ARNs and sample rate for success and failure feedback" + type = map(string) + default = {} + # Example: + # application_feedback = { + # failure_role_arn = "arn:aws:iam::11111111111:role/failure" + # success_role_arn = "arn:aws:iam::11111111111:role/success" + # success_sample_rate = 75 + # } +} + +variable "content_based_deduplication" { + description = "Boolean indicating whether or not to enable content-based deduplication for FIFO topics." + type = bool + default = false +} + +variable "delivery_policy" { + description = "The SNS delivery policy" + type = string + default = null +} + +variable "display_name" { + description = "The display name for the SNS topic" + type = string + default = null +} + +variable "fifo_topic" { + description = "Boolean indicating whether or not to create a FIFO (first-in-first-out) topic" + type = bool + default = false +} + +variable "firehose_feedback" { + description = "Map of IAM role ARNs and sample rate for success and failure feedback" + type = map(string) + default = {} + # Example: + # application_feedback = { + # failure_role_arn = "arn:aws:iam::11111111111:role/failure" + # success_role_arn = "arn:aws:iam::11111111111:role/success" + # success_sample_rate = 75 + # } +} + +variable "http_feedback" { + description = "Map of IAM role ARNs and sample rate for success and failure feedback" + type = map(string) + default = {} + # Example: + # application_feedback = { + # failure_role_arn = "arn:aws:iam::11111111111:role/failure" + # success_role_arn = "arn:aws:iam::11111111111:role/success" + # success_sample_rate = 75 + # } +} + +variable "kms_master_key_id" { + description = "The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK" + type = string + default = null +} + +variable "lambda_feedback" { + description = "Map of IAM role ARNs and sample rate for success and failure feedback" + type = map(string) + default = {} + # Example: + # application_feedback = { + # failure_role_arn = "arn:aws:iam::11111111111:role/failure" + # success_role_arn = "arn:aws:iam::11111111111:role/success" + # success_sample_rate = 75 + # } +} + +variable "topic_policy" { + description = "An externally created fully-formed AWS policy as JSON" + type = string + default = null +} + +variable "sqs_feedback" { + description = "Map of IAM role ARNs and sample rate for success and failure feedback" + type = map(string) + default = {} + # Example: + # application_feedback = { + # failure_role_arn = "arn:aws:iam::11111111111:role/failure" + # success_role_arn = "arn:aws:iam::11111111111:role/success" + # success_sample_rate = 75 + # } +} + +variable "signature_version" { + description = "If SignatureVersion should be 1 (SHA1) or 2 (SHA256). The signature version corresponds to the hashing algorithm used while creating the signature of the notifications, subscription confirmations, or unsubscribe confirmation messages sent by Amazon SNS." + type = number + default = null +} + +variable "tracing_config" { + description = "Tracing mode of an Amazon SNS topic. Valid values: PassThrough, Active." + type = string + default = null +} + +variable "archive_policy" { + description = "The message archive policy for FIFO topics." + type = string + default = null +} + +################################################################################ +# Topic Policy +################################################################################ + +variable "create_topic_policy" { + description = "Determines whether an SNS topic policy is created" + type = bool + default = true +} + +variable "source_topic_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_topic_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "enable_default_topic_policy" { + description = "Specifies whether to enable the default topic policy. Defaults to `true`" + type = bool + default = true +} + +variable "topic_policy_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Subscription(s) +################################################################################ + +variable "create_subscription" { + description = "Determines whether an SNS subscription is created" + type = bool + default = true +} + +variable "subscriptions" { + description = "A map of subscription definitions to create" + type = any + default = {} +} + +################################################################################ +# Data Protection Policy +################################################################################ + +variable "data_protection_policy" { + description = "A map of data protection policy statements" + type = string + default = null +} diff --git a/modules/aws-sns/versions.tf b/modules/aws-sns/versions.tf new file mode 100644 index 0000000..2c1a62c --- /dev/null +++ b/modules/aws-sns/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.25" + } + } +} diff --git a/modules/aws-ssm-parameter-store/.gitignore b/modules/aws-ssm-parameter-store/.gitignore new file mode 100644 index 0000000..8d1abcf --- /dev/null +++ b/modules/aws-ssm-parameter-store/.gitignore @@ -0,0 +1,11 @@ +*.tfstate +*.tfstate.* +**/*.pem +**/.terraform +**/terraform.tfstate +**/terraform.tfstate.backup +.idea +*.iml + +.build-harness +build-harness diff --git a/modules/aws-ssm-parameter-store/README.md b/modules/aws-ssm-parameter-store/README.md new file mode 100644 index 0000000..85e01de --- /dev/null +++ b/modules/aws-ssm-parameter-store/README.md @@ -0,0 +1,160 @@ + + +# terraform-aws-ssm-parameter-store + +Terraform module for providing read and write access to the AWS SSM Parameter Store. + +--- + + +## Usage + + +**IMPORTANT:** We do not pin modules to versions in our examples because of the +difficulty of keeping the versions in the documentation in sync with the latest released versions. +We highly recommend that in your code you pin the version to the exact version you are +using so that your infrastructure remains stable, and update versions in a +systematic way so that they do not catch you by surprise. + +Also, because of a bug in the Terraform registry ([hashicorp/terraform#21417](https://github.com/hashicorp/terraform/issues/21417)), +the registry shows many of our inputs as required when in fact they are optional. +The table below correctly indicates which inputs are required. + + +This example creates a new `String` parameter called `/cp/prod/app/database/master_password` with the value of `password1`. + +```hcl +module "store_write" { + source = "kloia/ssm-parameter-store/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + + parameter_write = [ + { + name = "/cp/prod/app/database/master_password" + value = "password1" + type = "String" + overwrite = "true" + description = "Production database master password" + } + ] + + tags = { + ManagedBy = "Terraform" + } +} +``` + +This example reads a value from the parameter store with the name `/cp/prod/app/database/master_password` + +```hcl +module "store_read" { + source = "kloia/ssm-parameter-store/aws" + # Cloud Posse recommends pinning every module to a specific version + # version = "x.x.x" + + parameter_read = ["/cp/prod/app/database/master_password"] +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | cloudposse/label/null | 0.25.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_ssm_parameter.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.ignore_value_changes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [ignore\_value\_changes](#input\_ignore\_value\_changes) | Whether to ignore future external changes in paramater values | `bool` | `false` | no | +| [kms\_arn](#input\_kms\_arn) | The ARN of a KMS key used to encrypt and decrypt SecretString values | `string` | `""` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [parameter\_read](#input\_parameter\_read) | List of parameters to read from SSM. These must already exist otherwise an error is returned. Can be used with `parameter_write` as long as the parameters are different. | `list(string)` | `[]` | no | +| [parameter\_write](#input\_parameter\_write) | List of maps with the parameter values to write to SSM Parameter Store | `list(map(string))` | `[]` | no | +| [parameter\_write\_defaults](#input\_parameter\_write\_defaults) | Parameter write default settings | `map(any)` |
{
"allowed_pattern": null,
"data_type": "text",
"description": null,
"overwrite": "false",
"tier": "Standard",
"type": "SecureString"
}
| no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tags](#cross\_account\_read) | Whether to create read parameter store data from cross account. | `bool` | `false` | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn\_map](#output\_arn\_map) | A map of the names and ARNs created | +| [map](#output\_map) | A map of the names and values created | +| [names](#output\_names) | A list of all of the parameter names | +| [values](#output\_values) | A list of all of the parameter values | + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-ssm-parameter-store/issues) to report any bugs or file feature requests. + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.0 | +| [aws](#requirement\_aws) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ssm_parameter.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.ignore_value_changes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [enabled](#input\_enabled) | Create resources if enabled true | `bool` | `true` | no | +| [ignore\_value\_changes](#input\_ignore\_value\_changes) | Whether to ignore future external changes in paramater values | `bool` | `false` | no | +| [kms\_arn](#input\_kms\_arn) | The ARN of a KMS key used to encrypt and decrypt SecretString values | `string` | `""` | no | +| [parameter\_read](#input\_parameter\_read) | List of parameters to read from SSM. These must already exist otherwise an error is returned. Can be used with `parameter_write` as long as the parameters are different. | `list(string)` | `[]` | no | +| [parameter\_write](#input\_parameter\_write) | List of maps with the parameter values to write to SSM Parameter Store | `list(map(string))` | `[]` | no | +| [parameter\_write\_defaults](#input\_parameter\_write\_defaults) | Parameter write default settings | `map(any)` |
{
"allowed_pattern": null,
"data_type": "text",
"description": null,
"overwrite": "false",
"tier": "Standard",
"type": "SecureString"
}
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn\_map](#output\_arn\_map) | A map of the names and ARNs created | +| [map](#output\_map) | A map of the names and values created | +| [names](#output\_names) | A list of all of the parameter names | +| [values](#output\_values) | A list of all of the parameter values | + \ No newline at end of file diff --git a/modules/aws-ssm-parameter-store/main.tf b/modules/aws-ssm-parameter-store/main.tf new file mode 100644 index 0000000..7492df3 --- /dev/null +++ b/modules/aws-ssm-parameter-store/main.tf @@ -0,0 +1,53 @@ +locals { + enabled = var.enabled + parameter_write = local.enabled && !var.ignore_value_changes ? { for e in var.parameter_write : e.name => merge(var.parameter_write_defaults, merge(e, { value = try(join(",", e.value), e.value) })) } : {} + parameter_write_ignore_values = local.enabled && var.ignore_value_changes ? { for e in var.parameter_write : e.name => merge(var.parameter_write_defaults, merge(e, { value = try(join(",", e.value), e.value) })) } : {} + parameter_read = local.enabled ? var.parameter_read : [] +} + +data "aws_ssm_parameter" "read" { + count = var.cross_account_read == false ? length(local.parameter_read) : 0 + name = element(local.parameter_read, count.index) +} + +data "aws_ssm_parameter" "cross_account_read" { + count = var.cross_account_read == true ? length(local.parameter_read) : 0 + name = element(local.parameter_read, count.index) + provider = aws.cross_account +} + +resource "aws_ssm_parameter" "default" { + for_each = local.parameter_write + name = each.key + + description = each.value.description + type = each.value.type + tier = each.value.tier + key_id = each.value.type == "SecureString" && length(var.kms_arn) > 0 ? var.kms_arn : "" + value = each.value.value + overwrite = each.value.overwrite + allowed_pattern = each.value.allowed_pattern + data_type = each.value.data_type + +} + +resource "aws_ssm_parameter" "ignore_value_changes" { + for_each = local.parameter_write_ignore_values + name = each.key + + description = each.value.description + type = each.value.type + tier = each.value.tier + key_id = each.value.type == "SecureString" && length(var.kms_arn) > 0 ? var.kms_arn : "" + value = each.value.value + overwrite = each.value.overwrite + allowed_pattern = each.value.allowed_pattern + data_type = each.value.data_type + + + lifecycle { + ignore_changes = [ + value, + ] + } +} diff --git a/modules/aws-ssm-parameter-store/outputs.tf b/modules/aws-ssm-parameter-store/outputs.tf new file mode 100644 index 0000000..3a0b9e5 --- /dev/null +++ b/modules/aws-ssm-parameter-store/outputs.tf @@ -0,0 +1,39 @@ +# Splitting and joining, and then compacting a list to get a normalised list +locals { + name_list = compact(concat(keys(local.parameter_write), keys(local.parameter_write_ignore_values), local.parameter_read)) + + value_list = compact( + concat( + [for p in aws_ssm_parameter.default : p.value], [for p in aws_ssm_parameter.ignore_value_changes : p.value], data.aws_ssm_parameter.read.*.value, data.aws_ssm_parameter.cross_account_read.*.value + ) + ) + + arn_list = compact( + concat( + [for p in aws_ssm_parameter.default : p.arn], [for p in aws_ssm_parameter.ignore_value_changes : p.arn], data.aws_ssm_parameter.read.*.arn, data.aws_ssm_parameter.cross_account_read.*.arn + ) + ) +} + +output "names" { + # Names are not sensitive + value = local.name_list + description = "A list of all of the parameter names" +} + +output "values" { + description = "A list of all of the parameter values" + value = local.value_list + sensitive = true +} + +output "map" { + description = "A map of the names and values created" + value = try(zipmap(local.name_list, local.value_list), "Failed") + sensitive = true +} + +output "arn_map" { + description = "A map of the names and ARNs created" + value = try(zipmap(local.name_list, local.arn_list), "Failed") +} diff --git a/modules/aws-ssm-parameter-store/variables.tf b/modules/aws-ssm-parameter-store/variables.tf new file mode 100644 index 0000000..9a8c968 --- /dev/null +++ b/modules/aws-ssm-parameter-store/variables.tf @@ -0,0 +1,47 @@ +variable "enabled" { + description = "Create resources if enabled true" + default = true +} + +variable "parameter_read" { + type = list(string) + description = "List of parameters to read from SSM. These must already exist otherwise an error is returned. Can be used with `parameter_write` as long as the parameters are different." + default = [] +} + +variable "parameter_write" { + type = list(map(string)) + description = "List of maps with the parameter values to write to SSM Parameter Store" + default = [] +} + +variable "kms_arn" { + type = string + default = "" + description = "The ARN of a KMS key used to encrypt and decrypt SecretString values" +} + +variable "parameter_write_defaults" { + type = map(any) + description = "Parameter write default settings" + default = { + description = null + type = "SecureString" + tier = "Standard" + overwrite = "false" + allowed_pattern = null + data_type = "text" + } +} + +variable "ignore_value_changes" { + type = bool + description = "Whether to ignore future external changes in paramater values" + default = false +} + +variable "cross_account_read" { + type = bool + description = "Read parameter from cross account" + default = false +} \ No newline at end of file diff --git a/modules/aws-ssm-parameter-store/versions.tf b/modules/aws-ssm-parameter-store/versions.tf new file mode 100644 index 0000000..5b2c49b --- /dev/null +++ b/modules/aws-ssm-parameter-store/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.0" + } + } +} diff --git a/modules/aws-transit-gateway/.editorconfig b/modules/aws-transit-gateway/.editorconfig new file mode 100644 index 0000000..88cb251 --- /dev/null +++ b/modules/aws-transit-gateway/.editorconfig @@ -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 diff --git a/modules/aws-transit-gateway/.github/workflows/lock.yml b/modules/aws-transit-gateway/.github/workflows/lock.yml new file mode 100644 index 0000000..6b6c9ce --- /dev/null +++ b/modules/aws-transit-gateway/.github/workflows/lock.yml @@ -0,0 +1,21 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '50 1 * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + issue-comment: > + I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. + If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. + issue-inactive-days: '30' + pr-comment: > + I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. + If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. + pr-inactive-days: '30' diff --git a/modules/aws-transit-gateway/.github/workflows/pr-title.yml b/modules/aws-transit-gateway/.github/workflows/pr-title.yml new file mode 100644 index 0000000..cb32a0f --- /dev/null +++ b/modules/aws-transit-gateway/.github/workflows/pr-title.yml @@ -0,0 +1,52 @@ +name: 'Validate PR title' + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + - uses: amannn/action-semantic-pull-request@v5.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + fix + feat + docs + ci + chore + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject starts with an uppercase character. + subjectPattern: ^[A-Z].+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + starts with an uppercase character. + # For work-in-progress PRs you can typically use draft pull requests + # from Github. However, private repositories on the free plan don't have + # this option and therefore this action allows you to opt-in to using the + # special "[WIP]" prefix to indicate this state. This will avoid the + # validation of the PR title and the pull request checks remain pending. + # Note that a second check will be reported if this is enabled. + wip: true + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for the + # merge commit, and it's easy to commit this by mistake. Enable this option + # to also validate the commit message for one commit PRs. + validateSingleCommit: false diff --git a/modules/aws-transit-gateway/.github/workflows/pre-commit.yml b/modules/aws-transit-gateway/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..cb82671 --- /dev/null +++ b/modules/aws-transit-gateway/.github/workflows/pre-commit.yml @@ -0,0 +1,83 @@ +name: Pre-Commit + +on: + pull_request: + branches: + - main + - master + +env: + TERRAFORM_DOCS_VERSION: v0.16.0 + TFLINT_VERSION: v0.44.1 + +jobs: + collectInputs: + name: Collect workflow inputs + runs-on: ubuntu-latest + outputs: + directories: ${{ steps.dirs.outputs.directories }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get root directories + id: dirs + uses: clowdhaus/terraform-composite-actions/directories@v1.8.3 + + preCommitMinVersions: + name: Min TF pre-commit + needs: collectInputs + runs-on: ubuntu-latest + strategy: + matrix: + directory: ${{ fromJson(needs.collectInputs.outputs.directories) }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Terraform min/max versions + id: minMax + uses: clowdhaus/terraform-min-max@v1.2.4 + with: + directory: ${{ matrix.directory }} + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} + # Run only validate pre-commit check on min version supported + if: ${{ matrix.directory != '.' }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + args: 'terraform_validate --color=always --show-diff-on-failure --files ${{ matrix.directory }}/*' + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} + # Run only validate pre-commit check on min version supported + if: ${{ matrix.directory == '.' }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + args: 'terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)' + + preCommitMaxVersion: + name: Max TF pre-commit + runs-on: ubuntu-latest + needs: collectInputs + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Terraform min/max versions + id: minMax + uses: clowdhaus/terraform-min-max@v1.2.4 + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.maxVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + terraform-docs-version: ${{ env.TERRAFORM_DOCS_VERSION }} + install-hcledit: true diff --git a/modules/aws-transit-gateway/.github/workflows/release.yml b/modules/aws-transit-gateway/.github/workflows/release.yml new file mode 100644 index 0000000..81f6747 --- /dev/null +++ b/modules/aws-transit-gateway/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + - master + paths: + - '**/*.tpl' + - '**/*.py' + - '**/*.tf' + - '.github/workflows/release.yml' + +jobs: + release: + name: Release + runs-on: ubuntu-latest + # Skip running release workflow on forks + if: github.repository_owner == 'terraform-aws-modules' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Release + uses: cycjimmy/semantic-release-action@v3 + with: + semantic_version: 18.0.0 + extra_plugins: | + @semantic-release/changelog@6.0.0 + @semantic-release/git@10.0.0 + conventional-changelog-conventionalcommits@4.6.3 + env: + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/modules/aws-transit-gateway/.github/workflows/stale-actions.yaml b/modules/aws-transit-gateway/.github/workflows/stale-actions.yaml new file mode 100644 index 0000000..5037995 --- /dev/null +++ b/modules/aws-transit-gateway/.github/workflows/stale-actions.yaml @@ -0,0 +1,32 @@ +name: 'Mark or close stale issues and PRs' +on: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v6 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # Staling issues and PR's + days-before-stale: 30 + stale-issue-label: stale + stale-pr-label: stale + stale-issue-message: | + This issue has been automatically marked as stale because it has been open 30 days + with no activity. Remove stale label or comment or this issue will be closed in 10 days + stale-pr-message: | + This PR has been automatically marked as stale because it has been open 30 days + with no activity. Remove stale label or comment or this PR will be closed in 10 days + # Not stale if have this labels or part of milestone + exempt-issue-labels: bug,wip,on-hold + exempt-pr-labels: bug,wip,on-hold + exempt-all-milestones: true + # Close issue operations + # Label will be automatically removed if the issues are no longer closed nor locked. + days-before-close: 10 + delete-branch: true + close-issue-message: This issue was automatically closed because of stale in 10 days + close-pr-message: This PR was automatically closed because of stale in 10 days diff --git a/modules/aws-transit-gateway/.gitignore b/modules/aws-transit-gateway/.gitignore new file mode 100644 index 0000000..0da622a --- /dev/null +++ b/modules/aws-transit-gateway/.gitignore @@ -0,0 +1,34 @@ +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* +*.tfplan + +# 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 + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Lambda directories +builds/ +__pycache__/ diff --git a/modules/aws-transit-gateway/.pre-commit-config.yaml b/modules/aws-transit-gateway/.pre-commit-config.yaml new file mode 100644 index 0000000..d5886a6 --- /dev/null +++ b/modules/aws-transit-gateway/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.77.0 + hooks: + - id: terraform_fmt + - id: terraform_validate + - id: terraform_docs + args: + - '--args=--lockfile=false' + - id: terraform_tflint + args: + - '--args=--only=terraform_deprecated_interpolation' + - '--args=--only=terraform_deprecated_index' + - '--args=--only=terraform_unused_declarations' + - '--args=--only=terraform_comment_syntax' + - '--args=--only=terraform_documented_outputs' + - '--args=--only=terraform_documented_variables' + - '--args=--only=terraform_typed_variables' + - '--args=--only=terraform_module_pinned_source' + - '--args=--only=terraform_naming_convention' + - '--args=--only=terraform_required_version' + - '--args=--only=terraform_required_providers' + - '--args=--only=terraform_standard_module_structure' + - '--args=--only=terraform_workspace_remote' + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer diff --git a/modules/aws-transit-gateway/.releaserc.json b/modules/aws-transit-gateway/.releaserc.json new file mode 100644 index 0000000..66b3eef --- /dev/null +++ b/modules/aws-transit-gateway/.releaserc.json @@ -0,0 +1,45 @@ +{ + "branches": [ + "main", + "master" + ], + "ci": false, + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/github", + { + "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", + "labels": false, + "releasedLabels": false + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md" + ], + "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/modules/aws-transit-gateway/CHANGELOG.md b/modules/aws-transit-gateway/CHANGELOG.md new file mode 100644 index 0000000..9e835f8 --- /dev/null +++ b/modules/aws-transit-gateway/CHANGELOG.md @@ -0,0 +1,151 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [2.10.0](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.9.0...v2.10.0) (2023-04-26) + + +### Features + +* Fixed typo in mutlicast to multicast, also in the variable name ([#108](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/108)) ([baaa7f4](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/baaa7f44c458d29b95d372e3faae7f89a148da0c)) + +## [2.9.0](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.8.2...v2.9.0) (2023-02-27) + + +### Features + +* Added tags per VPC attachment ([#103](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/103)) ([e4d6df2](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/e4d6df2aa4bab0d840bbab71276cca3bc69f9113)) + +### [2.8.2](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.8.1...v2.8.2) (2023-01-24) + + +### Bug Fixes + +* Use a version for to avoid GitHub API rate limiting on CI workflows ([#96](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/96)) ([de6e0cf](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/de6e0cf41b7ee1b84e506f77415257f01f51065d)) + +### [2.8.1](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.8.0...v2.8.1) (2022-10-27) + + +### Bug Fixes + +* Update CI configuration files to use latest version ([#88](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/88)) ([12ccdcc](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/12ccdcc0a209973e391e05079f3e1f04c0a78ff7)) + +## [2.8.0](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.7.0...v2.8.0) (2022-05-09) + + +### Features + +* Added TGW multicast support ([#73](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/73)) ([a4d569b](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/a4d569b7f03443921d9dff7ce54f8acc06aed7fa)) + +## [2.7.0](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.6.0...v2.7.0) (2022-03-26) + + +### Features + +* Add support for transit gateway CIDR blocks ([#69](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/69)) ([131ed50](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/131ed5006713aec86a20147796ce6489f6daadc6)) + +## [2.6.0](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.5.1...v2.6.0) (2022-03-26) + + +### Features + +* Update Terraform minimum supported version to `v0.13.1` ([#68](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/68)) ([4e8f9c9](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/4e8f9c95d429d8f623db563388fe759707e38379)) + +### [2.5.1](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.5.0...v2.5.1) (2022-01-10) + + +### Bug Fixes + +* update CI/CD process to enable auto-release workflow ([#63](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/63)) ([558f5ff](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/commit/558f5ff261d9e5b25304c3f38ae0242850c92b2b)) + + +## [v2.5.0] - 2021-07-07 + +- fix: add tags if the default route table association is enabled ([#52](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/52)) + + + +## [v2.4.0] - 2021-05-24 + +- feat: Optionally update VPC Route Tables for attached VPCs ([#35](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/35)) + + + +## [v2.3.0] - 2021-05-19 + +- feat: default tgw route table tags ([#49](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/49)) + + + +## [v2.2.0] - 2021-05-19 + +- feat: adding appliance_mode_support to vpc attachments ([#48](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/48)) + + + +## [v2.1.0] - 2021-05-05 + +- fix: Update map function to work in Terraform 0.15 ([#44](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/44)) +- chore: update CI/CD to use stable `terraform-docs` release artifact and discoverable Apache2.0 license ([#42](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/42)) + + + +## [v2.0.0] - 2021-04-27 + +- feat: Shorten outputs (removing this_) ([#41](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/41)) +- chore: update documentation and pin `terraform_docs` version to avoid future changes ([#40](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/40)) +- chore: align ci-cd static checks to use individual minimum Terraform versions ([#38](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/38)) +- fix: bump min supported version due to types unsupported on current ([#37](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/37)) +- chore: add ci-cd workflow for pre-commit checks ([#36](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/36)) + + + +## [v1.4.0] - 2020-11-24 + +- fix: Updated supported Terraform versions ([#30](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/30)) +- docs: typos on example readme.mds ([#21](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/21)) + + + +## [v1.3.0] - 2020-08-18 + +- fix: Added support for multi-account deployments ([#20](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/20)) + + + +## [v1.2.0] - 2020-08-17 + +- chore: Minor updates in docs +- fix: fix variable in aws_ec2_transit_gateway_route_table_propagation ([#13](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/issues/13)) + + + +## [v1.1.0] - 2020-01-16 + +- Updated notes in example + + + +## [v1.0.0] - 2020-01-15 + +- Added code for the module + + + +## v0.0.1 - 2020-01-15 + +- Initial commit + + +[Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.5.0...HEAD +[v2.5.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v1.4.0...v2.0.0 +[v1.4.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v1.1.0...v1.2.0 +[v1.1.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/compare/v0.0.1...v1.0.0 diff --git a/modules/aws-transit-gateway/LICENSE b/modules/aws-transit-gateway/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/modules/aws-transit-gateway/LICENSE @@ -0,0 +1,176 @@ + 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 diff --git a/modules/aws-transit-gateway/README.md b/modules/aws-transit-gateway/README.md new file mode 100644 index 0000000..9ddff42 --- /dev/null +++ b/modules/aws-transit-gateway/README.md @@ -0,0 +1,164 @@ +# AWS Transit Gateway Terraform module + +Terraform module which creates Transit Gateway resources on AWS. + +## Usage with VPC module + +```hcl +module "tgw" { + source = "terraform-aws-modules/transit-gateway/aws" + version = "~> 2.0" + + name = "my-tgw" + description = "My TGW shared with several other AWS accounts" + + enable_auto_accept_shared_attachments = true + + vpc_attachments = { + vpc = { + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + dns_support = true + ipv6_support = true + + tgw_routes = [ + { + destination_cidr_block = "30.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "40.0.0.0/20" + } + ] + vpc_route_table_ids = ["rtb-xyz", "rtb-abc"] + vpc_cidrs = ["10.0.0.0/16", "20.0.0.0/16"] + } + } + + ram_allow_external_principals = true + ram_principals = [307990089504] + + tags = { + Purpose = "tgw-complete-example" + } +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = "my-vpc" + + cidr = "10.10.0.0/16" + + azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] + private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] + + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [0, 1, 2] +} +``` + +## Examples + +- [Complete example](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/complete) shows TGW in combination with the [VPC module](https://github.com/terraform-aws-modules/terraform-aws-vpc) and [Resource Access Manager (RAM)](https://aws.amazon.com/ram/). +- [Multi-account example](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/multi-account) shows TGW resources shared with different AWS accounts (via [Resource Access Manager (RAM)](https://aws.amazon.com/ram/)). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.4 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.4 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ec2_tag.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_transit_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway) | resource | +| [aws_ec2_transit_gateway_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | +| [aws_ec2_transit_gateway_route_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource | +| [aws_ec2_transit_gateway_route_table_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource | +| [aws_ec2_transit_gateway_route_table_propagation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | +| [aws_ec2_transit_gateway_vpc_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_vpc_attachment) | resource | +| [aws_ram_principal_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | +| [aws_ram_resource_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | +| [aws_ram_resource_share.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | +| [aws_ram_resource_share_accepter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share_accepter) | resource | +| [aws_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [amazon\_side\_asn](#input\_amazon\_side\_asn) | The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the TGW is created with the current default Amazon ASN. | `string` | `null` | no | +| [create\_tgw](#input\_create\_tgw) | Controls if TGW should be created (it affects almost all resources) | `bool` | `true` | no | +| [description](#input\_description) | Description of the EC2 Transit Gateway | `string` | `null` | no | +| [enable\_auto\_accept\_shared\_attachments](#input\_enable\_auto\_accept\_shared\_attachments) | Whether resource attachment requests are automatically accepted | `bool` | `false` | no | +| [enable\_default\_route\_table\_association](#input\_enable\_default\_route\_table\_association) | Whether resource attachments are automatically associated with the default association route table | `bool` | `true` | no | +| [enable\_default\_route\_table\_propagation](#input\_enable\_default\_route\_table\_propagation) | Whether resource attachments automatically propagate routes to the default propagation route table | `bool` | `true` | no | +| [enable\_dns\_support](#input\_enable\_dns\_support) | Should be true to enable DNS support in the TGW | `bool` | `true` | no | +| [enable\_multicast\_support](#input\_enable\_multicast\_support) | Whether multicast support is enabled | `bool` | `false` | no | +| [enable\_vpn\_ecmp\_support](#input\_enable\_vpn\_ecmp\_support) | Whether VPN Equal Cost Multipath Protocol support is enabled | `bool` | `true` | no | +| [name](#input\_name) | Name to be used on all the resources as identifier | `string` | `""` | no | +| [ram\_allow\_external\_principals](#input\_ram\_allow\_external\_principals) | Indicates whether principals outside your organization can be associated with a resource share. | `bool` | `false` | no | +| [ram\_name](#input\_ram\_name) | The name of the resource share of TGW | `string` | `""` | no | +| [ram\_principals](#input\_ram\_principals) | A list of principals to share TGW with. Possible values are an AWS account ID, an AWS Organizations Organization ARN, or an AWS Organizations Organization Unit ARN | `list(string)` | `[]` | no | +| [ram\_resource\_share\_arn](#input\_ram\_resource\_share\_arn) | ARN of RAM resource share | `string` | `""` | no | +| [ram\_tags](#input\_ram\_tags) | Additional tags for the RAM | `map(string)` | `{}` | no | +| [share\_tgw](#input\_share\_tgw) | Whether to share your transit gateway with other accounts | `bool` | `true` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [tgw\_default\_route\_table\_tags](#input\_tgw\_default\_route\_table\_tags) | Additional tags for the Default TGW route table | `map(string)` | `{}` | no | +| [tgw\_route\_table\_tags](#input\_tgw\_route\_table\_tags) | Additional tags for the TGW route table | `map(string)` | `{}` | no | +| [tgw\_tags](#input\_tgw\_tags) | Additional tags for the TGW | `map(string)` | `{}` | no | +| [tgw\_vpc\_attachment\_tags](#input\_tgw\_vpc\_attachment\_tags) | Additional tags for VPC attachments | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the transit gateway | `map(string)` | `{}` | no | +| [transit\_gateway\_cidr\_blocks](#input\_transit\_gateway\_cidr\_blocks) | One or more IPv4 or IPv6 CIDR blocks for the transit gateway. Must be a size /24 CIDR block or larger for IPv4, or a size /64 CIDR block or larger for IPv6 | `list(string)` | `[]` | no | +| [transit\_gateway\_route\_table\_id](#input\_transit\_gateway\_route\_table\_id) | Identifier of EC2 Transit Gateway Route Table to use with the Target Gateway when reusing it between multiple TGWs | `string` | `null` | no | +| [vpc\_attachments](#input\_vpc\_attachments) | Maps of maps of VPC details to attach to TGW. Type 'any' to disable type validation by Terraform. | `any` | `{}` | no | +| [tgw\_route\_table\_env](#tgw\_route\_table\_env) | Identifier of EC2 Transit Gateway Route Table environment to use with routes. Required if you create vpc-attachment | `string` | `null` | no | +| [cross\_account\_assosiation\_propagation](#cross\_account\_assosiation\_propagation) | Cross account assosiation and propagation | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [ec2\_transit\_gateway\_arn](#output\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | +| [ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | +| [ec2\_transit\_gateway\_id](#output\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | +| [ec2\_transit\_gateway\_owner\_id](#output\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | +| [ec2\_transit\_gateway\_route\_ids](#output\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | +| [ec2\_transit\_gateway\_route\_table\_association](#output\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | +| [ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | +| [ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_id](#output\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | +| [ec2\_transit\_gateway\_route\_table\_propagation](#output\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | +| [ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | +| [ec2\_transit\_gateway\_vpc\_attachment](#output\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [ram\_principal\_association\_id](#output\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | +| [ram\_resource\_share\_id](#output\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/LICENSE) for full details. diff --git a/modules/aws-transit-gateway/examples/complete/README.md b/modules/aws-transit-gateway/examples/complete/README.md new file mode 100644 index 0000000..b11a040 --- /dev/null +++ b/modules/aws-transit-gateway/examples/complete/README.md @@ -0,0 +1,66 @@ +# Complete AWS Transit Gateway example + +Configuration in this directory creates AWS Transit Gateway, attach VPC to it and share it with other AWS principals using [Resource Access Manager (RAM)](https://aws.amazon.com/ram/). + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.4 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [tgw](#module\_tgw) | ../../ | n/a | +| [vpc1](#module\_vpc1) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc2](#module\_vpc2) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [ec2\_transit\_gateway\_arn](#output\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | +| [ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | +| [ec2\_transit\_gateway\_id](#output\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | +| [ec2\_transit\_gateway\_owner\_id](#output\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | +| [ec2\_transit\_gateway\_route\_ids](#output\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | +| [ec2\_transit\_gateway\_route\_table\_association](#output\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | +| [ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | +| [ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_id](#output\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | +| [ec2\_transit\_gateway\_route\_table\_propagation](#output\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | +| [ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | +| [ec2\_transit\_gateway\_vpc\_attachment](#output\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [ram\_principal\_association\_id](#output\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | +| [ram\_resource\_share\_id](#output\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | + diff --git a/modules/aws-transit-gateway/examples/complete/main.tf b/modules/aws-transit-gateway/examples/complete/main.tf new file mode 100644 index 0000000..a3be95f --- /dev/null +++ b/modules/aws-transit-gateway/examples/complete/main.tf @@ -0,0 +1,114 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-tgw-${replace(basename(path.cwd), "_", "-")}" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-transit-gateway" + } +} + +################################################################################ +# Transit Gateway Module +################################################################################ + +module "tgw" { + source = "../../" + + name = local.name + description = "My TGW shared with several other AWS accounts" + amazon_side_asn = 64532 + + transit_gateway_cidr_blocks = ["10.99.0.0/24"] + + # When "true" there is no need for RAM resources if using multiple AWS accounts + enable_auto_accept_shared_attachments = true + + # When "true", allows service discovery through IGMP + enable_multicast_support = false + + vpc_attachments = { + vpc1 = { + vpc_id = module.vpc1.vpc_id + subnet_ids = module.vpc1.private_subnets + dns_support = true + ipv6_support = true + + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + + tgw_routes = [ + { + destination_cidr_block = "30.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "0.0.0.0/0" + } + ] + }, + vpc2 = { + vpc_id = module.vpc2.vpc_id + subnet_ids = module.vpc2.private_subnets + + tgw_routes = [ + { + destination_cidr_block = "50.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "10.10.10.10/32" + } + ] + tags = { + Name = "${local.name}-vpc2" + } + }, + } + + ram_allow_external_principals = true + ram_principals = [307990089504] + + tags = local.tags +} + +################################################################################ +# Supporting resources +################################################################################ + +module "vpc1" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = "${local.name}-vpc1" + cidr = "10.10.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] + + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [0, 1, 2] + + tags = local.tags +} + +module "vpc2" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = "${local.name}-vpc2" + cidr = "10.20.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] + + enable_ipv6 = false + + tags = local.tags +} diff --git a/modules/aws-transit-gateway/examples/complete/outputs.tf b/modules/aws-transit-gateway/examples/complete/outputs.tf new file mode 100644 index 0000000..b9a0a40 --- /dev/null +++ b/modules/aws-transit-gateway/examples/complete/outputs.tf @@ -0,0 +1,100 @@ +################################################################################ +# Transit Gateway +################################################################################ + +output "ec2_transit_gateway_arn" { + description = "EC2 Transit Gateway Amazon Resource Name (ARN)" + value = module.tgw.ec2_transit_gateway_arn +} + +output "ec2_transit_gateway_id" { + description = "EC2 Transit Gateway identifier" + value = module.tgw.ec2_transit_gateway_id +} + +output "ec2_transit_gateway_owner_id" { + description = "Identifier of the AWS account that owns the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_owner_id +} + +output "ec2_transit_gateway_association_default_route_table_id" { + description = "Identifier of the default association route table" + value = module.tgw.ec2_transit_gateway_association_default_route_table_id +} + +output "ec2_transit_gateway_propagation_default_route_table_id" { + description = "Identifier of the default propagation route table" + value = module.tgw.ec2_transit_gateway_propagation_default_route_table_id +} + +################################################################################ +# VPC Attachment +################################################################################ + +output "ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = module.tgw.ec2_transit_gateway_vpc_attachment_ids +} + +output "ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = module.tgw.ec2_transit_gateway_vpc_attachment +} + +################################################################################ +# Route Table / Routes +################################################################################ + +output "ec2_transit_gateway_route_table_id" { + description = "EC2 Transit Gateway Route Table identifier" + value = module.tgw.ec2_transit_gateway_route_table_id +} + +output "ec2_transit_gateway_route_table_default_association_route_table" { + description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_route_table_default_association_route_table +} + +output "ec2_transit_gateway_route_table_default_propagation_route_table" { + description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_route_table_default_propagation_route_table +} + +output "ec2_transit_gateway_route_ids" { + description = "List of EC2 Transit Gateway Route Table identifier combined with destination" + value = module.tgw.ec2_transit_gateway_route_ids +} + +output "ec2_transit_gateway_route_table_association_ids" { + description = "List of EC2 Transit Gateway Route Table Association identifiers" + value = module.tgw.ec2_transit_gateway_route_table_association_ids +} + +output "ec2_transit_gateway_route_table_association" { + description = "Map of EC2 Transit Gateway Route Table Association attributes" + value = module.tgw.ec2_transit_gateway_route_table_association +} + +output "ec2_transit_gateway_route_table_propagation_ids" { + description = "List of EC2 Transit Gateway Route Table Propagation identifiers" + value = module.tgw.ec2_transit_gateway_route_table_propagation_ids +} + +output "ec2_transit_gateway_route_table_propagation" { + description = "Map of EC2 Transit Gateway Route Table Propagation attributes" + value = module.tgw.ec2_transit_gateway_route_table_propagation +} + +################################################################################ +# Resource Access Manager +################################################################################ + +output "ram_resource_share_id" { + description = "The Amazon Resource Name (ARN) of the resource share" + value = module.tgw.ram_resource_share_id +} + +output "ram_principal_association_id" { + description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" + value = module.tgw.ram_principal_association_id +} diff --git a/modules/aws-transit-gateway/examples/complete/variables.tf b/modules/aws-transit-gateway/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-transit-gateway/examples/complete/versions.tf b/modules/aws-transit-gateway/examples/complete/versions.tf new file mode 100644 index 0000000..03533eb --- /dev/null +++ b/modules/aws-transit-gateway/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.4" + } + } +} diff --git a/modules/aws-transit-gateway/examples/multi-account/README.md b/modules/aws-transit-gateway/examples/multi-account/README.md new file mode 100644 index 0000000..a6b439d --- /dev/null +++ b/modules/aws-transit-gateway/examples/multi-account/README.md @@ -0,0 +1,67 @@ +# Complete AWS Transit Gateway example + +Configuration in this directory creates AWS Transit Gateway, attach VPC to it and share it with other AWS principals using [Resource Access Manager (RAM)](https://aws.amazon.com/ram/). + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 4.4 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [tgw](#module\_tgw) | ../../ | n/a | +| [tgw\_peer](#module\_tgw\_peer) | ../../ | n/a | +| [vpc1](#module\_vpc1) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc2](#module\_vpc2) | terraform-aws-modules/vpc/aws | ~> 3.0 | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [ec2\_transit\_gateway\_arn](#output\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | +| [ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | +| [ec2\_transit\_gateway\_id](#output\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | +| [ec2\_transit\_gateway\_owner\_id](#output\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | +| [ec2\_transit\_gateway\_route\_ids](#output\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | +| [ec2\_transit\_gateway\_route\_table\_association](#output\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | +| [ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | +| [ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | +| [ec2\_transit\_gateway\_route\_table\_id](#output\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | +| [ec2\_transit\_gateway\_route\_table\_propagation](#output\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | +| [ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | +| [ec2\_transit\_gateway\_vpc\_attachment](#output\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [ram\_principal\_association\_id](#output\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | +| [ram\_resource\_share\_id](#output\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | + diff --git a/modules/aws-transit-gateway/examples/multi-account/main.tf b/modules/aws-transit-gateway/examples/multi-account/main.tf new file mode 100644 index 0000000..54dbcb2 --- /dev/null +++ b/modules/aws-transit-gateway/examples/multi-account/main.tf @@ -0,0 +1,164 @@ +provider "aws" { + region = local.region +} + +# This provider is required for attachment only installation in another AWS Account +provider "aws" { + region = local.region + alias = "peer" +} + +locals { + name = "ex-tgw-${replace(basename(path.cwd), "_", "-")}" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-transit-gateway" + } +} + +################################################################################ +# Transit Gateway Module +################################################################################ + +module "tgw" { + source = "../../" + + name = local.name + description = "My TGW shared with several other AWS accounts" + amazon_side_asn = 64532 + + # When "true" there is no need for RAM resources if using multiple AWS accounts + enable_auto_accept_shared_attachments = true + + vpc_attachments = { + vpc1 = { + vpc_id = module.vpc1.vpc_id + subnet_ids = module.vpc1.private_subnets + dns_support = true + ipv6_support = true + + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + + tgw_routes = [ + { + destination_cidr_block = "30.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "0.0.0.0/0" + } + ] + }, + vpc2 = { + vpc_id = module.vpc2.vpc_id + subnet_ids = module.vpc2.private_subnets + + tgw_routes = [ + { + destination_cidr_block = "50.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "10.10.10.10/32" + } + ] + }, + } + + ram_allow_external_principals = true + ram_principals = [307990089504] + + tags = local.tags +} + +module "tgw_peer" { + # This is optional and connects to another account. Meaning you need to be authenticated with 2 separate AWS Accounts + source = "../../" + + providers = { + aws = aws.peer + } + + name = "${local.name}-peer" + description = "My TGW shared with several other AWS accounts" + amazon_side_asn = 64532 + + create_tgw = false + share_tgw = true + ram_resource_share_arn = module.tgw.ram_resource_share_id + # When "true" there is no need for RAM resources if using multiple AWS accounts + enable_auto_accept_shared_attachments = true + + vpc_attachments = { + vpc1 = { + tgw_id = module.tgw.ec2_transit_gateway_id + vpc_id = module.vpc1.vpc_id + subnet_ids = module.vpc1.private_subnets + dns_support = true + ipv6_support = true + + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + + tgw_routes = [ + { + destination_cidr_block = "30.0.0.0/16" + }, + { + blackhole = true + destination_cidr_block = "0.0.0.0/0" + } + ] + }, + } + + ram_allow_external_principals = true + ram_principals = [307990089504] + + tags = local.tags +} + +################################################################################ +# Supporting resources +################################################################################ + +module "vpc1" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = "${local.name}-vpc1" + cidr = "10.10.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] + + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [0, 1, 2] + + tags = local.tags +} + + +module "vpc2" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + providers = { + aws = aws.peer + } + + name = "${local.name}-vpc2" + cidr = "10.20.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] + + enable_ipv6 = false + + tags = local.tags +} diff --git a/modules/aws-transit-gateway/examples/multi-account/outputs.tf b/modules/aws-transit-gateway/examples/multi-account/outputs.tf new file mode 100644 index 0000000..b9a0a40 --- /dev/null +++ b/modules/aws-transit-gateway/examples/multi-account/outputs.tf @@ -0,0 +1,100 @@ +################################################################################ +# Transit Gateway +################################################################################ + +output "ec2_transit_gateway_arn" { + description = "EC2 Transit Gateway Amazon Resource Name (ARN)" + value = module.tgw.ec2_transit_gateway_arn +} + +output "ec2_transit_gateway_id" { + description = "EC2 Transit Gateway identifier" + value = module.tgw.ec2_transit_gateway_id +} + +output "ec2_transit_gateway_owner_id" { + description = "Identifier of the AWS account that owns the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_owner_id +} + +output "ec2_transit_gateway_association_default_route_table_id" { + description = "Identifier of the default association route table" + value = module.tgw.ec2_transit_gateway_association_default_route_table_id +} + +output "ec2_transit_gateway_propagation_default_route_table_id" { + description = "Identifier of the default propagation route table" + value = module.tgw.ec2_transit_gateway_propagation_default_route_table_id +} + +################################################################################ +# VPC Attachment +################################################################################ + +output "ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = module.tgw.ec2_transit_gateway_vpc_attachment_ids +} + +output "ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = module.tgw.ec2_transit_gateway_vpc_attachment +} + +################################################################################ +# Route Table / Routes +################################################################################ + +output "ec2_transit_gateway_route_table_id" { + description = "EC2 Transit Gateway Route Table identifier" + value = module.tgw.ec2_transit_gateway_route_table_id +} + +output "ec2_transit_gateway_route_table_default_association_route_table" { + description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_route_table_default_association_route_table +} + +output "ec2_transit_gateway_route_table_default_propagation_route_table" { + description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" + value = module.tgw.ec2_transit_gateway_route_table_default_propagation_route_table +} + +output "ec2_transit_gateway_route_ids" { + description = "List of EC2 Transit Gateway Route Table identifier combined with destination" + value = module.tgw.ec2_transit_gateway_route_ids +} + +output "ec2_transit_gateway_route_table_association_ids" { + description = "List of EC2 Transit Gateway Route Table Association identifiers" + value = module.tgw.ec2_transit_gateway_route_table_association_ids +} + +output "ec2_transit_gateway_route_table_association" { + description = "Map of EC2 Transit Gateway Route Table Association attributes" + value = module.tgw.ec2_transit_gateway_route_table_association +} + +output "ec2_transit_gateway_route_table_propagation_ids" { + description = "List of EC2 Transit Gateway Route Table Propagation identifiers" + value = module.tgw.ec2_transit_gateway_route_table_propagation_ids +} + +output "ec2_transit_gateway_route_table_propagation" { + description = "Map of EC2 Transit Gateway Route Table Propagation attributes" + value = module.tgw.ec2_transit_gateway_route_table_propagation +} + +################################################################################ +# Resource Access Manager +################################################################################ + +output "ram_resource_share_id" { + description = "The Amazon Resource Name (ARN) of the resource share" + value = module.tgw.ram_resource_share_id +} + +output "ram_principal_association_id" { + description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" + value = module.tgw.ram_principal_association_id +} diff --git a/modules/aws-transit-gateway/examples/multi-account/variables.tf b/modules/aws-transit-gateway/examples/multi-account/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-transit-gateway/examples/multi-account/versions.tf b/modules/aws-transit-gateway/examples/multi-account/versions.tf new file mode 100644 index 0000000..03533eb --- /dev/null +++ b/modules/aws-transit-gateway/examples/multi-account/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.4" + } + } +} diff --git a/modules/aws-transit-gateway/main.tf b/modules/aws-transit-gateway/main.tf new file mode 100644 index 0000000..a199cd7 --- /dev/null +++ b/modules/aws-transit-gateway/main.tf @@ -0,0 +1,250 @@ +locals { + # List of maps with key and route values + vpc_attachments_with_routes = chunklist(flatten([ + for k, v in var.vpc_attachments : setproduct([{ key = k }], v.tgw_routes) if can(v.tgw_routes) + ]), 2) + + tgw_default_route_table_tags_merged = merge( + var.tags, + { Name = var.name }, + var.tgw_default_route_table_tags, + ) + # List of cidr + vpc_route_table_destination_cidrs = flatten([ + for k, v in var.vpc_attachments : [ + for cidr in try(v.vpc_cidrs, []) : { + cidr = cidr + } + ] + ]) + # List of route tables + vpc_route_table_id = flatten([ + for k, v in var.vpc_attachments : [ + for rtb_id in try(v.vpc_route_table_ids, []) : { + rtb_id = rtb_id + } + ] + ]) + #List of maps of subnet route information. + vpc_routes = flatten([ + for rtb_id in local.vpc_route_table_id : [ + for cidr in local.vpc_route_table_destination_cidrs : { + route_cidr = cidr + route_rtb_id = rtb_id + } + ] + ]) +} + +################################################################################ +# Transit Gateway +################################################################################ + +resource "aws_ec2_transit_gateway" "this" { + count = var.create_tgw ? 1 : 0 + + description = coalesce(var.description, var.name) + amazon_side_asn = var.amazon_side_asn + default_route_table_association = var.enable_default_route_table_association ? "enable" : "disable" + default_route_table_propagation = var.enable_default_route_table_propagation ? "enable" : "disable" + auto_accept_shared_attachments = var.enable_auto_accept_shared_attachments ? "enable" : "disable" + multicast_support = var.enable_multicast_support ? "enable" : "disable" + vpn_ecmp_support = var.enable_vpn_ecmp_support ? "enable" : "disable" + dns_support = var.enable_dns_support ? "enable" : "disable" + transit_gateway_cidr_blocks = var.transit_gateway_cidr_blocks + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge( + var.tags, + { Name = var.name }, + var.tgw_tags, + ) +} + +resource "aws_ec2_tag" "this" { + for_each = { for k, v in local.tgw_default_route_table_tags_merged : k => v if var.create_tgw && var.enable_default_route_table_association } + + resource_id = aws_ec2_transit_gateway.this[0].association_default_route_table_id + key = each.key + value = each.value +} + +################################################################################ +# Peer Attachment +################################################################################ + +# transit GW Peer +data "aws_ec2_transit_gateway_peering_attachment" "this" { + count = var.peer_attachment != {} ? 1 : 0 + provider = aws.peer_accepter + depends_on = [aws_ec2_transit_gateway_peering_attachment.this] + + filter { + name = "transit-gateway-id" + values = [try(var.peer_attachment.peer_transit_gateway_id, "id")] + } + # If you use multiple tgw peering you must fix this values. + filter { + name = "state" + values = ["pendingAcceptance", "available"] + } +} + +resource "aws_ec2_transit_gateway_peering_attachment" "this" { + count = var.peer_attachment != {} ? 1 : 0 + + peer_account_id = var.peer_attachment.peer_account_id + peer_region = var.peer_attachment.peer_region + peer_transit_gateway_id = var.peer_attachment.peer_transit_gateway_id + transit_gateway_id = var.peer_attachment.tgw_id + + tags = merge( + var.tags, + { Name = var.name }, + var.tgw_peer_attachment_tags, + ) +} + +resource "aws_ec2_transit_gateway_peering_attachment_accepter" "this" { + count = var.peer_attachment != {} ? 1 : 0 + provider = aws.peer_accepter + transit_gateway_attachment_id = data.aws_ec2_transit_gateway_peering_attachment.this[0].id +} + +################################################################################ +# VPC Attachment +################################################################################ + +resource "aws_ec2_transit_gateway_vpc_attachment" "this" { + for_each = var.vpc_attachments + + transit_gateway_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].id : each.value.tgw_id + vpc_id = each.value.vpc_id + subnet_ids = each.value.subnet_ids + + dns_support = try(each.value.dns_support, true) ? "enable" : "disable" + ipv6_support = try(each.value.ipv6_support, false) ? "enable" : "disable" + appliance_mode_support = try(each.value.appliance_mode_support, false) ? "enable" : "disable" + transit_gateway_default_route_table_association = try(each.value.transit_gateway_default_route_table_association, true) + transit_gateway_default_route_table_propagation = try(each.value.transit_gateway_default_route_table_propagation, true) + + tags = merge( + var.tags, + { Name = var.name }, + var.tgw_vpc_attachment_tags, + try(each.value.tags, {}), + ) +} + +################################################################################ +# Route Table / Routes +################################################################################ + +resource "aws_ec2_transit_gateway_route_table" "this" { + count = var.create_tgw ? 1 : 0 + + transit_gateway_id = aws_ec2_transit_gateway.this[0].id + + tags = merge( + var.tags, + var.tgw_route_table_tags, + ) +} + +resource "aws_ec2_transit_gateway_route" "this" { + count = length(local.vpc_attachments_with_routes) + + destination_cidr_block = local.vpc_attachments_with_routes[count.index][1].destination_cidr_block + blackhole = try(local.vpc_attachments_with_routes[count.index][1].blackhole, null) + + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : var.transit_gateway_route_table_id + transit_gateway_attachment_id = tobool(try(local.vpc_attachments_with_routes[count.index][1].blackhole, false)) == false ? aws_ec2_transit_gateway_vpc_attachment.this[local.vpc_attachments_with_routes[count.index][0].key].id : null +} + +resource "aws_route" "this" { + for_each = { for routes in local.vpc_routes : "${routes.route_rtb_id.rtb_id}.${routes.route_cidr.cidr}" => routes } + + route_table_id = each.value.route_rtb_id.rtb_id + destination_cidr_block = each.value.route_cidr.cidr + transit_gateway_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].id : join(",", [for k, v in var.vpc_attachments : v.tgw_id]) +} + +resource "aws_ec2_transit_gateway_route_table_association" "this" { + for_each = { + for k, v in var.vpc_attachments : k => v if var.create_tgw && try(v.transit_gateway_default_route_table_association, true) != true + } + + # Create association if it was not set already by aws_ec2_transit_gateway_vpc_attachment resource + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.this[each.key].id + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : try(each.value.transit_gateway_route_table_id, var.transit_gateway_route_table_id) +} + +resource "aws_ec2_transit_gateway_route_table_propagation" "this" { + for_each = { + for k, v in var.vpc_attachments : k => v if var.create_tgw && try(v.transit_gateway_default_route_table_propagation, true) != true + } + + # Create association if it was not set already by aws_ec2_transit_gateway_vpc_attachment resource + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.this[each.key].id + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : try(each.value.transit_gateway_route_table_id, var.transit_gateway_route_table_id) +} + +## Peer requester & Peer accepter static routes +resource "aws_ec2_transit_gateway_route" "requester" { + count = var.peer_attachment == {} ? 0 : length(var.peer_attachment.peer_requester_routes) + + destination_cidr_block = var.peer_attachment.peer_requester_routes[count.index] + transit_gateway_attachment_id = data.aws_ec2_transit_gateway_peering_attachment.this[0].id + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].association_default_route_table_id : var.transit_gateway_route_table_id_requester +} + +resource "aws_ec2_transit_gateway_route" "accepter" { + count = var.peer_attachment == {} ? 0 : length(var.peer_attachment.peer_accepter_routes) + + provider = aws.peer_accepter + destination_cidr_block = var.peer_attachment.peer_accepter_routes[count.index] + transit_gateway_attachment_id = data.aws_ec2_transit_gateway_peering_attachment.this[0].id + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].association_default_route_table_id : var.transit_gateway_route_table_id_accepter +} + +################################################################################ +# Resource Access Manager +################################################################################ + +resource "aws_ram_resource_share" "this" { + count = var.create_tgw && var.share_tgw ? 1 : 0 + + name = coalesce(var.ram_name, var.name) + allow_external_principals = var.ram_allow_external_principals + + tags = merge( + var.tags, + { Name = coalesce(var.ram_name, var.name) }, + var.ram_tags, + ) +} + +resource "aws_ram_resource_association" "this" { + count = var.create_tgw && var.share_tgw ? 1 : 0 + + resource_arn = aws_ec2_transit_gateway.this[0].arn + resource_share_arn = aws_ram_resource_share.this[0].id +} + +resource "aws_ram_principal_association" "this" { + count = var.create_tgw && var.share_tgw ? length(var.ram_principals) : 0 + + principal = var.ram_principals[count.index] + resource_share_arn = aws_ram_resource_share.this[0].arn +} + +resource "aws_ram_resource_share_accepter" "this" { + count = !var.create_tgw && var.share_tgw ? 1 : 0 + + share_arn = var.ram_resource_share_arn +} diff --git a/modules/aws-transit-gateway/outputs.tf b/modules/aws-transit-gateway/outputs.tf new file mode 100644 index 0000000..8dcb8a5 --- /dev/null +++ b/modules/aws-transit-gateway/outputs.tf @@ -0,0 +1,100 @@ +################################################################################ +# Transit Gateway +################################################################################ + +output "ec2_transit_gateway_arn" { + description = "EC2 Transit Gateway Amazon Resource Name (ARN)" + value = try(aws_ec2_transit_gateway.this[0].arn, "") +} + +output "ec2_transit_gateway_id" { + description = "EC2 Transit Gateway identifier" + value = try(aws_ec2_transit_gateway.this[0].id, "") +} + +output "ec2_transit_gateway_owner_id" { + description = "Identifier of the AWS account that owns the EC2 Transit Gateway" + value = try(aws_ec2_transit_gateway.this[0].owner_id, "") +} + +output "ec2_transit_gateway_association_default_route_table_id" { + description = "Identifier of the default association route table" + value = try(aws_ec2_transit_gateway.this[0].association_default_route_table_id, "") +} + +output "ec2_transit_gateway_propagation_default_route_table_id" { + description = "Identifier of the default propagation route table" + value = try(aws_ec2_transit_gateway.this[0].propagation_default_route_table_id, "") +} + +################################################################################ +# VPC Attachment +################################################################################ + +output "ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = [for k, v in aws_ec2_transit_gateway_vpc_attachment.this : v.id] +} + +output "ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = aws_ec2_transit_gateway_vpc_attachment.this +} + +################################################################################ +# Route Table / Routes +################################################################################ + +output "ec2_transit_gateway_route_table_id" { + description = "EC2 Transit Gateway Route Table identifier" + value = try(aws_ec2_transit_gateway_route_table.this[0].id, "") +} + +output "ec2_transit_gateway_route_table_default_association_route_table" { + description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" + value = try(aws_ec2_transit_gateway_route_table.this[0].default_association_route_table, "") +} + +output "ec2_transit_gateway_route_table_default_propagation_route_table" { + description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" + value = try(aws_ec2_transit_gateway_route_table.this[0].default_propagation_route_table, "") +} + +output "ec2_transit_gateway_route_ids" { + description = "List of EC2 Transit Gateway Route Table identifier combined with destination" + value = aws_ec2_transit_gateway_route.this[*].id +} + +output "ec2_transit_gateway_route_table_association_ids" { + description = "List of EC2 Transit Gateway Route Table Association identifiers" + value = [for k, v in aws_ec2_transit_gateway_route_table_association.this : v.id] +} + +output "ec2_transit_gateway_route_table_association" { + description = "Map of EC2 Transit Gateway Route Table Association attributes" + value = aws_ec2_transit_gateway_route_table_association.this +} + +output "ec2_transit_gateway_route_table_propagation_ids" { + description = "List of EC2 Transit Gateway Route Table Propagation identifiers" + value = [for k, v in aws_ec2_transit_gateway_route_table_propagation.this : v.id] +} + +output "ec2_transit_gateway_route_table_propagation" { + description = "Map of EC2 Transit Gateway Route Table Propagation attributes" + value = aws_ec2_transit_gateway_route_table_propagation.this +} + +################################################################################ +# Resource Access Manager +################################################################################ + +output "ram_resource_share_id" { + description = "The Amazon Resource Name (ARN) of the resource share" + value = try(aws_ram_resource_share.this[0].id, "") +} + +output "ram_principal_association_id" { + description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" + value = try(aws_ram_principal_association.this[0].id, "") +} diff --git a/modules/aws-transit-gateway/variables.tf b/modules/aws-transit-gateway/variables.tf new file mode 100644 index 0000000..fca6828 --- /dev/null +++ b/modules/aws-transit-gateway/variables.tf @@ -0,0 +1,225 @@ +variable "name" { + description = "Name to be used on all the resources as identifier" + type = string + default = "" +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + + +################################################################################ +# Transit Gateway +################################################################################ + +variable "create_tgw" { + description = "Controls if TGW should be created (it affects almost all resources)" + type = bool + default = true +} + +variable "peer_tgw" { + description = "Controls if TGW Peer should be created (it affects almost all resources)" + type = bool + default = false +} + +variable "tgw_accepter" { + description = "Controls TGW Peer Accepter if TGW Peer should be created (it affects almost all resources)" + type = bool + default = false +} + +variable "tgw_id" { + description = "The ID of the local TGW for peerings" + type = string + default = "" +} + +variable "tgw_peering_attachment_id" { + description = "The ID of the TGW attachment for peerings" + type = string + default = "" +} + +variable "description" { + description = "Description of the EC2 Transit Gateway" + type = string + default = null +} + +variable "amazon_side_asn" { + description = "The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the TGW is created with the current default Amazon ASN." + type = string + default = null +} + +variable "enable_default_route_table_association" { + description = "Whether resource attachments are automatically associated with the default association route table" + type = bool + default = true +} + +variable "enable_default_route_table_propagation" { + description = "Whether resource attachments automatically propagate routes to the default propagation route table" + type = bool + default = true +} + +variable "enable_auto_accept_shared_attachments" { + description = "Whether resource attachment requests are automatically accepted" + type = bool + default = false +} + +variable "enable_vpn_ecmp_support" { + description = "Whether VPN Equal Cost Multipath Protocol support is enabled" + type = bool + default = true +} + +variable "enable_multicast_support" { + description = "Whether multicast support is enabled" + type = bool + default = false +} + +variable "enable_dns_support" { + description = "Should be true to enable DNS support in the TGW" + type = bool + default = true +} + +variable "transit_gateway_cidr_blocks" { + description = "One or more IPv4 or IPv6 CIDR blocks for the transit gateway. Must be a size /24 CIDR block or larger for IPv4, or a size /64 CIDR block or larger for IPv6" + type = list(string) + default = [] +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the transit gateway" + type = map(string) + default = {} +} + +variable "tgw_tags" { + description = "Additional tags for the TGW" + type = map(string) + default = {} +} + +variable "tgw_default_route_table_tags" { + description = "Additional tags for the Default TGW route table" + type = map(string) + default = {} +} + +################################################################################ +# VPC Attachment +################################################################################ + +variable "vpc_attachments" { + description = "Maps of maps of VPC details to attach to TGW. Type 'any' to disable type validation by Terraform." + type = any + default = {} +} + +variable "tgw_vpc_attachment_tags" { + description = "Additional tags for VPC attachments" + type = map(string) + default = {} +} + + +################################################################################ +# Peer Attachment +################################################################################ + +variable "peer_attachment" { + description = "Maps of maps of Peer details to attach to TGW. Type 'any' to disable type validation by Terraform." + type = any + default = {} +} + +variable "peer_attachment_id" { + description = "(Required) The ID of the EC2 Transit Gateway Peering Attachment to manage." + type = string + default = "" +} + +variable "tgw_peer_attachment_tags" { + description = "Additional tags for Peer attachments" + type = map(string) + default = {} +} + +################################################################################ +# Route Table / Routes +################################################################################ + +variable "transit_gateway_route_table_id" { + description = "Identifier of EC2 Transit Gateway Route Table to use with the Target Gateway when reusing it between multiple TGWs" + type = string + default = null +} + +variable "tgw_route_table_tags" { + description = "Additional tags for the TGW route table" + type = map(string) + default = {} +} + +variable "transit_gateway_route_table_id_requester" { + description = "Identifier of EC2 Transit Gateway Route Table to use with the Target Gateway when reusing it between multiple TGWs" + type = string + default = null +} + +variable "transit_gateway_route_table_id_accepter" { + description = "Identifier of EC2 Transit Gateway Route Table to use with the Target Gateway when reusing it between multiple TGWs" + type = string + default = null +} + +################################################################################ +# Resource Access Manager +################################################################################ + +variable "share_tgw" { + description = "Whether to share your transit gateway with other accounts" + type = bool + default = true +} + +variable "ram_name" { + description = "The name of the resource share of TGW" + type = string + default = "" +} + +variable "ram_allow_external_principals" { + description = "Indicates whether principals outside your organization can be associated with a resource share." + type = bool + default = false +} + +variable "ram_principals" { + description = "A list of principals to share TGW with. Possible values are an AWS account ID, an AWS Organizations Organization ARN, or an AWS Organizations Organization Unit ARN" + type = list(string) + default = [] +} + +variable "ram_resource_share_arn" { + description = "ARN of RAM resource share" + type = string + default = "" +} + +variable "ram_tags" { + description = "Additional tags for the RAM" + type = map(string) + default = {} +} diff --git a/modules/aws-transit-gateway/versions.tf b/modules/aws-transit-gateway/versions.tf new file mode 100644 index 0000000..03533eb --- /dev/null +++ b/modules/aws-transit-gateway/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.4" + } + } +} diff --git a/modules/aws-vpc-peering-accepter/README.md b/modules/aws-vpc-peering-accepter/README.md new file mode 100644 index 0000000..568c72d --- /dev/null +++ b/modules/aws-vpc-peering-accepter/README.md @@ -0,0 +1,117 @@ +# vpc-peering-accepter + +This module accepts VPC Peering request. + +## Usage + +This module has three functions: + +- Accepts VPC peering requests and creates routes that indicate VPC-peering. +- Accepts multiple VPC Peering requests without route propagation. +- Accepts multiple VPC Peering requests with route propagation. + +## Accept peering request with route propagation + +```hcl +module "peering-accepter" { + source = "../vpc-peering-requester" + + vpc_peer_id = dependency.vpc-requester.outputs.vpc_peering_id + requester_vpc_cidr_block = dependency.vpc-requester.outputs.requester_vpc_cidr_block + + #Create route in database route table requester_vpc_cidr_block => vpc-peering-id + accepter_database_route_table_ids = ["rtb-1","rtb-2"] + +} +``` + +If you want to use this module with the vpc-peering-accepter module, set the `accepter_private_route_table_ids` variables. Because of the dependency cycle, the acceptor takes all of the variables from the requester. + +`accepter_private_route_table_ids` + +## Accept multiple peering requests without route propagation + +```hcl +module "peering-accepter" { + source = "../vpc-peering-requester" + + vpc_peering_ids = dependency.vpc-requester.outputs.vpc_peering_id + requester_vpc_cidr_block = dependency.vpc-requester.outputs.requester_vpc_cidr_block + + #Create route in database route table requester_vpc_cidr_block => vpc-peering-id + accepter_database_route_table_ids = ["rtb-1","rtb-2"] + +} +``` + +## Accept multiple peering requests with route propagation + +```hcl +module "peering-accepter" { + source = "../vpc-peering-requester" + + vpc_peer_ids = dependency.vpc-requester.outputs.vpc_peer_ids + + #Create route in specified tables: requester_cidr => peer_id + accepter_private_route_table_ids = ["rtb-1","rtb-2", "rtb-3"] + requester_peer_info = [ + { + requester_cidr = "192.168.240.0/21" + peer_id = "pcx-03d3ebd458ebae90f" + }, + { + requester_cidr = "192.168.240.0/21" + peer_id = "pcx-0016cf5631d7a80df" + }, + ] +} +``` + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route.accepter_private_route_table_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.accepter_public_route_table_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.accepter_table_multiple_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_vpc_peering_connection_accepter.batch_peer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_peering_connection_accepter) | resource | +| [aws_vpc_peering_connection_accepter.peer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_peering_connection_accepter) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [accepter\_all\_private\_route\_table\_ids](#input\_accepter\_all\_private\_route\_table\_ids) | All private route table IDs of accepter VPC route tables | `list(string)` | `[]` | no | +| [accepter\_private\_route\_table\_ids](#input\_accepter\_private\_route\_table\_ids) | The eks route table IDs of accepter VPC route tables | `list(string)` | `[]` | no | +| [accepter\_public\_route\_table\_ids](#input\_accepter\_public\_route\_table\_ids) | The eks route table IDs of accepter VPC route tables | `list(string)` | `[]` | no | +| [requester\_vpc\_cidr\_block](#input\_requester\_vpc\_cidr\_block) | The CIDR Block of the requester VPC | `string` | `""` | no | +| [tags](#input\_tags) | Tags for the peering connection | `map(string)` | `{}` | no | +| [vpc\_peer\_id](#input\_vpc\_peer\_id) | VPC peering connection ID | `string` | `""` | no | +| [vpc\_peer\_ids](#input\_vpc\_peer\_ids) | VPC peering connections IDs for batch operations | `list(string)` | `[]` | no | +| [requester\_peer\_info](#input\_requester\_peer\_info) | VPC peering connections IDs and cidr blocks for batch operatipn | `list(object({ requester_cidr = string, peer_id = string}))` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [accepter\_private\_route\_table\_ids](#output\_accepter\_private\_route\_table\_ids) | The eks route table IDs of accepter VPC route tables | +| [accepter\_public\_route\_table\_ids](#output\_accepter\_public\_route\_table\_ids) | The eks route table IDs of accepter VPC route tables | +| [requester\_vpc\_cidr\_block](#output\_requester\_vpc\_cidr\_block) | The CIDR Block of the requester VPC | +| [vpc\_peering\_id](#output\_vpc\_peering\_id) | ID of the vpc peering | +| [routes\_with\_requester\_peer\_info](#output\_routes\_with\_requester\_peer\_info) | Route table ids, requester cidr and peer id | + \ No newline at end of file diff --git a/modules/aws-vpc-peering-accepter/main.tf b/modules/aws-vpc-peering-accepter/main.tf new file mode 100644 index 0000000..964c5fd --- /dev/null +++ b/modules/aws-vpc-peering-accepter/main.tf @@ -0,0 +1,32 @@ + + +resource "aws_vpc_peering_connection_accepter" "peer" { + count = var.vpc_peer_id != "" ? 1 : 0 + vpc_peering_connection_id = var.vpc_peer_id + auto_accept = true + + tags = merge(var.tags, { + Name = var.name + Side = "Accepter" + }) + + accepter { + allow_remote_vpc_dns_resolution = true + } +} + + +resource "aws_vpc_peering_connection_accepter" "batch_peer" { + count = length(var.vpc_peer_ids) > 0 ? length(var.vpc_peer_ids) : 0 + vpc_peering_connection_id = var.vpc_peer_ids[count.index] + auto_accept = true + + tags = merge(var.tags, { + Name = var.name + Side = "Accepter" + }) + + accepter { + allow_remote_vpc_dns_resolution = true + } +} diff --git a/modules/aws-vpc-peering-accepter/outputs.tf b/modules/aws-vpc-peering-accepter/outputs.tf new file mode 100644 index 0000000..623da58 --- /dev/null +++ b/modules/aws-vpc-peering-accepter/outputs.tf @@ -0,0 +1,23 @@ +output "vpc_peering_id" { + description = "ID of the vpc peering" + value = var.vpc_peer_id +} + +output "requester_vpc_cidr_block" { + description = "The CIDR Block of the requester VPC" + value = var.requester_vpc_cidr_block +} + +output "accepter_private_route_table_ids" { + description = "The eks route table IDs of accepter VPC route tables" + value = var.accepter_private_route_table_ids +} + +output "accepter_public_route_table_ids" { + description = "The eks route table IDs of accepter VPC route tables" + value = var.accepter_public_route_table_ids +} + +output "routes_with_requester_peer_info" { + value = local.routes +} \ No newline at end of file diff --git a/modules/aws-vpc-peering-accepter/routes.tf b/modules/aws-vpc-peering-accepter/routes.tf new file mode 100644 index 0000000..dfdcca5 --- /dev/null +++ b/modules/aws-vpc-peering-accepter/routes.tf @@ -0,0 +1,42 @@ + +resource "aws_route" "accepter_private_route_table_route" { + count = length(var.accepter_private_route_table_ids) > 0 && length(var.requester_peer_info) == 0 ? length(var.accepter_private_route_table_ids) : 0 + vpc_peering_connection_id = var.vpc_peer_id + route_table_id = var.accepter_private_route_table_ids[count.index] + destination_cidr_block = var.requester_vpc_cidr_block + +} + + +resource "aws_route" "accepter_public_route_table_route" { + count = length(var.accepter_public_route_table_ids) > 0 && length(var.requester_peer_info) == 0 ? length(var.accepter_public_route_table_ids) : 0 + vpc_peering_connection_id = var.vpc_peer_id + route_table_id = var.accepter_public_route_table_ids[count.index] + destination_cidr_block = var.requester_vpc_cidr_block + +} + +# Multiple peerin acceptance with multpile route propagation. +locals { + + route_tables = length(var.accepter_private_route_table_ids) > 0 ? var.accepter_private_route_table_ids : var.accepter_public_route_table_ids + peer_detail = var.requester_peer_info + + routes = flatten([ + for table in local.route_tables : [ + for p in local.peer_detail : { + table_id = table + cidr = p.requester_cidr + peer_id = p.peer_id + } + ] + ] + ) +} + +resource "aws_route" "accepter_table_multiple_route" { + for_each = { for routes in local.routes : "${routes.table_id}.${routes.peer_id}" => routes } + vpc_peering_connection_id = each.value.peer_id + route_table_id = each.value.table_id + destination_cidr_block = each.value.cidr +} diff --git a/modules/aws-vpc-peering-accepter/variables.tf b/modules/aws-vpc-peering-accepter/variables.tf new file mode 100644 index 0000000..7310e17 --- /dev/null +++ b/modules/aws-vpc-peering-accepter/variables.tf @@ -0,0 +1,57 @@ +variable "name" { + description = "The Name of VPC Peering" + type = string + default = "" +} + +variable "vpc_peer_id" { + description = "VPC peering connection ID" + type = string + default = "" +} + +variable "vpc_peer_ids" { + description = "VPC peering connections IDs for batch operations" + type = list(string) + default = [] + +} + +variable "accepter_all_private_route_table_ids" { + description = "All private route table IDs of accepter VPC route tables" + type = list(string) + default = [] +} + +variable "requester_vpc_cidr_block" { + description = "The CIDR Block of the requester VPC" + type = string + default = "" +} + +variable "accepter_private_route_table_ids" { + description = "The eks route table IDs of accepter VPC route tables" + type = list(string) + default = [] +} + +variable "accepter_public_route_table_ids" { + description = "The eks route table IDs of accepter VPC route tables" + type = list(string) + default = [] +} + +variable "tags" { + description = "Tags for the peering connection" + default = {} + type = map(string) +} + +variable "requester_peer_info" { + description = "Tags for the peering connection" + default = [] + type = list(object({ + requester_cidr = string + peer_id = string + })) +} diff --git a/modules/aws-vpc-peering-requester/README.md b/modules/aws-vpc-peering-requester/README.md new file mode 100644 index 0000000..7bcad39 --- /dev/null +++ b/modules/aws-vpc-peering-requester/README.md @@ -0,0 +1,80 @@ +# vpc-peering-requester + +This module creates VPC Peering request. + +## Usage + +This module has two functions: it creates VPC peering requests and it creates routes which indicate VPC-peering. + +```hcl +module "peering-requester" { + source = "../vpc-peering-requester" + + peer_owner_account_id = "" + accepter_vpc_id = "" + accepter_vpc_cidr_block = "" + + requester_vpc_cidr_block = "" + requester_vpc_id = "" + + #Create route in private route table accepter_vpc_cidr_block => vpc-peering-id + requester_private_route_table_ids = ["rtb-1","rtb-2"] + + peer_region = " +} +``` + +If you want to use this module with the vpc-peering-accepter module, set the `accepter_private_route_table_ids` variables which you can have them via an explicit dependency from peering requester module outputs. Because of the dependency cycle, the acceptor takes all of the variables from the requester. + +`accepter_private_route_table_ids` +`requester_private_route_table_ids` + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route.requester_private_route_table_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.requester_public_route_table_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_vpc_peering_connection.peer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_peering_connection) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [accepter\_private\_route\_table\_ids](#input\_accepter\_private\_route\_table\_ids) | Private route table IDs of accepter VPC route tables | `list(string)` | `[]` | no | +| [accepter\_public\_route\_table\_ids](#input\_accepter\_public\_route\_table\_ids) | Public route table IDs of accepter VPC route tables | `list(string)` | `[]` | no | +| [accepter\_vpc\_cidr\_block](#input\_accepter\_vpc\_cidr\_block) | The CIDR Block of the accepter VPC | `string` | `""` | no | +| [accepter\_vpc\_id](#input\_accepter\_vpc\_id) | The ID of the VPC with which you are creating the VPC Peering Connection | `string` | `""` | no | +| [peer\_owner\_account\_id](#input\_peer\_owner\_account\_id) | The account ID of the peer owner | `string` | `""` | no | +| [peer\_region](#input\_peer\_region) | The region of the accepter side | `string` | `""` | no | +| [requester\_private\_route\_table\_ids](#input\_requester\_private\_route\_table\_ids) | Private route table IDs of requester VPC route tables | `list(string)` | `[]` | no | +| [requester\_public\_route\_table\_ids](#input\_requester\_public\_route\_table\_ids) | Public route table IDs of requester VPC route tables | `list(string)` | `[]` | no | +| [requester\_vpc\_cidr\_block](#input\_requester\_vpc\_cidr\_block) | The CIDR Block of the requester VPC | `string` | `""` | no | +| [requester\_vpc\_id](#input\_requester\_vpc\_id) | The ID of the requester VPC | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [accepter\_private\_route\_table\_ids](#output\_accepter\_private\_route\_table\_ids) | Private route table IDs of the accepter vpc | +| [accepter\_public\_route\_table\_ids](#output\_accepter\_public\_route\_table\_ids) | Public route table IDs of the accepter vpc | +| [accepter\_vpc\_id](#output\_accepter\_vpc\_id) | ID of the accepter vpc | +| [requester\_vpc\_cidr\_block](#output\_requester\_vpc\_cidr\_block) | VPC CIDR block of the requester vpc | +| [requester\_vpc\_id](#output\_requester\_vpc\_id) | ID of the requester vpc | +| [vpc\_peering\_id](#output\_vpc\_peering\_id) | ID of the vpc peering | + \ No newline at end of file diff --git a/modules/aws-vpc-peering-requester/main.tf b/modules/aws-vpc-peering-requester/main.tf new file mode 100644 index 0000000..2dbf048 --- /dev/null +++ b/modules/aws-vpc-peering-requester/main.tf @@ -0,0 +1,109 @@ +resource "aws_vpc_peering_connection" "peer" { + count = var.create_cross_account_peering ? 0 : 1 + + peer_owner_id = var.peer_owner_account_id #requester account id + peer_vpc_id = var.accepter_vpc_id #accepter vpc id + vpc_id = var.requester_vpc_id #requester vpc id + peer_region = var.peer_region + auto_accept = false + + tags = { + Name = var.name + } +} + + + + + +### Cross account requester resources + + +# Upstream account values +# data "aws_ssm_parameter" "requester_private_route_table_ids" { +# count = var.fetch_from_ssm && var.create_cross_account_peering ? 1 : 0 +# provider = aws.shared_infra +# name = var.requester_private_route_table_ids_key +# } + +# data "aws_ssm_parameter" "requester_public_route_table_ids" { +# count = var.fetch_from_ssm && var.create_cross_account_peering ? 1 : 0 +# provider = aws.shared_infra +# name = var.requester_public_route_table_ids_key +# } + +# data "aws_ssm_parameter" "requester_vpc_cidr_block" { +# count = var.fetch_from_ssm && var.create_cross_account_peering ? 1 : 0 +# provider = aws.shared_infra +# name = var.requester_vpc_cidr_block_key +# } + +# data "aws_ssm_parameter" "requester_vpc_id" { +# count = var.fetch_from_ssm && var.create_cross_account_peering ? 1 : 0 +# provider = aws.shared_infra +# name = var.requester_vpc_id_key +# } + + + + +# VPC Peering Connection Options + +resource "aws_vpc_peering_connection" "cross_peer" { + provider = aws.shared_infra + count = var.create_cross_account_peering ? 1 : 0 + + vpc_id = var.requester_vpc_id #requester vpc id + peer_vpc_id = var.accepter_vpc_id #accepter vpc id + peer_owner_id = var.peer_owner_account_id #accepter account id + peer_region = var.peer_region + auto_accept = false + + tags = { + Name = var.name + } + +} + +# Accepter's side of the connection. +resource "aws_vpc_peering_connection_accepter" "cross_accepter" { + count = var.create_cross_account_peering ? 1 : 0 + + vpc_peering_connection_id = aws_vpc_peering_connection.cross_peer[0].id + auto_accept = true + + tags = { + Name = var.name + Side = "Accepter" + } +} + + + + +resource "aws_vpc_peering_connection_options" "cross_peer" { + count = var.create_cross_account_peering ? 1 : 0 + + provider = aws.shared_infra + + # As options can't be set until the connection has been accepted + # create an explicit dependency on the accepter. + vpc_peering_connection_id = aws_vpc_peering_connection_accepter.cross_accepter[0].id + + requester { + allow_remote_vpc_dns_resolution = true + } +} + +resource "aws_vpc_peering_connection_options" "cross_accepter" { + count = var.create_cross_account_peering ? 1 : 0 + + + vpc_peering_connection_id = aws_vpc_peering_connection_accepter.cross_accepter[0].id + + accepter { + allow_remote_vpc_dns_resolution = true + } +} + + diff --git a/modules/aws-vpc-peering-requester/outputs.tf b/modules/aws-vpc-peering-requester/outputs.tf new file mode 100644 index 0000000..4267077 --- /dev/null +++ b/modules/aws-vpc-peering-requester/outputs.tf @@ -0,0 +1,30 @@ +output "vpc_peering_id" { + description = "ID of the vpc peering" + value = try(aws_vpc_peering_connection.peer[0].id, null) + +} + +output "requester_vpc_id" { + description = "ID of the requester vpc" + value = var.requester_vpc_id +} + +output "accepter_vpc_id" { + description = "ID of the accepter vpc" + value = var.accepter_vpc_id +} + +output "requester_vpc_cidr_block" { + description = "VPC CIDR block of the requester vpc" + value = var.requester_vpc_cidr_block +} + +output "accepter_private_route_table_ids" { + description = "Private route table IDs of the accepter vpc" + value = var.accepter_private_route_table_ids +} + +output "accepter_public_route_table_ids" { + description = "Public route table IDs of the accepter vpc" + value = var.accepter_public_route_table_ids +} \ No newline at end of file diff --git a/modules/aws-vpc-peering-requester/routes.tf b/modules/aws-vpc-peering-requester/routes.tf new file mode 100644 index 0000000..8e1ab68 --- /dev/null +++ b/modules/aws-vpc-peering-requester/routes.tf @@ -0,0 +1,67 @@ +resource "aws_route" "requester_private_route_table_route" { + count = var.create_cross_account_peering ? 0 : 1 + vpc_peering_connection_id = aws_vpc_peering_connection.peer[0].id + route_table_id = var.requester_private_route_table_ids[count.index] + destination_cidr_block = var.accepter_vpc_cidr_block + +} + + +resource "aws_route" "requester_public_route_table_route" { + count = var.create_cross_account_peering ? 0 : 1 + vpc_peering_connection_id = aws_vpc_peering_connection.peer[0].id + route_table_id = var.requester_public_route_table_ids[count.index] + destination_cidr_block = var.accepter_vpc_cidr_block + +} + + + +# Cross Account Route Resources +# Upstream (Requester Account) Routes +locals { + private_route_table_ids = var.create_cross_account_peering ? var.requester_private_route_table_ids : null + + public_route_table_ids = var.create_cross_account_peering ? var.requester_public_route_table_ids : null +} + + +resource "aws_route" "cross_account_requester_private_route_table_route" { + provider = aws.shared_infra + count = var.create_cross_account_peering ? length(local.private_route_table_ids) : 0 + vpc_peering_connection_id = aws_vpc_peering_connection.cross_peer[0].id + route_table_id = local.private_route_table_ids[count.index] + destination_cidr_block = var.accepter_vpc_cidr_block + +} + + +resource "aws_route" "cross_account_requester_public_route_table_route" { + provider = aws.shared_infra + count = var.create_cross_account_peering ? length(local.public_route_table_ids) : 0 + vpc_peering_connection_id = aws_vpc_peering_connection.cross_peer[0].id + route_table_id = local.public_route_table_ids[count.index] + destination_cidr_block = var.accepter_vpc_cidr_block + +} + +# # Downstream (Accepter Account) Routes +resource "aws_route" "cross_account_accepter_private_route_table_route" { + count = length(var.accepter_private_route_table_ids) > 0 && var.create_cross_account_peering ? length(var.accepter_private_route_table_ids) : 0 + vpc_peering_connection_id = aws_vpc_peering_connection.cross_peer[0].id + route_table_id = var.accepter_private_route_table_ids[count.index] + destination_cidr_block = var.requester_vpc_cidr_block + +} + + +resource "aws_route" "cross_account_accepter_public_route_table_route" { + count = length(var.accepter_public_route_table_ids) > 0 && var.create_cross_account_peering ? length(var.accepter_public_route_table_ids) : 0 + vpc_peering_connection_id = aws_vpc_peering_connection.cross_peer[0].id + route_table_id = var.accepter_public_route_table_ids[count.index] + destination_cidr_block = var.requester_vpc_cidr_block + +} + + + diff --git a/modules/aws-vpc-peering-requester/variables.tf b/modules/aws-vpc-peering-requester/variables.tf new file mode 100644 index 0000000..557dccb --- /dev/null +++ b/modules/aws-vpc-peering-requester/variables.tf @@ -0,0 +1,123 @@ +variable "name" { + description = "The Name of VPC Peering" + type = string + default = "" +} + +variable "requester_vpc_id" { + description = "The ID of the requester VPC" + type = string + default = "" +} + +variable "requester_vpc_cidr_block" { + description = "The CIDR Block of the requester VPC" + type = string + default = "" +} + +variable "requester_private_route_table_ids" { + description = "Private route table IDs of requester VPC route tables" + type = list(string) + default = [] +} + +variable "requester_public_route_table_ids" { + description = "Public route table IDs of requester VPC route tables" + type = list(string) + default = [] +} +variable "accepter_vpc_id" { + description = "The ID of the VPC with which you are creating the VPC Peering Connection" + type = string + default = "" +} + +variable "accepter_vpc_cidr_block" { + description = "The CIDR Block of the accepter VPC" + type = string + default = "" +} + +variable "accepter_private_route_table_ids" { + description = "Private route table IDs of accepter VPC route tables" + type = list(string) + default = [] +} + + +variable "accepter_public_route_table_ids" { + description = "Public route table IDs of accepter VPC route tables" + type = list(string) + default = [] +} + + +variable "peer_owner_account_id" { + description = "The account ID of the peer owner" + type = string + default = "" +} + + +variable "peer_region" { + description = "The region of the accepter side" + type = string + default = "" +} + + +variable "enable_cross_account_peering" { + description = "Control logic to create cross account peering not cross region" + type = string + default = "" +} + +variable "create_peering" { + description = "Regional VPC Peering creation control logic" + type = bool + default = true +} + + + + + +# Cross account variables + +variable "create_cross_account_peering" { + description = "Cross account VPC Peering creation control variable" + type = bool + default = false +} + + +# For SSM Data sources +variable "requester_private_route_table_ids_key" { + description = "Cross account requester private route table ids SSM Key" + type = string + default = "" +} + +variable "requester_public_route_table_ids_key" { + description = "Cross account requester private route table ids SSM Key" + type = string + default = "" +} + +variable "requester_vpc_cidr_block_key" { + description = "Cross account requester vpc cidr block SSM Key" + type = string + default = "" +} + +variable "requester_vpc_id_key" { + description = "Cross account requester vpc id SSM Key" + type = string + default = "" +} +variable "fetch_from_ssm" { + description = "Cross account variables fetching from SSM enablement" + type = bool + default = true +} \ No newline at end of file diff --git a/modules/aws-vpc/.pre-commit-config.yaml b/modules/aws-vpc/.pre-commit-config.yaml new file mode 100644 index 0000000..27d478d --- /dev/null +++ b/modules/aws-vpc/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.72.1 + hooks: + - id: terraform_fmt + - id: terraform_validate + - id: terraform_docs + args: + - '--args=--lockfile=false' + - id: terraform_tflint + args: + - '--args=--only=terraform_deprecated_interpolation' + - '--args=--only=terraform_deprecated_index' + - '--args=--only=terraform_unused_declarations' + - '--args=--only=terraform_comment_syntax' + - '--args=--only=terraform_documented_outputs' + - '--args=--only=terraform_documented_variables' + - '--args=--only=terraform_typed_variables' + - '--args=--only=terraform_module_pinned_source' + - '--args=--only=terraform_naming_convention' + - '--args=--only=terraform_required_version' + - '--args=--only=terraform_required_providers' + - '--args=--only=terraform_standard_module_structure' + - '--args=--only=terraform_workspace_remote' + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer diff --git a/modules/aws-vpc/README.md b/modules/aws-vpc/README.md new file mode 100644 index 0000000..73abb69 --- /dev/null +++ b/modules/aws-vpc/README.md @@ -0,0 +1,481 @@ +# AWS VPC Terraform module + +Terraform module which creates VPC resources on AWS. +## Usage + +```hcl +module "vpc" { + + name = "my-vpc" + cidr = "10.0.0.0/16" + public_subnets = ["10.0.4.0/24", "10.0.5.0/24"] + enable_nat_gateway = true + + private_subnets = [ + { + name = "eks-subnet-1" + cidr_block = "10.100.0.0/22" + availability_zone = "eu-west-1a" + }, + { + name = "eks-subnet-2" + cidr_block = "10.100.4.0/22" + availability_zone = "eu-west-1b" + }, + { + name = "database-subnet-1" + cidr_block = "10.100.8.0/24" + availability_zone = "eu-west-1a" + }, + { + name = "database-subnet-2" + cidr_block = "10.100.9.0/24" + availability_zone = "eu-west-1b" + }, + { + name = "msk-subnet-1" + cidr_block = "10.100.10.0/24" + availability_zone = "eu-west-1a" + }, + { + name = "msk-subnet-2" + cidr_block = "10.100.11.0/24" + availability_zone = "eu-west-1b" + }, + { + name = "elasticache-subnet-1" + cidr_block = "10.100.12.0/24" + availability_zone = "eu-west-1a" + }, + { + name = "elasticache-subnet-2" + cidr_block = "10.100.13.0/24" + availability_zone = "eu-west-1b" + }, + { + name = "mq-subnet-1" + cidr_block = "10.100.16.0/24" + availability_zone = "eu-west-1a" + }, + { + name = "mq-subnet-2" + cidr_block = "10.100.17.0/24" + availability_zone = "eu-west-1b" + } + ] + + + +} +``` + +## External NAT Gateway IPs + +By default this module will provision new Elastic IPs for the VPC's NAT Gateways. +This means that when creating a new VPC, new IPs are allocated, and when that VPC is destroyed those IPs are released. +Sometimes it is handy to keep the same IPs even after the VPC is destroyed and re-created. +To that end, it is possible to assign existing IPs to the NAT Gateways. +This prevents the destruction of the VPC from releasing those IPs, while making it possible that a re-created VPC uses the same IPs. + +To achieve this, allocate the IPs outside the VPC module declaration. + +```hcl +resource "aws_eip" "nat" { + count = 3 + + vpc = true +} +``` + +Then, pass the allocated IPs as a parameter to this module. + +```hcl +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + # The rest of arguments are omitted for brevity + + enable_nat_gateway = true + single_nat_gateway = false + reuse_nat_ips = true # <= Skip creation of EIPs for the NAT Gateways + external_nat_ip_ids = "${aws_eip.nat.*.id}" # <= IPs specified here as input to the module +} +``` + +Note that in the example we allocate 3 IPs because we will be provisioning 3 NAT Gateways (due to `single_nat_gateway = false` and having 3 subnets). +If, on the other hand, `single_nat_gateway = true`, then `aws_eip.nat` would only need to allocate 1 IP. +Passing the IPs into the module is done by setting two variables `reuse_nat_ips = true` and `external_nat_ip_ids = "${aws_eip.nat.*.id}"`. + +## NAT Gateway Scenarios + +This module supports three scenarios for creating NAT gateways. Each will be explained in further detail in the corresponding sections. + +- One NAT Gateway per subnet (default behavior) + - `enable_nat_gateway = true` + - `single_nat_gateway = false` + - `one_nat_gateway_per_az = false` +- Single NAT Gateway + - `enable_nat_gateway = true` + - `single_nat_gateway = true` + - `one_nat_gateway_per_az = false` +- One NAT Gateway per availability zone + - `enable_nat_gateway = true` + - `single_nat_gateway = false` + - `one_nat_gateway_per_az = true` + +If both `single_nat_gateway` and `one_nat_gateway_per_az` are set to `true`, then `single_nat_gateway` takes precedence. + +### One NAT Gateway per subnet (default) + +By default, the module will determine the number of NAT Gateways to create based on the the `max()` of the private subnet lists (`database_subnets`, `cache_subnets`, `private_subnets`, and `eks_subnets`). The module **does not** take into account the number of `intra_subnets`, since the latter are designed to have no Internet access via NAT Gateway. For example, if your configuration looks like the following: + +```hcl +database_subnets = ["10.0.21.0/24", "10.0.22.0/24"] +cache_subnets = ["10.0.31.0/24", "10.0.32.0/24"] +private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"] +eks_subnets = ["10.0.41.0/24", "10.0.42.0/24"] +intra_subnets = ["10.0.51.0/24", "10.0.52.0/24", "10.0.53.0/24"] +mq_subnets = ["10.0.25.0/24", "10.0.26.0/24"] +``` + +Then `5` NAT Gateways will be created since `5` private subnet CIDR blocks were specified. + +### Single NAT Gateway + +If `single_nat_gateway = true`, then all private subnets will route their Internet traffic through this single NAT gateway. The NAT gateway will be placed in the first public subnet in your `public_subnets` block. + +### One NAT Gateway per availability zone + +If `one_nat_gateway_per_az = true` and `single_nat_gateway = false`, then the module will place one NAT gateway in each availability zone you specify in `var.azs`. There are some requirements around using this feature flag: + +- The variable `var.azs` **must** be specified. +- The number of public subnet CIDR blocks specified in `public_subnets` **must** be greater than or equal to the number of availability zones specified in `var.azs`. This is to ensure that each NAT Gateway has a dedicated public subnet to deploy to. + +## "private" versus "intra" subnets + +By default, if NAT Gateways are enabled, private subnets will be configured with routes for Internet traffic that point at the NAT Gateways configured by use of the above options. + +If you need private subnets that should have no Internet routing (in the sense of [RFC1918 Category 1 subnets](https://tools.ietf.org/html/rfc1918)), `intra_subnets` should be specified. An example use case is configuration of AWS Lambda functions within a VPC, where AWS Lambda functions only need to pass traffic to internal resources or VPC endpoints for AWS services. + +Since AWS Lambda functions allocate Elastic Network Interfaces in proportion to the traffic received ([read more](https://docs.aws.amazon.com/lambda/latest/dg/vpc.html)), it can be useful to allocate a large private subnet for such allocations, while keeping the traffic they generate entirely internal to the VPC. + +You can add additional tags with `intra_subnet_tags` as with other subnet types. + +## VPC Flow Log + +VPC Flow Log allows to capture IP traffic for a specific network interface (ENI), subnet, or entire VPC. This module supports enabling or disabling VPC Flow Logs for entire VPC. If you need to have VPC Flow Logs for subnet or ENI, you have to manage it outside of this module with [aws_flow_log resource](https://www.terraform.io/docs/providers/aws/r/flow_log.html). + +### VPC Flow Log Examples + +By default `file_format` is `plain-text`. You can also specify `parquet` to have logs written in Apache Parquet format. + +``` +flow_log_file_format = "parquet" +``` + +### Permissions Boundary + +If your organization requires a permissions boundary to be attached to the VPC Flow Log role, make sure that you specify an ARN of the permissions boundary policy as `vpc_flow_log_permissions_boundary` argument. Read more about required [IAM policy for publishing flow logs](https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-cwl.html#flow-logs-iam). + +## Conditional creation + +Prior to Terraform 0.13, you were unable to specify `count` in a module block. If you wish to toggle the creation of the module's resources in an older (pre 0.13) version of Terraform, you can use the `create_vpc` argument. + +```hcl +# This VPC will not be created +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + create_vpc = false + # ... omitted +} +``` + +## Public access to RDS instances + +Sometimes it is handy to have public access to RDS instances (it is not recommended for production) by specifying these arguments: + +```hcl + create_database_subnet_group = true + create_database_subnet_route_table = true + create_database_internet_gateway_route = true + + enable_dns_hostnames = true + enable_dns_support = true +``` + +## Network Access Control Lists (ACL or NACL) + +This module can manage network ACL and rules. Once VPC is created, AWS creates the default network ACL, which can be controlled using this module (`manage_default_network_acl = true`). + +Also, each type of subnet may have its own network ACL with custom rules per subnet. Eg, set `public_dedicated_network_acl = true` to use dedicated network ACL for the public subnets; set values of `public_inbound_acl_rules` and `public_outbound_acl_rules` to specify all the NACL rules you need to have on public subnets (see `variables.tf` for default values and structures). + +```hcl + #Available network acls + + public_dedicated_network_acl = true + private_dedicated_network_acl = true + cache_dedicated_network_acl = true + eks_dedicated_network_acl = true + ecs_dedicated_network_acl = true + database_dedicated_network_acl = true + mq_dedicated_network_acl = true + + + enable_dns_hostnames = true + enable_dns_support = true + + #ACL Rule example + + public_inbound_acl_rules = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0"} + ] + + + +``` +By default, all subnets are associated with the default network ACL. + +## Public access to eks cluster + +Sometimes it is handy to have public access to eks clusters (for example if you need to access it by Kinesis - VPC endpoint for Kinesis is not yet supported by eks) by specifying these arguments: + +```hcl + enable_public_eks = true # <= By default eks subnets will be associated with the private route table +``` + +## Transit Gateway (TGW) integration + +It is possible to integrate this VPC module with [terraform-aws-transit-gateway module](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway) which handles the creation of TGW resources and VPC attachments. See [complete example there](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/complete). + +## Examples + +- [Simple VPC](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/simple-vpc) +- [Simple VPC with secondary CIDR blocks](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/secondary-cidr-blocks) +- [Complete VPC](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/complete-vpc) with VPC Endpoints. +- [VPC with IPv6 enabled](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/ipv6) +- [Network ACL](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/network-acls) +- [VPC Flow Logs](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/vpc-flow-logs) +- [Manage Default VPC](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/manage-default-vpc) +- [Few tests and edge case examples](https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/issues) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.63 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.63 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.flow_log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_default_network_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_network_acl) | resource | +| [aws_default_route_table.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_route_table) | resource | +| [aws_default_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group) | resource | +| [aws_egress_only_internet_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/egress_only_internet_gateway) | resource | +| [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | +| [aws_flow_log.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource | +| [aws_iam_policy.vpc_flow_log_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.vpc_flow_log_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.vpc_flow_log_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_internet_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | +| [aws_nat_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource | +| [aws_network_acl.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl) | resource | +| [aws_network_acl.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl) | resource | +| [aws_network_acl_rule.private_inbound](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource | +| [aws_network_acl_rule.private_outbound](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource | +| [aws_network_acl_rule.public_inbound](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource | +| [aws_network_acl_rule.public_outbound](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource | +| [aws_route.private_ipv6_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.private_nat_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.public_internet_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route.public_internet_gateway_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_route_table.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | +| [aws_route_table.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | +| [aws_route_table_association.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | +| [aws_route_table_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | +| [aws_subnet.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | +| [aws_subnet.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | +| [aws_vpc.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource | +| [aws_vpc_dhcp_options.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_dhcp_options) | resource | +| [aws_vpc_dhcp_options_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_dhcp_options_association) | resource | +| [aws_vpc_ipv4_cidr_block_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipv4_cidr_block_association) | resource | +| [aws_iam_policy_document.flow_log_cloudwatch_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.vpc_flow_log_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [amazon\_side\_asn](#input\_amazon\_side\_asn) | The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the virtual private gateway is created with the current default Amazon ASN. | `string` | `"64512"` | no | +| [assign\_ipv6\_address\_on\_creation](#input\_assign\_ipv6\_address\_on\_creation) | Assign IPv6 address on subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map\_public\_ip\_on\_launch | `bool` | `false` | no | +| [azs](#input\_azs) | A list of availability zones names or ids in the region | `list(string)` | `[]` | no | +| [cidr](#input\_cidr) | The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overridden | `string` | `"0.0.0.0/0"` | no | +| [create\_egress\_only\_igw](#input\_create\_egress\_only\_igw) | Controls if an Egress Only Internet Gateway is created and its related routes. | `bool` | `true` | no | +| [create\_flow\_log\_cloudwatch\_iam\_role](#input\_create\_flow\_log\_cloudwatch\_iam\_role) | Whether to create IAM role for VPC Flow Logs | `bool` | `false` | no | +| [create\_flow\_log\_cloudwatch\_log\_group](#input\_create\_flow\_log\_cloudwatch\_log\_group) | Whether to create CloudWatch log group for VPC Flow Logs | `bool` | `false` | no | +| [create\_igw](#input\_create\_igw) | Controls if an Internet Gateway is created for public subnets and the related routes that connect them. | `bool` | `true` | no | +| [create\_private\_subnet\_route\_table](#input\_create\_private\_subnet\_route\_table) | Controls if separate route table for private should be created | `bool` | `false` | no | +| [create\_vpc](#input\_create\_vpc) | Controls if VPC should be created (it affects almost all resources) | `bool` | `true` | no | +| [default\_network\_acl\_egress](#input\_default\_network\_acl\_egress) | List of maps of egress rules to set on the Default Network ACL | `list(map(string))` |
[
{
"action": "allow",
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_no": 100,
"to_port": 0
},
{
"action": "allow",
"from_port": 0,
"ipv6_cidr_block": "::/0",
"protocol": "-1",
"rule_no": 101,
"to_port": 0
}
]
| no | +| [default\_network\_acl\_ingress](#input\_default\_network\_acl\_ingress) | List of maps of ingress rules to set on the Default Network ACL | `list(map(string))` |
[
{
"action": "allow",
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_no": 100,
"to_port": 0
},
{
"action": "allow",
"from_port": 0,
"ipv6_cidr_block": "::/0",
"protocol": "-1",
"rule_no": 101,
"to_port": 0
}
]
| no | +| [default\_network\_acl\_name](#input\_default\_network\_acl\_name) | Name to be used on the Default Network ACL | `string` | `null` | no | +| [default\_network\_acl\_tags](#input\_default\_network\_acl\_tags) | Additional tags for the Default Network ACL | `map(string)` | `{}` | no | +| [default\_route\_table\_name](#input\_default\_route\_table\_name) | Name to be used on the default route table | `string` | `null` | no | +| [default\_route\_table\_propagating\_vgws](#input\_default\_route\_table\_propagating\_vgws) | List of virtual gateways for propagation | `list(string)` | `[]` | no | +| [default\_route\_table\_routes](#input\_default\_route\_table\_routes) | Configuration block of routes. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_route_table#route | `list(map(string))` | `[]` | no | +| [default\_route\_table\_tags](#input\_default\_route\_table\_tags) | Additional tags for the default route table | `map(string)` | `{}` | no | +| [default\_security\_group\_egress](#input\_default\_security\_group\_egress) | List of maps of egress rules to set on the default security group | `list(map(string))` | `[]` | no | +| [default\_security\_group\_ingress](#input\_default\_security\_group\_ingress) | List of maps of ingress rules to set on the default security group | `list(map(string))` | `[]` | no | +| [default\_security\_group\_name](#input\_default\_security\_group\_name) | Name to be used on the default security group | `string` | `null` | no | +| [default\_security\_group\_tags](#input\_default\_security\_group\_tags) | Additional tags for the default security group | `map(string)` | `{}` | no | +| [default\_vpc\_enable\_classiclink](#input\_default\_vpc\_enable\_classiclink) | [DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink in the Default VPC | `bool` | `false` | no | +| [default\_vpc\_enable\_dns\_hostnames](#input\_default\_vpc\_enable\_dns\_hostnames) | Should be true to enable DNS hostnames in the Default VPC | `bool` | `false` | no | +| [default\_vpc\_enable\_dns\_support](#input\_default\_vpc\_enable\_dns\_support) | Should be true to enable DNS support in the Default VPC | `bool` | `true` | no | +| [default\_vpc\_name](#input\_default\_vpc\_name) | Name to be used on the Default VPC | `string` | `null` | no | +| [default\_vpc\_tags](#input\_default\_vpc\_tags) | Additional tags for the Default VPC | `map(string)` | `{}` | no | +| [dhcp\_options\_domain\_name](#input\_dhcp\_options\_domain\_name) | Specifies DNS name for DHCP options set (requires enable\_dhcp\_options set to true) | `string` | `""` | no | +| [dhcp\_options\_domain\_name\_servers](#input\_dhcp\_options\_domain\_name\_servers) | Specify a list of DNS server addresses for DHCP options set, default to AWS provided (requires enable\_dhcp\_options set to true) | `list(string)` |
[
"AmazonProvidedDNS"
]
| no | +| [dhcp\_options\_netbios\_name\_servers](#input\_dhcp\_options\_netbios\_name\_servers) | Specify a list of netbios servers for DHCP options set (requires enable\_dhcp\_options set to true) | `list(string)` | `[]` | no | +| [dhcp\_options\_netbios\_node\_type](#input\_dhcp\_options\_netbios\_node\_type) | Specify netbios node\_type for DHCP options set (requires enable\_dhcp\_options set to true) | `string` | `""` | no | +| [dhcp\_options\_ntp\_servers](#input\_dhcp\_options\_ntp\_servers) | Specify a list of NTP servers for DHCP options set (requires enable\_dhcp\_options set to true) | `list(string)` | `[]` | no | +| [dhcp\_options\_tags](#input\_dhcp\_options\_tags) | Additional tags for the DHCP option set (requires enable\_dhcp\_options set to true) | `map(string)` | `{}` | no | +| [enable\_classiclink](#input\_enable\_classiclink) | [DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink for the VPC. Only valid in regions and accounts that support EC2 Classic. | `bool` | `null` | no | +| [enable\_classiclink\_dns\_support](#input\_enable\_classiclink\_dns\_support) | [DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink DNS Support for the VPC. Only valid in regions and accounts that support EC2 Classic. | `bool` | `null` | no | +| [enable\_dhcp\_options](#input\_enable\_dhcp\_options) | Should be true if you want to specify a DHCP options set with a custom domain name, DNS servers, NTP servers, netbios servers, and/or netbios server type | `bool` | `false` | no | +| [enable\_dns\_hostnames](#input\_enable\_dns\_hostnames) | Should be true to enable DNS hostnames in the VPC | `bool` | `false` | no | +| [enable\_dns\_support](#input\_enable\_dns\_support) | Should be true to enable DNS support in the VPC | `bool` | `true` | no | +| [enable\_flow\_log](#input\_enable\_flow\_log) | Whether or not to enable VPC Flow Logs | `bool` | `false` | no | +| [enable\_ipv6](#input\_enable\_ipv6) | Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. | `bool` | `false` | no | +| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Should be true if you want to provision NAT Gateways for each of your private networks | `bool` | `false` | no | +| [enable\_nat\_gateway\_for\_db](#input\_enable\_nat\_gateway\_for\_db) | Controls if nat gateway for db should be enabled | `bool` | `true` | no | +| [external\_nat\_ip\_ids](#input\_external\_nat\_ip\_ids) | List of EIP IDs to be assigned to the NAT Gateways (used in combination with reuse\_nat\_ips) | `list(string)` | `[]` | no | +| [external\_nat\_ips](#input\_external\_nat\_ips) | List of EIPs to be used for `nat_public_ips` output (used in combination with reuse\_nat\_ips and external\_nat\_ip\_ids) | `list(string)` | `[]` | no | +| [flow\_log\_cloudwatch\_iam\_role\_arn](#input\_flow\_log\_cloudwatch\_iam\_role\_arn) | The ARN for the IAM role that's used to post flow logs to a CloudWatch Logs log group. When flow\_log\_destination\_arn is set to ARN of Cloudwatch Logs, this argument needs to be provided. | `string` | `""` | no | +| [flow\_log\_cloudwatch\_log\_group\_kms\_key\_id](#input\_flow\_log\_cloudwatch\_log\_group\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data for VPC flow logs. | `string` | `null` | no | +| [flow\_log\_cloudwatch\_log\_group\_name\_prefix](#input\_flow\_log\_cloudwatch\_log\_group\_name\_prefix) | Specifies the name prefix of CloudWatch Log Group for VPC flow logs. | `string` | `"/aws/vpc-flow-log/"` | no | +| [flow\_log\_cloudwatch\_log\_group\_retention\_in\_days](#input\_flow\_log\_cloudwatch\_log\_group\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group for VPC flow logs. | `number` | `null` | no | +| [flow\_log\_destination\_arn](#input\_flow\_log\_destination\_arn) | The ARN of the CloudWatch log group or S3 bucket where VPC Flow Logs will be pushed. If this ARN is a S3 bucket the appropriate permissions need to be set on that bucket's policy. When create\_flow\_log\_cloudwatch\_log\_group is set to false this argument must be provided. | `string` | `""` | no | +| [flow\_log\_destination\_type](#input\_flow\_log\_destination\_type) | Type of flow log destination. Can be s3 or cloud-watch-logs. | `string` | `"cloud-watch-logs"` | no | +| [flow\_log\_file\_format](#input\_flow\_log\_file\_format) | (Optional) The format for the flow log. Valid values: `plain-text`, `parquet`. | `string` | `"plain-text"` | no | +| [flow\_log\_hive\_compatible\_partitions](#input\_flow\_log\_hive\_compatible\_partitions) | (Optional) Indicates whether to use Hive-compatible prefixes for flow logs stored in Amazon S3. | `bool` | `false` | no | +| [flow\_log\_log\_format](#input\_flow\_log\_log\_format) | The fields to include in the flow log record, in the order in which they should appear. | `string` | `null` | no | +| [flow\_log\_max\_aggregation\_interval](#input\_flow\_log\_max\_aggregation\_interval) | The maximum interval of time during which a flow of packets is captured and aggregated into a flow log record. Valid Values: `60` seconds or `600` seconds. | `number` | `600` | no | +| [flow\_log\_per\_hour\_partition](#input\_flow\_log\_per\_hour\_partition) | (Optional) Indicates whether to partition the flow log per hour. This reduces the cost and response time for queries. | `bool` | `false` | no | +| [flow\_log\_traffic\_type](#input\_flow\_log\_traffic\_type) | The type of traffic to capture. Valid values: ACCEPT, REJECT, ALL. | `string` | `"ALL"` | no | +| [igw\_tags](#input\_igw\_tags) | Additional tags for the internet gateway | `map(string)` | `{}` | no | +| [instance\_tenancy](#input\_instance\_tenancy) | A tenancy option for instances launched into the VPC | `string` | `"default"` | no | +| [manage\_default\_network\_acl](#input\_manage\_default\_network\_acl) | Should be true to adopt and manage Default Network ACL | `bool` | `false` | no | +| [manage\_default\_route\_table](#input\_manage\_default\_route\_table) | Should be true to manage default route table | `bool` | `false` | no | +| [manage\_default\_security\_group](#input\_manage\_default\_security\_group) | Should be true to adopt and manage default security group | `bool` | `false` | no | +| [manage\_default\_vpc](#input\_manage\_default\_vpc) | Should be true to adopt and manage Default VPC | `bool` | `false` | no | +| [map\_public\_ip\_on\_launch](#input\_map\_public\_ip\_on\_launch) | Should be false if you do not want to auto-assign public IP on launch | `bool` | `true` | no | +| [name](#input\_name) | Name to be used on all the resources as identifier | `string` | `""` | no | +| [nat\_eip\_tags](#input\_nat\_eip\_tags) | Additional tags for the NAT EIP | `map(string)` | `{}` | no | +| [nat\_gateway\_destination\_cidr\_block](#input\_nat\_gateway\_destination\_cidr\_block) | Used to pass a custom destination route for private NAT Gateway. If not specified, the default 0.0.0.0/0 is used as a destination route. | `string` | `"0.0.0.0/0"` | no | +| [nat\_gateway\_tags](#input\_nat\_gateway\_tags) | Additional tags for the NAT gateways | `map(string)` | `{}` | no | +| [one\_nat\_gateway\_per\_az](#input\_one\_nat\_gateway\_per\_az) | Should be true if you want only one NAT Gateway per availability zone. Requires `var.azs` to be set, and the number of `public_subnets` created to be greater than or equal to the number of availability zones specified in `var.azs`. | `bool` | `false` | no | +| [private\_acl\_tags](#input\_private\_acl\_tags) | Additional tags for the private subnets network ACL | `map(string)` | `{}` | no | +| [private\_dedicated\_network\_acl](#input\_private\_dedicated\_network\_acl) | Whether to use dedicated network ACL (not default) and custom rules for private subnets | `bool` | `false` | no | +| [private\_inbound\_acl\_rules](#input\_private\_inbound\_acl\_rules) | Private subnets inbound network ACLs | `list(map(string))` |
[
{
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_action": "allow",
"rule_number": 100,
"to_port": 0
}
]
| no | +| [private\_outbound\_acl\_rules](#input\_private\_outbound\_acl\_rules) | Private subnets outbound network ACLs | `list(map(string))` |
[
{
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_action": "allow",
"rule_number": 100,
"to_port": 0
}
]
| no | +| [private\_route\_table\_tags](#input\_private\_route\_table\_tags) | Additional tags for the private route tables | `map(string)` | `{}` | no | +| [private\_subnet\_assign\_ipv6\_address\_on\_creation](#input\_private\_subnet\_assign\_ipv6\_address\_on\_creation) | Assign IPv6 address on private subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map\_public\_ip\_on\_launch | `bool` | `null` | no | +| [private\_subnet\_ipv6\_prefixes](#input\_private\_subnet\_ipv6\_prefixes) | Assigns IPv6 private subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list | `list(string)` | `[]` | no | +| [private\_subnet\_suffix](#input\_private\_subnet\_suffix) | Suffix to append to private subnets name | `string` | `"private"` | no | +| [private\_subnet\_tags](#input\_private\_subnet\_tags) | Additional tags for the private subnets | `map(string)` | `{}` | no | +| [private\_subnets](#input\_private\_subnets) | n/a | `list(map(string))` |
[
{
"availability_zone": "us-east-1a",
"cidr_block": "10.0.1.0/24",
"name": "subnet-1"
},
{
"availability_zone": "us-east-1b",
"cidr_block": "10.0.2.0/24",
"name": "subnet-2"
}
]
| no | +| [propagate\_private\_route\_tables\_vgw](#input\_propagate\_private\_route\_tables\_vgw) | Should be true if you want route table propagation | `bool` | `false` | no | +| [propagate\_public\_route\_tables\_vgw](#input\_propagate\_public\_route\_tables\_vgw) | Should be true if you want route table propagation | `bool` | `false` | no | +| [public\_acl\_tags](#input\_public\_acl\_tags) | Additional tags for the public subnets network ACL | `map(string)` | `{}` | no | +| [public\_dedicated\_network\_acl](#input\_public\_dedicated\_network\_acl) | Whether to use dedicated network ACL (not default) and custom rules for public subnets | `bool` | `false` | no | +| [public\_inbound\_acl\_rules](#input\_public\_inbound\_acl\_rules) | Public subnets inbound network ACLs | `list(map(string))` |
[
{
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_action": "allow",
"rule_number": 100,
"to_port": 0
}
]
| no | +| [public\_outbound\_acl\_rules](#input\_public\_outbound\_acl\_rules) | Public subnets outbound network ACLs | `list(map(string))` |
[
{
"cidr_block": "0.0.0.0/0",
"from_port": 0,
"protocol": "-1",
"rule_action": "allow",
"rule_number": 100,
"to_port": 0
}
]
| no | +| [public\_route\_table\_tags](#input\_public\_route\_table\_tags) | Additional tags for the public route tables | `map(string)` | `{}` | no | +| [public\_subnet\_assign\_ipv6\_address\_on\_creation](#input\_public\_subnet\_assign\_ipv6\_address\_on\_creation) | Assign IPv6 address on public subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map\_public\_ip\_on\_launch | `bool` | `null` | no | +| [public\_subnet\_ipv6\_prefixes](#input\_public\_subnet\_ipv6\_prefixes) | Assigns IPv6 public subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list | `list(string)` | `[]` | no | +| [public\_subnet\_suffix](#input\_public\_subnet\_suffix) | Suffix to append to public subnets name | `string` | `"public"` | no | +| [public\_subnet\_tags](#input\_public\_subnet\_tags) | Additional tags for the public subnets | `map(string)` | `{}` | no | +| [public\_subnets](#input\_public\_subnets) | A list of public subnets inside the VPC | `list(string)` | `[]` | no | +| [reuse\_nat\_ips](#input\_reuse\_nat\_ips) | Should be true if you don't want EIPs to be created for your NAT Gateways and will instead pass them in via the 'external\_nat\_ip\_ids' variable | `bool` | `false` | no | +| [secondary\_cidr\_blocks](#input\_secondary\_cidr\_blocks) | List of secondary CIDR blocks to associate with the VPC to extend the IP Address pool | `list(string)` | `[]` | no | +| [single\_nat\_gateway](#input\_single\_nat\_gateway) | Should be true if you want to provision a single shared NAT Gateway across all of your private networks | `bool` | `false` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [vpc\_flow\_log\_permissions\_boundary](#input\_vpc\_flow\_log\_permissions\_boundary) | The ARN of the Permissions Boundary for the VPC Flow Log IAM Role | `string` | `null` | no | +| [vpc\_flow\_log\_tags](#input\_vpc\_flow\_log\_tags) | Additional tags for the VPC Flow Logs | `map(string)` | `{}` | no | +| [vpc\_tags](#input\_vpc\_tags) | Additional tags for the VPC | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [all\_private\_route\_table\_ids](#output\_all\_private\_route\_table\_ids) | List of IDs of all private route tables | +| [all\_private\_subnets](#output\_all\_private\_subnets) | n/a | +| [azs](#output\_azs) | A list of availability zones specified as argument to this module | +| [default\_network\_acl\_id](#output\_default\_network\_acl\_id) | The ID of the default network ACL | +| [default\_route\_table\_id](#output\_default\_route\_table\_id) | The ID of the default route table | +| [default\_security\_group\_id](#output\_default\_security\_group\_id) | The ID of the security group created by default on VPC creation | +| [dhcp\_options\_id](#output\_dhcp\_options\_id) | The ID of the DHCP options | +| [egress\_only\_internet\_gateway\_id](#output\_egress\_only\_internet\_gateway\_id) | The ID of the egress only Internet Gateway | +| [igw\_arn](#output\_igw\_arn) | The ARN of the Internet Gateway | +| [igw\_id](#output\_igw\_id) | The ID of the Internet Gateway | +| [name](#output\_name) | The name of the VPC specified as argument to this module | +| [nat\_ids](#output\_nat\_ids) | List of allocation ID of Elastic IPs created for AWS NAT Gateway | +| [nat\_public\_ips](#output\_nat\_public\_ips) | List of public Elastic IPs created for AWS NAT Gateway | +| [natgw\_ids](#output\_natgw\_ids) | List of NAT Gateway IDs | +| [private\_ipv6\_egress\_route\_ids](#output\_private\_ipv6\_egress\_route\_ids) | List of IDs of the ipv6 egress route | +| [private\_nat\_gateway\_route\_ids](#output\_private\_nat\_gateway\_route\_ids) | List of IDs of the private nat gateway route | +| [private\_network\_acl\_arn](#output\_private\_network\_acl\_arn) | ARN of the private network ACL | +| [private\_network\_acl\_id](#output\_private\_network\_acl\_id) | ID of the private network ACL | +| [private\_route\_table\_association\_ids](#output\_private\_route\_table\_association\_ids) | List of IDs of the private route table association | +| [private\_route\_table\_ids](#output\_private\_route\_table\_ids) | List of IDs of private route tables | +| [private\_subnet\_arns](#output\_private\_subnet\_arns) | List of ARNs of private subnets | +| [private\_subnets](#output\_private\_subnets) | List of IDs of private subnets | +| [private\_subnets\_cidr\_blocks](#output\_private\_subnets\_cidr\_blocks) | List of cidr\_blocks of private subnets | +| [private\_subnets\_ipv6\_cidr\_blocks](#output\_private\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of private subnets in an IPv6 enabled VPC | +| [public\_internet\_gateway\_ipv6\_route\_id](#output\_public\_internet\_gateway\_ipv6\_route\_id) | ID of the IPv6 internet gateway route | +| [public\_internet\_gateway\_route\_id](#output\_public\_internet\_gateway\_route\_id) | ID of the internet gateway route | +| [public\_network\_acl\_arn](#output\_public\_network\_acl\_arn) | ARN of the public network ACL | +| [public\_network\_acl\_id](#output\_public\_network\_acl\_id) | ID of the public network ACL | +| [public\_route\_table\_association\_ids](#output\_public\_route\_table\_association\_ids) | List of IDs of the public route table association | +| [public\_route\_table\_ids](#output\_public\_route\_table\_ids) | List of IDs of public route tables | +| [public\_subnet\_arns](#output\_public\_subnet\_arns) | List of ARNs of public subnets | +| [public\_subnet\_ids](#output\_public\_subnet\_ids) | ID of the public subnets | +| [public\_subnets](#output\_public\_subnets) | List of IDs of public subnets | +| [public\_subnets\_cidr\_blocks](#output\_public\_subnets\_cidr\_blocks) | List of cidr\_blocks of public subnets | +| [public\_subnets\_ipv6\_cidr\_blocks](#output\_public\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of public subnets in an IPv6 enabled VPC | +| [vpc\_arn](#output\_vpc\_arn) | The ARN of the VPC | +| [vpc\_cidr\_block](#output\_vpc\_cidr\_block) | The CIDR block of the VPC | +| [vpc\_enable\_dns\_hostnames](#output\_vpc\_enable\_dns\_hostnames) | Whether or not the VPC has DNS hostname support | +| [vpc\_enable\_dns\_support](#output\_vpc\_enable\_dns\_support) | Whether or not the VPC has DNS support | +| [vpc\_flow\_log\_cloudwatch\_iam\_role\_arn](#output\_vpc\_flow\_log\_cloudwatch\_iam\_role\_arn) | The ARN of the IAM role used when pushing logs to Cloudwatch log group | +| [vpc\_flow\_log\_destination\_arn](#output\_vpc\_flow\_log\_destination\_arn) | The ARN of the destination for VPC Flow Logs | +| [vpc\_flow\_log\_destination\_type](#output\_vpc\_flow\_log\_destination\_type) | The type of the destination for VPC Flow Logs | +| [vpc\_flow\_log\_id](#output\_vpc\_flow\_log\_id) | The ID of the Flow Log resource | +| [vpc\_id](#output\_vpc\_id) | The ID of the VPC | +| [vpc\_instance\_tenancy](#output\_vpc\_instance\_tenancy) | Tenancy of instances spin up within VPC | +| [vpc\_ipv6\_association\_id](#output\_vpc\_ipv6\_association\_id) | The association ID for the IPv6 CIDR block | +| [vpc\_ipv6\_cidr\_block](#output\_vpc\_ipv6\_cidr\_block) | The IPv6 CIDR block | +| [vpc\_main\_route\_table\_id](#output\_vpc\_main\_route\_table\_id) | The ID of the main route table associated with this VPC | +| [vpc\_owner\_id](#output\_vpc\_owner\_id) | The ID of the AWS account that owns the VPC | +| [vpc\_secondary\_cidr\_blocks](#output\_vpc\_secondary\_cidr\_blocks) | List of secondary CIDR blocks of the VPC | + diff --git a/modules/aws-vpc/main.tf b/modules/aws-vpc/main.tf new file mode 100644 index 0000000..8e74c80 --- /dev/null +++ b/modules/aws-vpc/main.tf @@ -0,0 +1,563 @@ +locals { + max_subnet_length = max( + length(var.private_subnets), + ) + nat_gateway_count = var.single_nat_gateway ? 1 : var.one_nat_gateway_per_az ? length(var.azs) : local.max_subnet_length + + # Use `local.vpc_id` to give a hint to Terraform that subnets should be deleted before secondary CIDR blocks can be free! + vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this[0].id, "") + + create_vpc = var.create_vpc + + +} + + + + + + +################################################################################ +# VPC +################################################################################ + +resource "aws_vpc" "this" { + count = local.create_vpc ? 1 : 0 + + cidr_block = var.cidr + instance_tenancy = var.instance_tenancy + enable_dns_hostnames = var.enable_dns_hostnames + enable_dns_support = var.enable_dns_support + assign_generated_ipv6_cidr_block = var.enable_ipv6 + + tags = merge( + { "Name" = var.name }, + var.tags, + var.vpc_tags, + ) +} + +resource "aws_vpc_ipv4_cidr_block_association" "this" { + count = local.create_vpc && length(var.secondary_cidr_blocks) > 0 ? length(var.secondary_cidr_blocks) : 0 + + # Do not turn this into `local.vpc_id` + vpc_id = aws_vpc.this[0].id + + cidr_block = element(var.secondary_cidr_blocks, count.index) +} + +resource "aws_default_security_group" "this" { + count = local.create_vpc && var.manage_default_security_group ? 1 : 0 + + vpc_id = aws_vpc.this[0].id + + dynamic "ingress" { + for_each = var.default_security_group_ingress + content { + self = lookup(ingress.value, "self", null) + cidr_blocks = compact(split(",", lookup(ingress.value, "cidr_blocks", ""))) + ipv6_cidr_blocks = compact(split(",", lookup(ingress.value, "ipv6_cidr_blocks", ""))) + prefix_list_ids = compact(split(",", lookup(ingress.value, "prefix_list_ids", ""))) + security_groups = compact(split(",", lookup(ingress.value, "security_groups", ""))) + description = lookup(ingress.value, "description", null) + from_port = lookup(ingress.value, "from_port", 0) + to_port = lookup(ingress.value, "to_port", 0) + protocol = lookup(ingress.value, "protocol", "-1") + } + } + + dynamic "egress" { + for_each = var.default_security_group_egress + content { + self = lookup(egress.value, "self", null) + cidr_blocks = compact(split(",", lookup(egress.value, "cidr_blocks", ""))) + ipv6_cidr_blocks = compact(split(",", lookup(egress.value, "ipv6_cidr_blocks", ""))) + prefix_list_ids = compact(split(",", lookup(egress.value, "prefix_list_ids", ""))) + security_groups = compact(split(",", lookup(egress.value, "security_groups", ""))) + description = lookup(egress.value, "description", null) + from_port = lookup(egress.value, "from_port", 0) + to_port = lookup(egress.value, "to_port", 0) + protocol = lookup(egress.value, "protocol", "-1") + } + } + + tags = merge( + { "Name" = coalesce(var.default_security_group_name, var.name) }, + var.tags, + var.default_security_group_tags, + ) +} + +################################################################################ +# DHCP Options Set +################################################################################ + +resource "aws_vpc_dhcp_options" "this" { + count = local.create_vpc && var.enable_dhcp_options ? 1 : 0 + + domain_name = var.dhcp_options_domain_name + domain_name_servers = var.dhcp_options_domain_name_servers + ntp_servers = var.dhcp_options_ntp_servers + netbios_name_servers = var.dhcp_options_netbios_name_servers + netbios_node_type = var.dhcp_options_netbios_node_type + + tags = merge( + { "Name" = var.name }, + var.tags, + var.dhcp_options_tags, + ) +} + +resource "aws_vpc_dhcp_options_association" "this" { + count = local.create_vpc && var.enable_dhcp_options ? 1 : 0 + + vpc_id = local.vpc_id + dhcp_options_id = aws_vpc_dhcp_options.this[0].id +} + +################################################################################ +# Internet Gateway +################################################################################ + +resource "aws_internet_gateway" "this" { + count = local.create_vpc && var.create_igw && length(var.public_subnets) > 0 ? 1 : 0 + + vpc_id = local.vpc_id + + tags = merge( + { "Name" = var.name }, + var.tags, + var.igw_tags, + ) +} + +resource "aws_egress_only_internet_gateway" "this" { + count = local.create_vpc && var.create_egress_only_igw && var.enable_ipv6 && local.max_subnet_length > 0 ? 1 : 0 + + vpc_id = local.vpc_id + + tags = merge( + { "Name" = var.name }, + var.tags, + var.igw_tags, + ) +} + +################################################################################ +# Default route +################################################################################ + +resource "aws_default_route_table" "default" { + count = local.create_vpc && var.manage_default_route_table ? 1 : 0 + + default_route_table_id = aws_vpc.this[0].default_route_table_id + propagating_vgws = var.default_route_table_propagating_vgws + + dynamic "route" { + for_each = var.default_route_table_routes + content { + # One of the following destinations must be provided + cidr_block = route.value.cidr_block + ipv6_cidr_block = lookup(route.value, "ipv6_cidr_block", null) + + # One of the following targets must be provided + egress_only_gateway_id = lookup(route.value, "egress_only_gateway_id", null) + gateway_id = lookup(route.value, "gateway_id", null) + instance_id = lookup(route.value, "instance_id", null) + nat_gateway_id = lookup(route.value, "nat_gateway_id", null) + network_interface_id = lookup(route.value, "network_interface_id", null) + transit_gateway_id = lookup(route.value, "transit_gateway_id", null) + vpc_endpoint_id = lookup(route.value, "vpc_endpoint_id", null) + vpc_peering_connection_id = lookup(route.value, "vpc_peering_connection_id", null) + } + } + + timeouts { + create = "5m" + update = "5m" + } + + tags = merge( + { "Name" = coalesce(var.default_route_table_name, var.name) }, + var.tags, + var.default_route_table_tags, + ) +} + +################################################################################ +# Publiс routes +################################################################################ + +resource "aws_route_table" "public" { + count = local.create_vpc && length(var.public_subnets) > 0 ? 1 : 0 + + vpc_id = local.vpc_id + + tags = merge( + { "Name" = "${var.name}-${var.public_subnet_suffix}" }, + var.tags, + var.public_route_table_tags, + ) +} + +resource "aws_route" "public_internet_gateway" { + count = local.create_vpc && var.create_igw && length(var.public_subnets) > 0 ? 1 : 0 + + route_table_id = aws_route_table.public[0].id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.this[0].id + + timeouts { + create = "5m" + } +} + +resource "aws_route" "public_internet_gateway_ipv6" { + count = local.create_vpc && var.create_igw && var.enable_ipv6 && length(var.public_subnets) > 0 ? 1 : 0 + + route_table_id = aws_route_table.public[0].id + destination_ipv6_cidr_block = "::/0" + gateway_id = aws_internet_gateway.this[0].id +} + + + + + + +################################################################################ +# Private routes +# There are as many routing tables as the number of NAT gateways +################################################################################ + +resource "aws_route_table" "private" { + count = local.create_vpc && var.create_private_subnet_route_table && local.max_subnet_length > 0 ? length(var.private_subnets) : 0 + + vpc_id = local.vpc_id + + tags = merge( + { + "Name" = var.single_nat_gateway ? "${var.name}-${var.private_subnet_suffix}" : "${var.name}-${var.private_subnets[count.index].name}-${var.private_subnets[count.index].availability_zone}" + }, + var.tags, + var.private_route_table_tags, + ) +} + + + +resource "aws_route" "private_nat_gateway" { + # not necessary for now however + # if you only want spesifik subnets to be routed to nat gateway (for private subnets to public internet, put them into private_subnets variable as first elements, e.g: + # create a custom variable like, length of private subnets to be routed to nat gw, let's say give it 2 and put 2 subnets to 0. and 1. element at this private_subnets list and change length(var.private_subnets) to length(givenCount) ) + count = local.create_vpc && var.create_private_subnet_route_table && var.enable_nat_gateway ? length(var.private_subnets) : 0 + + route_table_id = element(aws_route_table.private[*].id, count.index) + destination_cidr_block = var.nat_gateway_destination_cidr_block + nat_gateway_id = element(aws_nat_gateway.this[*].id, count.index) + + timeouts { + create = "5m" + } +} + +resource "aws_route" "private_ipv6_egress" { + count = local.create_vpc && var.create_private_subnet_route_table && var.create_egress_only_igw && var.enable_ipv6 ? length(var.private_subnets) : 0 + + route_table_id = element(aws_route_table.private[*].id, count.index) + destination_ipv6_cidr_block = "::/0" + egress_only_gateway_id = element(aws_egress_only_internet_gateway.this[*].id, 0) +} + + + + + +################################################################################ +# Public subnet +################################################################################ + +resource "aws_subnet" "public" { + count = local.create_vpc && length(var.public_subnets) > 0 && (false == var.one_nat_gateway_per_az || length(var.public_subnets) >= length(var.azs)) ? length(var.public_subnets) : 0 + + vpc_id = local.vpc_id + cidr_block = element(concat(var.public_subnets, [""]), count.index) + availability_zone = length(regexall("^[a-z]{2}-", element(var.azs, count.index))) > 0 ? element(var.azs, count.index) : null + availability_zone_id = length(regexall("^[a-z]{2}-", element(var.azs, count.index))) == 0 ? element(var.azs, count.index) : null + map_public_ip_on_launch = var.map_public_ip_on_launch + assign_ipv6_address_on_creation = var.public_subnet_assign_ipv6_address_on_creation == null ? var.assign_ipv6_address_on_creation : var.public_subnet_assign_ipv6_address_on_creation + + ipv6_cidr_block = var.enable_ipv6 && length(var.public_subnet_ipv6_prefixes) > 0 ? cidrsubnet(aws_vpc.this[0].ipv6_cidr_block, 8, var.public_subnet_ipv6_prefixes[count.index]) : null + + tags = merge( + { + "Name" = format( + "${var.name}-${var.public_subnet_suffix}-%s", + element(var.azs, count.index), + ) + }, + var.tags, + var.public_subnet_tags, + ) +} + +################################################################################ +# Private subnet +################################################################################ + +resource "aws_subnet" "private" { + count = local.create_vpc && length(var.private_subnets) > 0 ? length(var.private_subnets) : 0 + + vpc_id = local.vpc_id + cidr_block = var.private_subnets[count.index].cidr_block + availability_zone = var.private_subnets[count.index].availability_zone + # availability_zone = length(regexall("^[a-z]{2}-", element(var.azs, count.index))) > 0 ? element(var.azs, count.index) : null + # availability_zone_id = length(regexall("^[a-z]{2}-", element(var.azs, count.index))) == 0 ? element(var.azs, count.index) : null + assign_ipv6_address_on_creation = var.private_subnet_assign_ipv6_address_on_creation == null ? var.assign_ipv6_address_on_creation : var.private_subnet_assign_ipv6_address_on_creation + + ipv6_cidr_block = var.enable_ipv6 && length(var.private_subnet_ipv6_prefixes) > 0 ? cidrsubnet(aws_vpc.this[0].ipv6_cidr_block, 8, var.private_subnet_ipv6_prefixes[count.index]) : null + + tags = merge( + { + "Name" = "${var.private_subnets[count.index].name}-${var.private_subnets[count.index].availability_zone}", + + }, + var.tags, + var.private_subnet_tags, + try(var.private_subnets[count.index].additional_tags, {}) + ) +} + + + +################################################################################ +# Default Network ACLs +################################################################################ + +resource "aws_default_network_acl" "this" { + count = local.create_vpc && var.manage_default_network_acl ? 1 : 0 + + default_network_acl_id = aws_vpc.this[0].default_network_acl_id + + # subnet_ids is using lifecycle ignore_changes, so it is not necessary to list + # any explicitly. See https://github.com/terraform-aws-modules/terraform-aws-vpc/issues/736. + subnet_ids = null + + dynamic "ingress" { + for_each = var.default_network_acl_ingress + content { + action = ingress.value.action + cidr_block = lookup(ingress.value, "cidr_block", null) + from_port = ingress.value.from_port + icmp_code = lookup(ingress.value, "icmp_code", null) + icmp_type = lookup(ingress.value, "icmp_type", null) + ipv6_cidr_block = lookup(ingress.value, "ipv6_cidr_block", null) + protocol = ingress.value.protocol + rule_no = ingress.value.rule_no + to_port = ingress.value.to_port + } + } + dynamic "egress" { + for_each = var.default_network_acl_egress + content { + action = egress.value.action + cidr_block = lookup(egress.value, "cidr_block", null) + from_port = egress.value.from_port + icmp_code = lookup(egress.value, "icmp_code", null) + icmp_type = lookup(egress.value, "icmp_type", null) + ipv6_cidr_block = lookup(egress.value, "ipv6_cidr_block", null) + protocol = egress.value.protocol + rule_no = egress.value.rule_no + to_port = egress.value.to_port + } + } + + tags = merge( + { "Name" = coalesce(var.default_network_acl_name, var.name) }, + var.tags, + var.default_network_acl_tags, + ) + + lifecycle { + ignore_changes = [subnet_ids] + } +} + + +################################################################################ +# Public Network ACLs +################################################################################ + +resource "aws_network_acl" "public" { + count = local.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 ? 1 : 0 + + vpc_id = local.vpc_id + subnet_ids = aws_subnet.public[*].id + + tags = merge( + { "Name" = "${var.name}-${var.public_subnet_suffix}" }, + var.tags, + var.public_acl_tags, + ) +} + +resource "aws_network_acl_rule" "public_inbound" { + count = local.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 ? length(var.public_inbound_acl_rules) : 0 + + network_acl_id = aws_network_acl.public[0].id + + egress = false + rule_number = var.public_inbound_acl_rules[count.index]["rule_number"] + rule_action = var.public_inbound_acl_rules[count.index]["rule_action"] + from_port = lookup(var.public_inbound_acl_rules[count.index], "from_port", null) + to_port = lookup(var.public_inbound_acl_rules[count.index], "to_port", null) + icmp_code = lookup(var.public_inbound_acl_rules[count.index], "icmp_code", null) + icmp_type = lookup(var.public_inbound_acl_rules[count.index], "icmp_type", null) + protocol = var.public_inbound_acl_rules[count.index]["protocol"] + cidr_block = lookup(var.public_inbound_acl_rules[count.index], "cidr_block", null) + ipv6_cidr_block = lookup(var.public_inbound_acl_rules[count.index], "ipv6_cidr_block", null) +} + +resource "aws_network_acl_rule" "public_outbound" { + count = local.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 ? length(var.public_outbound_acl_rules) : 0 + + network_acl_id = aws_network_acl.public[0].id + + egress = true + rule_number = var.public_outbound_acl_rules[count.index]["rule_number"] + rule_action = var.public_outbound_acl_rules[count.index]["rule_action"] + from_port = lookup(var.public_outbound_acl_rules[count.index], "from_port", null) + to_port = lookup(var.public_outbound_acl_rules[count.index], "to_port", null) + icmp_code = lookup(var.public_outbound_acl_rules[count.index], "icmp_code", null) + icmp_type = lookup(var.public_outbound_acl_rules[count.index], "icmp_type", null) + protocol = var.public_outbound_acl_rules[count.index]["protocol"] + cidr_block = lookup(var.public_outbound_acl_rules[count.index], "cidr_block", null) + ipv6_cidr_block = lookup(var.public_outbound_acl_rules[count.index], "ipv6_cidr_block", null) +} + +################################################################################ +# Private Network ACLs +################################################################################ + +resource "aws_network_acl" "private" { + count = local.create_vpc && var.private_dedicated_network_acl && length(var.private_subnets) > 0 ? 1 : 0 + + vpc_id = local.vpc_id + subnet_ids = aws_subnet.private[*].id + + tags = merge( + { "Name" = "${var.name}-${var.private_subnet_suffix}" }, + var.tags, + var.private_acl_tags, + ) +} + +resource "aws_network_acl_rule" "private_inbound" { + count = local.create_vpc && var.private_dedicated_network_acl && length(var.private_subnets) > 0 ? length(var.private_inbound_acl_rules) : 0 + + network_acl_id = aws_network_acl.private[0].id + + egress = false + rule_number = var.private_inbound_acl_rules[count.index]["rule_number"] + rule_action = var.private_inbound_acl_rules[count.index]["rule_action"] + from_port = lookup(var.private_inbound_acl_rules[count.index], "from_port", null) + to_port = lookup(var.private_inbound_acl_rules[count.index], "to_port", null) + icmp_code = lookup(var.private_inbound_acl_rules[count.index], "icmp_code", null) + icmp_type = lookup(var.private_inbound_acl_rules[count.index], "icmp_type", null) + protocol = var.private_inbound_acl_rules[count.index]["protocol"] + cidr_block = lookup(var.private_inbound_acl_rules[count.index], "cidr_block", null) + ipv6_cidr_block = lookup(var.private_inbound_acl_rules[count.index], "ipv6_cidr_block", null) +} + +resource "aws_network_acl_rule" "private_outbound" { + count = local.create_vpc && var.private_dedicated_network_acl && length(var.private_subnets) > 0 ? length(var.private_outbound_acl_rules) : 0 + + network_acl_id = aws_network_acl.private[0].id + + egress = true + rule_number = var.private_outbound_acl_rules[count.index]["rule_number"] + rule_action = var.private_outbound_acl_rules[count.index]["rule_action"] + from_port = lookup(var.private_outbound_acl_rules[count.index], "from_port", null) + to_port = lookup(var.private_outbound_acl_rules[count.index], "to_port", null) + icmp_code = lookup(var.private_outbound_acl_rules[count.index], "icmp_code", null) + icmp_type = lookup(var.private_outbound_acl_rules[count.index], "icmp_type", null) + protocol = var.private_outbound_acl_rules[count.index]["protocol"] + cidr_block = lookup(var.private_outbound_acl_rules[count.index], "cidr_block", null) + ipv6_cidr_block = lookup(var.private_outbound_acl_rules[count.index], "ipv6_cidr_block", null) +} + + +################################################################################ +# NAT Gateway +################################################################################ + +locals { + nat_gateway_ips = var.reuse_nat_ips ? var.external_nat_ip_ids : try(aws_eip.nat[*].id, []) +} + +resource "aws_eip" "nat" { + count = local.create_vpc && var.enable_nat_gateway && false == var.reuse_nat_ips ? local.nat_gateway_count : 0 + + vpc = true + + tags = merge( + { + "Name" = format( + "${var.name}-%s", + element(var.azs, var.single_nat_gateway ? 0 : count.index), + ) + }, + var.tags, + var.nat_eip_tags, + ) +} + +resource "aws_nat_gateway" "this" { + count = local.create_vpc && var.enable_nat_gateway ? local.nat_gateway_count : 0 + + allocation_id = element( + local.nat_gateway_ips, + var.single_nat_gateway ? 0 : count.index, + ) + subnet_id = element( + aws_subnet.public[*].id, + var.single_nat_gateway ? 0 : count.index, + ) + + tags = merge( + { + "Name" = format( + "${var.name}-%s", + element(var.azs, var.single_nat_gateway ? 0 : count.index), + ) + }, + var.tags, + var.nat_gateway_tags, + ) + + depends_on = [aws_internet_gateway.this] +} + + + +################################################################################ +# Route table association +################################################################################ + +resource "aws_route_table_association" "private" { + count = local.create_vpc && length(var.private_subnets) > 0 ? length(var.private_subnets) : 0 + + subnet_id = element(aws_subnet.private[*].id, count.index) + route_table_id = element( + aws_route_table.private[*].id, + var.single_nat_gateway ? 0 : count.index, + ) +} + + +resource "aws_route_table_association" "public" { + count = local.create_vpc && length(var.public_subnets) > 0 ? length(var.public_subnets) : 0 + + subnet_id = element(aws_subnet.public[*].id, count.index) + route_table_id = aws_route_table.public[0].id +} diff --git a/modules/aws-vpc/modules/vpc-endpoints/README.md b/modules/aws-vpc/modules/vpc-endpoints/README.md new file mode 100644 index 0000000..5382aef --- /dev/null +++ b/modules/aws-vpc/modules/vpc-endpoints/README.md @@ -0,0 +1,93 @@ +# AWS VPC Endpoints Terraform sub-module + +Terraform sub-module which creates VPC endpoint resources on AWS. + +## Usage + + +```hcl +module "endpoints" { + + vpc_id = "vpc-12345678" + security_group_ids = ["sg-12345678"] + + endpoints = { + s3 = { + # interface endpoint + service = "s3" + tags = { Name = "s3-vpc-endpoint" } + }, + dynamodb = { + # gateway endpoint + service = "dynamodb" + route_table_ids = ["rt-12322456", "rt-43433343", "rt-11223344"] + tags = { Name = "dynamodb-vpc-endpoint" } + }, + sns = { + service = "sns" + subnet_ids = ["subnet-12345678", "subnet-87654321"] + tags = { Name = "sns-vpc-endpoint" } + }, + sqs = { + service = "sqs" + private_dns_enabled = true + security_group_ids = ["sg-987654321"] + subnet_ids = ["subnet-12345678", "subnet-87654321"] + tags = { Name = "sqs-vpc-endpoint" } + }, + } + + tags = { + Owner = "user" + Environment = "dev" + } +} +``` + +## Examples + +- [Complete-VPC](../../examples/complete-vpc) with VPC Endpoints. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.28 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.28 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_vpc_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource | +| [aws_vpc_endpoint_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc_endpoint_service) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Determines whether resources will be created | `bool` | `true` | no | +| [endpoints](#input\_endpoints) | A map of interface and/or gateway endpoints containing their properties and configurations | `any` | `{}` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | Default security group IDs to associate with the VPC endpoints | `list(string)` | `[]` | no | +| [subnet\_ids](#input\_subnet\_ids) | Default subnets IDs to associate with the VPC endpoints | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to use on all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting VPC endpoint resources | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | The ID of the VPC in which the endpoint will be used | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [endpoints](#output\_endpoints) | Array containing the full resource object and attributes for all endpoints created | + diff --git a/modules/aws-vpc/modules/vpc-endpoints/main.tf b/modules/aws-vpc/modules/vpc-endpoints/main.tf new file mode 100644 index 0000000..b538c7a --- /dev/null +++ b/modules/aws-vpc/modules/vpc-endpoints/main.tf @@ -0,0 +1,42 @@ +################################################################################ +# Endpoint(s) +################################################################################ + +locals { + endpoints = { for k, v in var.endpoints : k => v if var.create && try(v.create, true) } +} + +data "aws_vpc_endpoint_service" "this" { + for_each = local.endpoints + + service = lookup(each.value, "service", null) + service_name = lookup(each.value, "service_name", null) + + filter { + name = "service-type" + values = [lookup(each.value, "service_type", "Interface")] + } +} + +resource "aws_vpc_endpoint" "this" { + for_each = local.endpoints + + vpc_id = var.vpc_id + service_name = data.aws_vpc_endpoint_service.this[each.key].service_name + vpc_endpoint_type = lookup(each.value, "service_type", "Interface") + auto_accept = lookup(each.value, "auto_accept", null) + + security_group_ids = lookup(each.value, "service_type", "Interface") == "Interface" ? length(distinct(concat(var.security_group_ids, lookup(each.value, "security_group_ids", [])))) > 0 ? distinct(concat(var.security_group_ids, lookup(each.value, "security_group_ids", []))) : null : null + subnet_ids = lookup(each.value, "service_type", "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null + route_table_ids = lookup(each.value, "service_type", "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null + policy = lookup(each.value, "policy", null) + private_dns_enabled = lookup(each.value, "service_type", "Interface") == "Interface" ? lookup(each.value, "private_dns_enabled", null) : null + + tags = merge(var.tags, lookup(each.value, "tags", {})) + + timeouts { + create = lookup(var.timeouts, "create", "10m") + update = lookup(var.timeouts, "update", "10m") + delete = lookup(var.timeouts, "delete", "10m") + } +} diff --git a/modules/aws-vpc/modules/vpc-endpoints/outputs.tf b/modules/aws-vpc/modules/vpc-endpoints/outputs.tf new file mode 100644 index 0000000..88aa989 --- /dev/null +++ b/modules/aws-vpc/modules/vpc-endpoints/outputs.tf @@ -0,0 +1,4 @@ +output "endpoints" { + description = "Array containing the full resource object and attributes for all endpoints created" + value = aws_vpc_endpoint.this +} diff --git a/modules/aws-vpc/modules/vpc-endpoints/variables.tf b/modules/aws-vpc/modules/vpc-endpoints/variables.tf new file mode 100644 index 0000000..afcebc3 --- /dev/null +++ b/modules/aws-vpc/modules/vpc-endpoints/variables.tf @@ -0,0 +1,41 @@ +variable "create" { + description = "Determines whether resources will be created" + type = bool + default = true +} + +variable "vpc_id" { + description = "The ID of the VPC in which the endpoint will be used" + type = string + default = null +} + +variable "endpoints" { + description = "A map of interface and/or gateway endpoints containing their properties and configurations" + type = any + default = {} +} + +variable "security_group_ids" { + description = "Default security group IDs to associate with the VPC endpoints" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "Default subnets IDs to associate with the VPC endpoints" + type = list(string) + default = [] +} + +variable "tags" { + description = "A map of tags to use on all resources" + type = map(string) + default = {} +} + +variable "timeouts" { + description = "Define maximum timeout for creating, updating, and deleting VPC endpoint resources" + type = map(string) + default = {} +} diff --git a/modules/aws-vpc/modules/vpc-endpoints/versions.tf b/modules/aws-vpc/modules/vpc-endpoints/versions.tf new file mode 100644 index 0000000..ab4d354 --- /dev/null +++ b/modules/aws-vpc/modules/vpc-endpoints/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.28" + } + } +} diff --git a/modules/aws-vpc/outputs.tf b/modules/aws-vpc/outputs.tf new file mode 100644 index 0000000..7196fbf --- /dev/null +++ b/modules/aws-vpc/outputs.tf @@ -0,0 +1,265 @@ +output "vpc_id" { + description = "The ID of the VPC" + value = try(aws_vpc.this[0].id, "") +} + +output "public_subnet_ids" { + description = "ID of the public subnets" + value = try(aws_subnet.public[*].id, []) +} + +output "vpc_arn" { + description = "The ARN of the VPC" + value = try(aws_vpc.this[0].arn, "") +} + +output "vpc_cidr_block" { + description = "The CIDR block of the VPC" + value = try(aws_vpc.this[0].cidr_block, "") +} + +output "default_security_group_id" { + description = "The ID of the security group created by default on VPC creation" + value = try(aws_vpc.this[0].default_security_group_id, "") +} + +output "default_network_acl_id" { + description = "The ID of the default network ACL" + value = try(aws_vpc.this[0].default_network_acl_id, "") +} + +output "default_route_table_id" { + description = "The ID of the default route table" + value = try(aws_vpc.this[0].default_route_table_id, "") +} + +output "vpc_instance_tenancy" { + description = "Tenancy of instances spin up within VPC" + value = try(aws_vpc.this[0].instance_tenancy, "") +} + +output "vpc_enable_dns_support" { + description = "Whether or not the VPC has DNS support" + value = try(aws_vpc.this[0].enable_dns_support, "") +} + +output "vpc_enable_dns_hostnames" { + description = "Whether or not the VPC has DNS hostname support" + value = try(aws_vpc.this[0].enable_dns_hostnames, "") +} + +output "vpc_main_route_table_id" { + description = "The ID of the main route table associated with this VPC" + value = try(aws_vpc.this[0].main_route_table_id, "") +} + +output "vpc_ipv6_association_id" { + description = "The association ID for the IPv6 CIDR block" + value = try(aws_vpc.this[0].ipv6_association_id, "") +} + +output "vpc_ipv6_cidr_block" { + description = "The IPv6 CIDR block" + value = try(aws_vpc.this[0].ipv6_cidr_block, "") +} + +output "vpc_secondary_cidr_blocks" { + description = "List of secondary CIDR blocks of the VPC" + value = compact(aws_vpc_ipv4_cidr_block_association.this[*].cidr_block) +} + +output "vpc_owner_id" { + description = "The ID of the AWS account that owns the VPC" + value = try(aws_vpc.this[0].owner_id, "") +} + +output "private_subnets" { + description = "List of IDs of private subnets" + value = aws_subnet.private[*].id +} + +output "private_subnet_arns" { + description = "List of ARNs of private subnets" + value = aws_subnet.private[*].arn +} + +output "private_subnets_cidr_blocks" { + description = "List of cidr_blocks of private subnets" + value = compact(aws_subnet.private[*].cidr_block) +} + +output "private_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of private subnets in an IPv6 enabled VPC" + value = compact(aws_subnet.private[*].ipv6_cidr_block) +} + +output "public_subnets" { + description = "List of IDs of public subnets" + value = aws_subnet.public[*].id +} + +output "public_subnet_arns" { + description = "List of ARNs of public subnets" + value = aws_subnet.public[*].arn +} + +output "public_subnets_cidr_blocks" { + description = "List of cidr_blocks of public subnets" + value = compact(aws_subnet.public[*].cidr_block) +} + +output "public_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of public subnets in an IPv6 enabled VPC" + value = compact(aws_subnet.public[*].ipv6_cidr_block) +} + +output "all_private_route_table_ids" { + value = concat( + try(aws_route_table.private[*].id, []), + ) + description = "List of IDs of all private route tables" +} + +output "public_route_table_ids" { + description = "List of IDs of public route tables" + value = aws_route_table.public[*].id +} + +output "private_route_table_ids" { + description = "List of IDs of private route tables" + value = aws_route_table.private[*].id +} + +output "all_route_table_ids" { + description = "List of IDs of private and public route tables" + value = concat(aws_route_table.private[*].id, aws_route_table.public[*].id) +} + +output "public_internet_gateway_route_id" { + description = "ID of the internet gateway route" + value = try(aws_route.public_internet_gateway[0].id, "") +} + +output "public_internet_gateway_ipv6_route_id" { + description = "ID of the IPv6 internet gateway route" + value = try(aws_route.public_internet_gateway_ipv6[0].id, "") +} + +output "private_nat_gateway_route_ids" { + description = "List of IDs of the private nat gateway route" + value = aws_route.private_nat_gateway[*].id +} + +output "private_ipv6_egress_route_ids" { + description = "List of IDs of the ipv6 egress route" + value = aws_route.private_ipv6_egress[*].id +} + + + + +output "private_route_table_association_ids" { + description = "List of IDs of the private route table association" + value = aws_route_table_association.private[*].id +} + + +output "public_route_table_association_ids" { + description = "List of IDs of the public route table association" + value = aws_route_table_association.public[*].id +} + +output "dhcp_options_id" { + description = "The ID of the DHCP options" + value = try(aws_vpc_dhcp_options.this[0].id, "") +} + +output "nat_ids" { + description = "List of allocation ID of Elastic IPs created for AWS NAT Gateway" + value = aws_eip.nat[*].id +} + +output "nat_public_ips" { + description = "List of public Elastic IPs created for AWS NAT Gateway" + value = var.reuse_nat_ips ? var.external_nat_ips : aws_eip.nat[*].public_ip +} + +output "natgw_ids" { + description = "List of NAT Gateway IDs" + value = aws_nat_gateway.this[*].id +} + +output "igw_id" { + description = "The ID of the Internet Gateway" + value = try(aws_internet_gateway.this[0].id, "") +} + +output "igw_arn" { + description = "The ARN of the Internet Gateway" + value = try(aws_internet_gateway.this[0].arn, "") +} + +output "egress_only_internet_gateway_id" { + description = "The ID of the egress only Internet Gateway" + value = try(aws_egress_only_internet_gateway.this[0].id, "") +} + + +output "public_network_acl_id" { + description = "ID of the public network ACL" + value = try(aws_network_acl.public[0].id, "") +} + +output "public_network_acl_arn" { + description = "ARN of the public network ACL" + value = try(aws_network_acl.public[0].arn, "") +} + +output "private_network_acl_id" { + description = "ID of the private network ACL" + value = try(aws_network_acl.private[0].id, "") +} + +output "private_network_acl_arn" { + description = "ARN of the private network ACL" + value = try(aws_network_acl.private[0].arn, "") +} + +# VPC flow log +output "vpc_flow_log_id" { + description = "The ID of the Flow Log resource" + value = try(aws_flow_log.this[0].id, "") +} + +output "vpc_flow_log_destination_arn" { + description = "The ARN of the destination for VPC Flow Logs" + value = local.flow_log_destination_arn +} + +output "vpc_flow_log_destination_type" { + description = "The type of the destination for VPC Flow Logs" + value = var.flow_log_destination_type +} + +output "vpc_flow_log_cloudwatch_iam_role_arn" { + description = "The ARN of the IAM role used when pushing logs to Cloudwatch log group" + value = local.flow_log_iam_role_arn +} + +# Static values (arguments) +output "azs" { + description = "A list of availability zones specified as argument to this module" + value = var.azs +} + +output "name" { + description = "The name of the VPC specified as argument to this module" + value = var.name +} + +output "all_private_subnets" { + value = try(aws_subnet.private[*]) +} + + + diff --git a/modules/aws-vpc/variables.tf b/modules/aws-vpc/variables.tf new file mode 100644 index 0000000..cb37ba2 --- /dev/null +++ b/modules/aws-vpc/variables.tf @@ -0,0 +1,682 @@ +variable "private_subnets" { + type = any + default = [ + { + name = "subnet-1" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" + additional_tags = {} + }, + { + name = "subnet-2" + cidr_block = "10.0.2.0/24" + availability_zone = "us-east-1b" + additional_tags = {} + } + ] +} + +variable "create_vpc" { + description = "Controls if VPC should be created (it affects almost all resources)" + type = bool + default = true +} + +variable "name" { + description = "Name to be used on all the resources as identifier" + type = string + default = "" +} + +variable "cidr" { + description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overridden" + type = string + default = "0.0.0.0/0" +} + +variable "enable_ipv6" { + description = "Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block." + type = bool + default = false +} + +variable "private_subnet_ipv6_prefixes" { + description = "Assigns IPv6 private subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list" + type = list(string) + default = [] +} + +variable "public_subnet_ipv6_prefixes" { + description = "Assigns IPv6 public subnet id based on the Amazon provided /56 prefix base 10 integer (0-256). Must be of equal length to the corresponding IPv4 subnet list" + type = list(string) + default = [] +} + +variable "assign_ipv6_address_on_creation" { + description = "Assign IPv6 address on subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch" + type = bool + default = false +} + + +variable "private_subnet_assign_ipv6_address_on_creation" { + description = "Assign IPv6 address on private subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch" + type = bool + default = null +} + +variable "public_subnet_assign_ipv6_address_on_creation" { + description = "Assign IPv6 address on public subnet, must be disabled to change IPv6 CIDRs. This is the IPv6 equivalent of map_public_ip_on_launch" + type = bool + default = null +} + +variable "secondary_cidr_blocks" { + description = "List of secondary CIDR blocks to associate with the VPC to extend the IP Address pool" + type = list(string) + default = [] +} + +variable "instance_tenancy" { + description = "A tenancy option for instances launched into the VPC" + type = string + default = "default" +} + +variable "public_subnet_suffix" { + description = "Suffix to append to public subnets name" + type = string + default = "public" +} + +variable "private_subnet_suffix" { + description = "Suffix to append to private subnets name" + type = string + default = "private" +} + + +variable "public_subnets" { + description = "A list of public subnets inside the VPC" + type = list(string) + default = [] +} + +variable "create_private_subnet_route_table" { + description = "Controls if separate route table for private should be created" + type = bool + default = false + +} + +variable "azs" { + description = "A list of availability zones names or ids in the region" + type = list(string) + default = [] +} + +variable "enable_dns_hostnames" { + description = "Should be true to enable DNS hostnames in the VPC" + type = bool + default = false +} + +variable "enable_dns_support" { + description = "Should be true to enable DNS support in the VPC" + type = bool + default = true +} + +# tflint-ignore: terraform_unused_declarations +variable "enable_classiclink" { + description = "[DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink for the VPC. Only valid in regions and accounts that support EC2 Classic." + type = bool + default = null +} + +# tflint-ignore: terraform_unused_declarations +variable "enable_classiclink_dns_support" { + description = "[DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink DNS Support for the VPC. Only valid in regions and accounts that support EC2 Classic." + type = bool + default = null +} + +variable "enable_nat_gateway" { + description = "Should be true if you want to provision NAT Gateways for each of your private networks" + type = bool + default = false +} + +variable "nat_gateway_destination_cidr_block" { + description = "Used to pass a custom destination route for private NAT Gateway. If not specified, the default 0.0.0.0/0 is used as a destination route." + type = string + default = "0.0.0.0/0" +} + +variable "single_nat_gateway" { + description = "Should be true if you want to provision a single shared NAT Gateway across all of your private networks" + type = bool + default = false +} + +variable "one_nat_gateway_per_az" { + description = "Should be true if you want only one NAT Gateway per availability zone. Requires `var.azs` to be set, and the number of `public_subnets` created to be greater than or equal to the number of availability zones specified in `var.azs`." + type = bool + default = false +} + +variable "reuse_nat_ips" { + description = "Should be true if you don't want EIPs to be created for your NAT Gateways and will instead pass them in via the 'external_nat_ip_ids' variable" + type = bool + default = false +} + +variable "external_nat_ip_ids" { + description = "List of EIP IDs to be assigned to the NAT Gateways (used in combination with reuse_nat_ips)" + type = list(string) + default = [] +} + +variable "external_nat_ips" { + description = "List of EIPs to be used for `nat_public_ips` output (used in combination with reuse_nat_ips and external_nat_ip_ids)" + type = list(string) + default = [] +} + +variable "map_public_ip_on_launch" { + description = "Should be false if you do not want to auto-assign public IP on launch" + type = bool + default = true +} + +variable "amazon_side_asn" { + description = "The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the virtual private gateway is created with the current default Amazon ASN." + type = string + default = "64512" +} + +variable "propagate_private_route_tables_vgw" { + description = "Should be true if you want route table propagation" + type = bool + default = false +} + +variable "propagate_public_route_tables_vgw" { + description = "Should be true if you want route table propagation" + type = bool + default = false +} + +variable "manage_default_route_table" { + description = "Should be true to manage default route table" + type = bool + default = false +} + +variable "default_route_table_name" { + description = "Name to be used on the default route table" + type = string + default = null +} + +variable "default_route_table_propagating_vgws" { + description = "List of virtual gateways for propagation" + type = list(string) + default = [] +} + +variable "default_route_table_routes" { + description = "Configuration block of routes. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_route_table#route" + type = list(map(string)) + default = [] +} + +variable "default_route_table_tags" { + description = "Additional tags for the default route table" + type = map(string) + default = {} +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "vpc_tags" { + description = "Additional tags for the VPC" + type = map(string) + default = {} +} + +variable "igw_tags" { + description = "Additional tags for the internet gateway" + type = map(string) + default = {} +} + +variable "public_subnet_tags" { + description = "Additional tags for the public subnets" + type = map(string) + default = {} +} + +variable "private_subnet_tags" { + description = "Additional tags for the private subnets" + type = map(string) + default = {} +} + +variable "public_route_table_tags" { + description = "Additional tags for the public route tables" + type = map(string) + default = {} +} + +variable "private_route_table_tags" { + description = "Additional tags for the private route tables" + type = map(string) + default = {} +} + +variable "public_acl_tags" { + description = "Additional tags for the public subnets network ACL" + type = map(string) + default = {} +} + +variable "private_acl_tags" { + description = "Additional tags for the private subnets network ACL" + type = map(string) + default = {} +} + +variable "dhcp_options_tags" { + description = "Additional tags for the DHCP option set (requires enable_dhcp_options set to true)" + type = map(string) + default = {} +} + +variable "nat_gateway_tags" { + description = "Additional tags for the NAT gateways" + type = map(string) + default = {} +} + +variable "nat_eip_tags" { + description = "Additional tags for the NAT EIP" + type = map(string) + default = {} +} + +variable "vpc_flow_log_tags" { + description = "Additional tags for the VPC Flow Logs" + type = map(string) + default = {} +} + +variable "vpc_flow_log_permissions_boundary" { + description = "The ARN of the Permissions Boundary for the VPC Flow Log IAM Role" + type = string + default = null +} + +variable "enable_dhcp_options" { + description = "Should be true if you want to specify a DHCP options set with a custom domain name, DNS servers, NTP servers, netbios servers, and/or netbios server type" + type = bool + default = false +} + +variable "dhcp_options_domain_name" { + description = "Specifies DNS name for DHCP options set (requires enable_dhcp_options set to true)" + type = string + default = "" +} + +variable "dhcp_options_domain_name_servers" { + description = "Specify a list of DNS server addresses for DHCP options set, default to AWS provided (requires enable_dhcp_options set to true)" + type = list(string) + default = ["AmazonProvidedDNS"] +} + +variable "dhcp_options_ntp_servers" { + description = "Specify a list of NTP servers for DHCP options set (requires enable_dhcp_options set to true)" + type = list(string) + default = [] +} + +variable "dhcp_options_netbios_name_servers" { + description = "Specify a list of netbios servers for DHCP options set (requires enable_dhcp_options set to true)" + type = list(string) + default = [] +} + +variable "dhcp_options_netbios_node_type" { + description = "Specify netbios node_type for DHCP options set (requires enable_dhcp_options set to true)" + type = string + default = "" +} + +variable "manage_default_vpc" { + description = "Should be true to adopt and manage Default VPC" + type = bool + default = false +} + +variable "default_vpc_name" { + description = "Name to be used on the Default VPC" + type = string + default = null +} + +variable "default_vpc_enable_dns_support" { + description = "Should be true to enable DNS support in the Default VPC" + type = bool + default = true +} + +variable "default_vpc_enable_dns_hostnames" { + description = "Should be true to enable DNS hostnames in the Default VPC" + type = bool + default = false +} + +# tflint-ignore: terraform_unused_declarations +variable "default_vpc_enable_classiclink" { + description = "[DEPRECATED](https://github.com/hashicorp/terraform/issues/31730) Should be true to enable ClassicLink in the Default VPC" + type = bool + default = false +} + +variable "default_vpc_tags" { + description = "Additional tags for the Default VPC" + type = map(string) + default = {} +} + +variable "manage_default_network_acl" { + description = "Should be true to adopt and manage Default Network ACL" + type = bool + default = false +} + +variable "default_network_acl_name" { + description = "Name to be used on the Default Network ACL" + type = string + default = null +} + +variable "default_network_acl_tags" { + description = "Additional tags for the Default Network ACL" + type = map(string) + default = {} +} + +variable "public_dedicated_network_acl" { + description = "Whether to use dedicated network ACL (not default) and custom rules for public subnets" + type = bool + default = false +} + +variable "private_dedicated_network_acl" { + description = "Whether to use dedicated network ACL (not default) and custom rules for private subnets" + type = bool + default = false +} + +variable "default_network_acl_ingress" { + description = "List of maps of ingress rules to set on the Default Network ACL" + type = list(map(string)) + + default = [ + { + rule_no = 100 + action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + { + rule_no = 101 + action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + ipv6_cidr_block = "::/0" + }, + ] +} + +variable "default_network_acl_egress" { + description = "List of maps of egress rules to set on the Default Network ACL" + type = list(map(string)) + + default = [ + { + rule_no = 100 + action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + { + rule_no = 101 + action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + ipv6_cidr_block = "::/0" + }, + ] +} + + + +variable "public_inbound_acl_rules" { + description = "Public subnets inbound network ACLs" + type = list(map(string)) + + default = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + ] +} + +variable "public_outbound_acl_rules" { + description = "Public subnets outbound network ACLs" + type = list(map(string)) + + default = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + ] +} + +variable "private_inbound_acl_rules" { + description = "Private subnets inbound network ACLs" + type = list(map(string)) + + default = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + ] +} + +variable "private_outbound_acl_rules" { + description = "Private subnets outbound network ACLs" + type = list(map(string)) + + default = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "0.0.0.0/0" + }, + ] +} + +variable "manage_default_security_group" { + description = "Should be true to adopt and manage default security group" + type = bool + default = false +} + +variable "default_security_group_name" { + description = "Name to be used on the default security group" + type = string + default = null +} + +variable "default_security_group_ingress" { + description = "List of maps of ingress rules to set on the default security group" + type = list(map(string)) + default = [] +} + +variable "enable_flow_log" { + description = "Whether or not to enable VPC Flow Logs" + type = bool + default = false +} + +variable "default_security_group_egress" { + description = "List of maps of egress rules to set on the default security group" + type = list(map(string)) + default = [] +} + +variable "default_security_group_tags" { + description = "Additional tags for the default security group" + type = map(string) + default = {} +} + +variable "create_flow_log_cloudwatch_log_group" { + description = "Whether to create CloudWatch log group for VPC Flow Logs" + type = bool + default = false +} + +variable "create_flow_log_cloudwatch_iam_role" { + description = "Whether to create IAM role for VPC Flow Logs" + type = bool + default = false +} + +variable "flow_log_traffic_type" { + description = "The type of traffic to capture. Valid values: ACCEPT, REJECT, ALL." + type = string + default = "ALL" +} + +variable "flow_log_destination_type" { + description = "Type of flow log destination. Can be s3 or cloud-watch-logs." + type = string + default = "cloud-watch-logs" +} + +variable "flow_log_log_format" { + description = "The fields to include in the flow log record, in the order in which they should appear." + type = string + default = null +} + +variable "flow_log_destination_arn" { + description = "The ARN of the CloudWatch log group or S3 bucket where VPC Flow Logs will be pushed. If this ARN is a S3 bucket the appropriate permissions need to be set on that bucket's policy. When create_flow_log_cloudwatch_log_group is set to false this argument must be provided." + type = string + default = "" +} + +variable "flow_log_cloudwatch_iam_role_arn" { + description = "The ARN for the IAM role that's used to post flow logs to a CloudWatch Logs log group. When flow_log_destination_arn is set to ARN of Cloudwatch Logs, this argument needs to be provided." + type = string + default = "" +} + +variable "flow_log_cloudwatch_log_group_name_prefix" { + description = "Specifies the name prefix of CloudWatch Log Group for VPC flow logs." + type = string + default = "/aws/vpc-flow-log/" +} + +variable "flow_log_cloudwatch_log_group_retention_in_days" { + description = "Specifies the number of days you want to retain log events in the specified log group for VPC flow logs." + type = number + default = null +} + +variable "flow_log_cloudwatch_log_group_kms_key_id" { + description = "The ARN of the KMS Key to use when encrypting log data for VPC flow logs." + type = string + default = null +} + +variable "flow_log_max_aggregation_interval" { + description = "The maximum interval of time during which a flow of packets is captured and aggregated into a flow log record. Valid Values: `60` seconds or `600` seconds." + type = number + default = 600 +} + +variable "create_igw" { + description = "Controls if an Internet Gateway is created for public subnets and the related routes that connect them." + type = bool + default = true +} + +variable "create_egress_only_igw" { + description = "Controls if an Egress Only Internet Gateway is created and its related routes." + type = bool + default = true +} + +variable "flow_log_file_format" { + description = "(Optional) The format for the flow log. Valid values: `plain-text`, `parquet`." + type = string + default = "plain-text" + validation { + condition = can(regex("^(plain-text|parquet)$", + var.flow_log_file_format)) + error_message = "ERROR valid values: plain-text, parquet." + } +} + +variable "flow_log_hive_compatible_partitions" { + description = "(Optional) Indicates whether to use Hive-compatible prefixes for flow logs stored in Amazon S3." + type = bool + default = false +} + +variable "flow_log_per_hour_partition" { + description = "(Optional) Indicates whether to partition the flow log per hour. This reduces the cost and response time for queries." + type = bool + default = false +} + +variable "enable_nat_gateway_for_db" { + description = "Controls if nat gateway for db should be enabled" + type = bool + default = true +} diff --git a/modules/aws-vpc/versions.tf b/modules/aws-vpc/versions.tf new file mode 100644 index 0000000..5a9fd0f --- /dev/null +++ b/modules/aws-vpc/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.63" + } + } +} diff --git a/modules/aws-vpc/vpc-flow-logs.tf b/modules/aws-vpc/vpc-flow-logs.tf new file mode 100644 index 0000000..209ccc5 --- /dev/null +++ b/modules/aws-vpc/vpc-flow-logs.tf @@ -0,0 +1,113 @@ +locals { + # Only create flow log if user selected to create a VPC as well + enable_flow_log = var.create_vpc && var.enable_flow_log + + create_flow_log_cloudwatch_iam_role = local.enable_flow_log && var.flow_log_destination_type != "s3" && var.create_flow_log_cloudwatch_iam_role + create_flow_log_cloudwatch_log_group = local.enable_flow_log && var.flow_log_destination_type != "s3" && var.create_flow_log_cloudwatch_log_group + + flow_log_destination_arn = local.create_flow_log_cloudwatch_log_group ? aws_cloudwatch_log_group.flow_log[0].arn : var.flow_log_destination_arn + flow_log_iam_role_arn = var.flow_log_destination_type != "s3" && local.create_flow_log_cloudwatch_iam_role ? aws_iam_role.vpc_flow_log_cloudwatch[0].arn : var.flow_log_cloudwatch_iam_role_arn +} + +################################################################################ +# Flow Log +################################################################################ + +resource "aws_flow_log" "this" { + count = local.enable_flow_log ? 1 : 0 + + log_destination_type = var.flow_log_destination_type + log_destination = local.flow_log_destination_arn + log_format = var.flow_log_log_format + iam_role_arn = local.flow_log_iam_role_arn + traffic_type = var.flow_log_traffic_type + vpc_id = local.vpc_id + max_aggregation_interval = var.flow_log_max_aggregation_interval + + dynamic "destination_options" { + for_each = var.flow_log_destination_type == "s3" ? [true] : [] + + content { + file_format = var.flow_log_file_format + hive_compatible_partitions = var.flow_log_hive_compatible_partitions + per_hour_partition = var.flow_log_per_hour_partition + } + } + + tags = merge(var.tags, var.vpc_flow_log_tags) +} + +################################################################################ +# Flow Log CloudWatch +################################################################################ + +resource "aws_cloudwatch_log_group" "flow_log" { + count = local.create_flow_log_cloudwatch_log_group ? 1 : 0 + + name = "${var.flow_log_cloudwatch_log_group_name_prefix}${local.vpc_id}" + retention_in_days = var.flow_log_cloudwatch_log_group_retention_in_days + kms_key_id = var.flow_log_cloudwatch_log_group_kms_key_id + + tags = merge(var.tags, var.vpc_flow_log_tags) +} + +resource "aws_iam_role" "vpc_flow_log_cloudwatch" { + count = local.create_flow_log_cloudwatch_iam_role ? 1 : 0 + + name_prefix = "vpc-flow-log-role-" + assume_role_policy = data.aws_iam_policy_document.flow_log_cloudwatch_assume_role[0].json + permissions_boundary = var.vpc_flow_log_permissions_boundary + + tags = merge(var.tags, var.vpc_flow_log_tags) +} + +data "aws_iam_policy_document" "flow_log_cloudwatch_assume_role" { + count = local.create_flow_log_cloudwatch_iam_role ? 1 : 0 + + statement { + sid = "AWSVPCFlowLogsAssumeRole" + + principals { + type = "Service" + identifiers = ["vpc-flow-logs.amazonaws.com"] + } + + effect = "Allow" + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role_policy_attachment" "vpc_flow_log_cloudwatch" { + count = local.create_flow_log_cloudwatch_iam_role ? 1 : 0 + + role = aws_iam_role.vpc_flow_log_cloudwatch[0].name + policy_arn = aws_iam_policy.vpc_flow_log_cloudwatch[0].arn +} + +resource "aws_iam_policy" "vpc_flow_log_cloudwatch" { + count = local.create_flow_log_cloudwatch_iam_role ? 1 : 0 + + name_prefix = "vpc-flow-log-to-cloudwatch-" + policy = data.aws_iam_policy_document.vpc_flow_log_cloudwatch[0].json + tags = merge(var.tags, var.vpc_flow_log_tags) +} + +data "aws_iam_policy_document" "vpc_flow_log_cloudwatch" { + count = local.create_flow_log_cloudwatch_iam_role ? 1 : 0 + + statement { + sid = "AWSVPCFlowLogsPushToCloudWatch" + + effect = "Allow" + + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + ] + + resources = ["*"] + } +} diff --git a/modules/aws-waf-ip-set/README.md b/modules/aws-waf-ip-set/README.md new file mode 100644 index 0000000..2987c3c --- /dev/null +++ b/modules/aws-waf-ip-set/README.md @@ -0,0 +1,62 @@ +### Used to create whitelist and blacklist for to feed waf rules. + + +Example -> Create a trusted ip set, basically you need to fill the necessary variables, it's dynamically generated by module +``` + ip_set_name = "${local.env}-TrustedIPSet" + ip_set_addresses = [ + "65.20.23.23/32 + ] + ip_set_description = "${local.env} Trusted IP Set" +``` + + +Example -> Create a blacklist ip set, basically you need to fill the necessary variables, it's dynamically generated by module + +``` + blacklist_ip_set_name = "${local.env}-BlacklistIPSet" + blacklist_ip_set_description = "Black list consists of blocked IP addresses" + blacklist_ip_addresses = [] +``` + + + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_wafv2_ip_set.blacklist_ip_set](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_ip_set) | resource | +| [aws_wafv2_ip_set.whitelist_ip_set](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_ip_set) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [blacklist\_ip\_addresses](#input\_blacklist\_ip\_addresses) | Blacklist IP addresses | `list(string)` | `[]` | no | +| [blacklist\_ip\_set\_description](#input\_blacklist\_ip\_set\_description) | Description of the IP set | `string` | `""` | no | +| [blacklist\_ip\_set\_name](#input\_blacklist\_ip\_set\_name) | IP Set Name | `string` | `""` | no | +| [ip\_set\_addresses](#input\_ip\_set\_addresses) | IP addresses | `list(string)` | `[]` | no | +| [ip\_set\_description](#input\_ip\_set\_description) | Description of the IP set | `string` | `""` | no | +| [ip\_set\_name](#input\_ip\_set\_name) | IP Set Name | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [blacklist\_ip\_set\_arn](#output\_blacklist\_ip\_set\_arn) | arn of blacklist ip set | +| [whitelist\_ip\_set\_arn](#output\_whitelist\_ip\_set\_arn) | arn of whitelist ip set | diff --git a/modules/aws-waf-ip-set/main.tf b/modules/aws-waf-ip-set/main.tf new file mode 100644 index 0000000..e70b5ab --- /dev/null +++ b/modules/aws-waf-ip-set/main.tf @@ -0,0 +1,29 @@ +resource "aws_wafv2_ip_set" "blacklist_ip_set" { + + ip_address_version = "IPV4" + scope = var.blacklist_scope + description = var.blacklist_ip_set_description + name = var.blacklist_ip_set_name + addresses = var.blacklist_ip_addresses + + +} + +resource "aws_wafv2_ip_set" "whitelist_ip_set" { + ip_address_version = "IPV4" + scope = var.whitelist_scope + description = var.ip_set_description + name = var.ip_set_name + addresses = var.ip_set_addresses + +} + + +resource "aws_wafv2_ip_set" "ip_addresses" { + for_each = var.ip_address_groups + ip_address_version = each.value.ip_address_version + scope = each.value.whitelist_scope + description = each.value.ip_set_description + name = each.value.ip_set_name + addresses = each.value.ip_set_addresses +} diff --git a/modules/aws-waf-ip-set/output.tf b/modules/aws-waf-ip-set/output.tf new file mode 100644 index 0000000..4abfe4c --- /dev/null +++ b/modules/aws-waf-ip-set/output.tf @@ -0,0 +1,15 @@ +output "blacklist_ip_set_arn" { + value = aws_wafv2_ip_set.blacklist_ip_set.arn + description = "arn of blacklist ip set" +} + +output "whitelist_ip_set_arn" { + value = aws_wafv2_ip_set.whitelist_ip_set.arn + description = "arn of whitelist ip set" +} + +output "dynamic_waf_ip_set_arns" { + value = { for key, ip_set in aws_wafv2_ip_set.ip_addresses : key => ip_set.arn } + description = "Map of ARNs for dynamically created IP address groups" +} + diff --git a/modules/aws-waf-ip-set/variables.tf b/modules/aws-waf-ip-set/variables.tf new file mode 100644 index 0000000..5e8fbd9 --- /dev/null +++ b/modules/aws-waf-ip-set/variables.tf @@ -0,0 +1,58 @@ +variable "ip_set_addresses" { + description = "IP addresses" + type = list(string) + default = [] +} + +variable "ip_set_name" { + description = "IP Set Name" + default = "" +} + +variable "ip_set_description" { + description = "Description of the IP set" + default = "" + +} + +variable "whitelist_scope" { + description = "" + default = "REGIONAL" +} + +variable "blacklist_ip_addresses" { + description = "Blacklist IP addresses" + type = list(string) + default = [] +} + +variable "blacklist_ip_set_name" { + description = "IP Set Name" + default = "" +} + +variable "blacklist_ip_set_description" { + description = "Description of the IP set" + default = "" + +} + +variable "blacklist_scope" { + description = "" + default = "REGIONAL" +} + + + + +variable "ip_address_groups" { + description = "Map of IP address groups for creating multiple AWS WAFv2 IP Sets" + type = map(object({ + ip_address_version = string + whitelist_scope = string + ip_set_description = string + ip_set_name = string + ip_set_addresses = list(string) + })) + default = {} +} diff --git a/modules/aws-waf/README.md b/modules/aws-waf/README.md new file mode 100644 index 0000000..bc56d31 --- /dev/null +++ b/modules/aws-waf/README.md @@ -0,0 +1,389 @@ +# terraform-aws-waf-webaclv2 + + +Terraform module to configure WAF Web ACL V2 for Application Load Balancer or Cloudfront distribution. + +Supported WAF v2 components: + +- Module supports all AWS managed rules defined in https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html. +- Associating WAFv2 ACL with one or more Application Load Balancers (ALB) +- Blocking IP Sets +- Rate limiting IPs (and optional scopedown statements) +- Byte Match statements +- Geo set statements +- Logical Statements (AND, OR, NOT) +- Size constraint statements +- Label Match statements +- Regex Pattern Match statements +- Custom responses + +## Terraform versions + +Terraform 0.13+ Pin module version to `~> v3.0`. Submit pull-requests to `main` branch. +Terraform 0.12 < 0.13. Pin module version to `~> v1.0`. + +## Usage + +Please pin down version of this module to exact version + +If referring directly to the code instead of a pinned version, take note that from release 3.0.0 all future changes will only be made to the `main` branch. + +```hcl +module "waf" { + source = "kloia/waf/aws" + version = "~> 3.0.0" + + name_prefix = "test-waf-setup" + alb_arn = module.alb.arn + + scope = "REGIONAL" + + create_alb_association = true + + allow_default_action = true # set to allow if not specified + + visibility_config = { + metric_name = "test-waf-setup-waf-main-metrics" + } + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + metric_name = "AWSManagedRulesCommonRuleSet-metric" + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + name = "AWSManagedRulesKnownBadInputsRuleSet-rule-2" + priority = "2" + + override_action = "count" + + visibility_config = { + metric_name = "AWSManagedRulesKnownBadInputsRuleSet-metric" + } + + managed_rule_group_statement = { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + }, + { + name = "AWSManagedRulesPHPRuleSet-rule-3" + priority = "3" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesPHPRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesPHPRuleSet" + vendor_name = "AWS" + } + }, + ### Byte Match Rule example + # Refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#byte-match-statement + # for all of the options available. + # Additional examples available in the examples directory + { + name = "ByteMatchRule-4" + priority = "4" + + action = "count" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "ByteMatchRule-metric" + sampled_requests_enabled = false + } + + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" + } + }, + ### Geo Match Rule example + { + name = "GeoMatchRule-5" + priority = "5" + + action = "allow" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "GeoMatchRule-metric" + sampled_requests_enabled = false + } + + geo_match_statement = { + country_codes = ["NL", "GB", "US"] + } + }, + ### IP Set Rule example + { + name = "IpSetRule-6" + priority = "6" + + action = "allow" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "IpSetRule-metric" + sampled_requests_enabled = false + } + + ip_set_reference_statement = { + arn = "arn:aws:wafv2:eu-west-1:111122223333:regional/ipset/ip-set-test/a1bcdef2-1234-123a-abc0-1234a5bc67d8" + } + }, + ### IP Rate Based Rule example + { + name = "IpRateBasedRule-7" + priority = "7" + + action = "block" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "IpRateBasedRule-metric" + sampled_requests_enabled = false + } + + rate_based_statement = { + limit = 100 + aggregate_key_type = "IP" + # Optional scope_down_statement to refine what gets rate limited + scope_down_statement = { + not_statement = { + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" + } + } + } + } + }, + ### NOT rule example (can be applied to byte_match, geo_match, and ip_set rules) + { + name = "NotByteMatchRule-8" + priority = "8" + + action = "count" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "NotByteMatchRule-metric" + sampled_requests_enabled = false + } + + not_statement = { + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" + } + } + }, + ### Size constraint Rule example + # Refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#size-constraint-statement + # for all of the options available. + # Additional examples available in the examples directory + { + name = "BodySizeConstraint" + priority = 0 + size_constraint_statement = { + field_to_match = { + body = "{}" + } + comparison_operator = "GT" + size = 8192 + priority = 0 + type = "NONE" + } + + action = "count" + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "BodySizeConstraint" + sampled_requests_enabled = true + } + }, + ### Regex Pattern Set Reference Rule example + # Refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#regex-pattern-set-reference-statement + # for all of the options available. + # Additional examples available in the examples directory + { + name = "MatchRegexRule-1" + priority = "1" + + action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "RegexBadBotsUserAgent-metric" + sampled_requests_enabled = false + } + + # You need to previously create you regex pattern + # Refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_regex_pattern_set + # for all of the options available. + regex_pattern_set_reference_statement = { + arn = aws_wafv2_regex_pattern_set.example.arn + field_to_match = { + single_header = { + name = "user-agent" + } + } + priority = 0 + type = "LOWERCASE" # The text transformation type + } + } + ] + + tags = { + "Name" = "test-waf-setup" + "Env" = "test" + } +} +``` + +### Cloudfront configuration + +```hcl +provider "aws" { + alias = "us-east" + + version = ">= 3.38" + region = "us-east-1" +} + +module "waf" { + providers = { + aws = aws.us-east + } + + source = "kloia/waf/aws" + version = "~> 3.0.0" + + name_prefix = "test-waf-setup-cloudfront" + scope = "CLOUDFRONT" + create_alb_association = false + ... +} +``` + +## Logging configuration + +When you enable logging configuration for WAFv2. Remember to follow naming convention defined in https://docs.aws.amazon.com/waf/latest/developerguide/logging.html. + +Importantly, make sure that Amazon Kinesis Data Firehose is using a name starting with the prefix aws-waf-logs-. + +## Examples + +* [WAF ACL](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/core) +* [WAF ACL with configuration logging](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-logging-configuration) +* [WAF ACL with ip rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-ip-rules) +* [WAF ACL with bytematch rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-bytematch-rules) +* [WAF ACL with geo match rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-geo-rules) +* [WAF ACL with and / or rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-and-or-rules) +* [WAF ACL with label match rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-labelmatch-rules) +* [WAF ACL with regex pattern rules](https://github.com/kloia/terraform-modules/tree/main/terraform-aws-waf/examples/wafv2-regex-pattern-rules) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.7 | +| [aws](#requirement\_aws) | >= 4.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_wafv2_web_acl.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl) | resource | +| [aws_wafv2_web_acl_association.alb_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource | +| [aws_wafv2_web_acl_association.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource | +| [aws_wafv2_web_acl_logging_configuration.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alb\_arn](#input\_alb\_arn) | Application Load Balancer ARN | `string` | `""` | no | +| [alb\_arn\_list](#input\_alb\_arn\_list) | Application Load Balancer ARN list | `list(string)` | `[]` | no | +| [allow\_default\_action](#input\_allow\_default\_action) | Set to `true` for WAF to allow requests by default. Set to `false` for WAF to block requests by default. | `bool` | `true` | no | +| [create\_alb\_association](#input\_create\_alb\_association) | Whether to create alb association with WAF web acl | `bool` | `true` | no | +| [create\_logging\_configuration](#input\_create\_logging\_configuration) | Whether to create logging configuration in order start logging from a WAFv2 Web ACL to Amazon Kinesis Data Firehose. | `bool` | `false` | no | +| [custom\_response\_bodies](#input\_custom\_response\_bodies) | Custom response bodies to be referenced on a per rule basis. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#custom-response-body |
list(object({
key = string
content = string
content_type = string
}))
| `[]` | no | +| [description](#input\_description) | A friendly description of the WebACL | `string` | `null` | no | +| [enabled](#input\_enabled) | Whether to create the resources. Set to `false` to prevent the module from creating any resources | `bool` | `true` | no | +| [log\_destination\_configs](#input\_log\_destination\_configs) | The Amazon Kinesis Data Firehose Amazon Resource Name (ARNs) that you want to associate with the web ACL. Currently, only 1 ARN is supported. | `list(string)` | `[]` | no | +| [logging\_filter](#input\_logging\_filter) | A configuration block that specifies which web requests are kept in the logs and which are dropped. You can filter on the rule action and on the web request labels that were applied by matching rules during web ACL evaluation. | `any` | `{}` | no | +| [name\_prefix](#input\_name\_prefix) | Name prefix used to create resources. | `string` | n/a | yes | +| [redacted\_fields](#input\_redacted\_fields) | The parts of the request that you want to keep out of the logs. Up to 100 `redacted_fields` blocks are supported. | `any` | `[]` | no | +| [rules](#input\_rules) | List of WAF rules. | `any` | `[]` | no | +| [scope](#input\_scope) | Specifies whether this is for an AWS CloudFront distribution or for a regional application. Valid values are CLOUDFRONT or REGIONAL. To work with CloudFront, you must also specify the region us-east-1 (N. Virginia) on the AWS provider. | `string` | `"REGIONAL"` | no | +| [tags](#input\_tags) | A map of tags (key-value pairs) passed to resources. | `map(string)` | `{}` | no | +| [visibility\_config](#input\_visibility\_config) | Visibility config for WAFv2 web acl. https://www.terraform.io/docs/providers/aws/r/wafv2_web_acl.html#visibility-configuration | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [web\_acl\_arn](#output\_web\_acl\_arn) | The ARN of the WAFv2 WebACL. | +| [web\_acl\_assoc\_acl\_arn](#output\_web\_acl\_assoc\_acl\_arn) | The ARN of the Web ACL attached to the Web ACL Association | +| [web\_acl\_assoc\_alb\_list\_acl\_arn](#output\_web\_acl\_assoc\_alb\_list\_acl\_arn) | The ARN of the Web ACL attached to the Web ACL Association for the alb\_list resource | +| [web\_acl\_assoc\_alb\_list\_id](#output\_web\_acl\_assoc\_alb\_list\_id) | The ID of the Web ACL Association for the alb\_list resource | +| [web\_acl\_assoc\_alb\_list\_resource\_arn](#output\_web\_acl\_assoc\_alb\_list\_resource\_arn) | The ARN of the ALB attached to the Web ACL Association for the alb\_list resource | +| [web\_acl\_assoc\_id](#output\_web\_acl\_assoc\_id) | The ID of the Web ACL Association | +| [web\_acl\_assoc\_resource\_arn](#output\_web\_acl\_assoc\_resource\_arn) | The ARN of the ALB attached to the Web ACL Association | +| [web\_acl\_capacity](#output\_web\_acl\_capacity) | The web ACL capacity units (WCUs) currently being used by this web ACL. | +| [web\_acl\_id](#output\_web\_acl\_id) | The ID of the WAFv2 WebACL. | +| [web\_acl\_name](#output\_web\_acl\_name) | The name of the WAFv2 WebACL. | +| [web\_acl\_rule\_names](#output\_web\_acl\_rule\_names) | List of created rule names | +| [web\_acl\_visibility\_config\_name](#output\_web\_acl\_visibility\_config\_name) | The web ACL visibility config name | + \ No newline at end of file diff --git a/modules/aws-waf/examples/core/README.md b/modules/aws-waf/examples/core/README.md new file mode 100644 index 0000000..c2e598a --- /dev/null +++ b/modules/aws-waf/examples/core/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/modules/aws-waf/examples/core/main.tf b/modules/aws-waf/examples/core/main.tf new file mode 100644 index 0000000..f24f381 --- /dev/null +++ b/modules/aws-waf/examples/core/main.tf @@ -0,0 +1,122 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = "test-waf-setup" + allow_default_action = true + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "test-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + # Uses optional excluded_rules to exclude certain managed rules + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + name = "AWSManagedRulesKnownBadInputsRuleSet-rule-2" + priority = "2" + + override_action = "count" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesKnownBadInputsRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + }, + { + # Uses an optional scope down statement to further refine what the rule is being applied to + name = "AWSManagedRulesPHPRuleSet-rule-3" + priority = "3" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesPHPRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesPHPRuleSet" + vendor_name = "AWS" + + # Optional scope_down_statement + scope_down_statement = { + geo_match_statement = { + country_codes = ["NL", "GB", "US"] + } + } + } + }, + { + name = "AWSManagedRulesBotControlRuleSet-rule-4" + priority = "4" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesBotControlRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + } + }, + { + name = "block-nl-us-traffic" + priority = "5" + action = "block" + + geo_match_statement = { + country_codes = ["NL", "US"] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-and-or-rules/main.tf b/modules/aws-waf/examples/wafv2-and-or-rules/main.tf new file mode 100644 index 0000000..16ee6d9 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-and-or-rules/main.tf @@ -0,0 +1,130 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# IP set resources +##### +resource "aws_wafv2_ip_set" "custom_ip_set" { + name = "${var.name_prefix}-custom-ip-set" + + scope = "REGIONAL" + ip_address_version = "IPV4" + + addresses = [ + "10.0.0.0/16", + "10.10.0.0/16" + ] +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + + allow_default_action = true + + scope = "REGIONAL" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + ### AND rule example + name = "block-specific-uri-path-and-requests-from-nl-gb-and-us" + priority = 2 + action = "block" + + and_statement = { + statements = [ # 2 or more statements are required for AND + { + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" + } + }, + { + geo_match_statement = { + country_codes = ["NL", "GB", "US"] + } + } + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + ### OR rule example + name = "block-specific-ip-set-or-body-contains-hotmail" + priority = 3 + action = "block" + + or_statement = { + statements = [ # 2 or more statements are required for OR + { + ip_set_reference_statement = { + arn = aws_wafv2_ip_set.custom_ip_set.arn + } + }, + { + byte_match_statement = { + field_to_match = { + body = "{}" + } + positional_constraint = "CONTAINS" + search_string = "@hotmail.com" + priority = 0 + type = "NONE" + } + } + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-and-or-rules/outputs.tf b/modules/aws-waf/examples/wafv2-and-or-rules/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-and-or-rules/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-and-or-rules/variables.tf b/modules/aws-waf/examples/wafv2-and-or-rules/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-and-or-rules/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-bytematch-rules/main.tf b/modules/aws-waf/examples/wafv2-bytematch-rules/main.tf new file mode 100644 index 0000000..27f7869 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-bytematch-rules/main.tf @@ -0,0 +1,134 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + + allow_default_action = true + + scope = "REGIONAL" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + name = "block-specific-uri-path" + priority = "2" + action = "block" + + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" # The text transformation type + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + name = "block-if-request-body-contains-hotmail-email" + priority = "3" + action = "block" + + byte_match_statement = { + field_to_match = { + body = "{}" + } + positional_constraint = "CONTAINS" + search_string = "@hotmail.com" + priority = 0 + type = "NONE" # The text transformation type + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + name = "block-all-post-requests" + priority = "4" + action = "block" + + byte_match_statement = { + field_to_match = { + method = "{}" + } + positional_constraint = "EXACTLY" + search_string = "post" + priority = 0 + type = "LOWERCASE" # The text transformation type + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + # Blocks a single user by checking the username header + name = "block-single-user" + priority = "5" + action = "block" + + byte_match_statement = { + field_to_match = { + single_header = { + name = "Username" + } + } + positional_constraint = "EXACTLY" + search_string = "testuser" + priority = 0 + type = "LOWERCASE" # The text transformation type + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-bytematch-rules/outputs.tf b/modules/aws-waf/examples/wafv2-bytematch-rules/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-bytematch-rules/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-bytematch-rules/variables.tf b/modules/aws-waf/examples/wafv2-bytematch-rules/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-bytematch-rules/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/main.tf b/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/main.tf new file mode 100644 index 0000000..3aa1be3 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/main.tf @@ -0,0 +1,64 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + allow_default_action = true + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + custom_response_bodies = [ + { + key = "403-forbidden-json" + content = "{\"code\":403,\"message\":\"Forbidden\"}" + content_type = "APPLICATION_JSON" + } + ] + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule" + priority = "0" + + override_action = "count" + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = true + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + } + }, + { + name = "JsonResponse" + priority = "1" + action = "block" + custom_response = { + custom_response_body_key = "403-forbidden-json" + response_code = 403 + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesCommonRuleSetJsonResponse" + sampled_requests_enabled = true + } + + label_match_statement = { + key = "awswaf:managed:aws:" + scope = "NAMESPACE" + } + }, + ] +} diff --git a/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/variables.tf b/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-json-response-managed-rule-group/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-custom-response-code/main.tf b/modules/aws-waf/examples/wafv2-custom-response-code/main.tf new file mode 100644 index 0000000..6c7509a --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response-code/main.tf @@ -0,0 +1,66 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + allow_default_action = true + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "ip-rate-based" + priority = "6" + action = "block" + rule_labels = ["LabelNameA"] + + custom_response = { + response_code = 412 + } + + rate_based_statement = { + limit = 2000 # Note this is by default in a 5-min span, ref: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#rate_based_statement + aggregate_key_type = "IP" + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "IPRateBased-metric" + sampled_requests_enabled = false + } + }, + { + # Note: custom responses can not be applied to AWS managed rule groups directly. Must use a label technique, ref: https://aws.amazon.com/blogs/security/how-to-customize-behavior-of-aws-managed-rules-for-aws-waf/ + name = "AWSManagedRulesBotControlRuleSet-rule-0" + priority = "0" + + # Note: override_action is for managed rule sets only, otherwise would be action + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS", + excluded_rule = [ + "SignalNonBrowserUserAgent", + "CategoryHttpLibrary", + "SignalAutomatedBrowser", + "CategoryMonitoring" + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesBotControlRuleSet-metric" + sampled_requests_enabled = false + } + } + ] +} diff --git a/modules/aws-waf/examples/wafv2-custom-response-code/outputs.tf b/modules/aws-waf/examples/wafv2-custom-response-code/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response-code/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-custom-response-code/variables.tf b/modules/aws-waf/examples/wafv2-custom-response-code/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response-code/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-custom-response/main.tf b/modules/aws-waf/examples/wafv2-custom-response/main.tf new file mode 100644 index 0000000..f11c729 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response/main.tf @@ -0,0 +1,89 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + allow_default_action = true + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + custom_response_bodies = [ + { + key = "custom_response_body_1", + content = "You are not authorized to access this resource.", + content_type = "TEXT_PLAIN" + }, + { + key = "custom_response_body_2", + content = "You there are not authorized to access this resource.", + content_type = "TEXT_PLAIN" + } + ] + + rules = [ + { + name = "ip-rate-based" + priority = "6" + action = "block" + rule_labels = ["LabelNameA", "LabelNameB"] + + custom_response = { + custom_response_body_key = "default_1", + response_code = 412 + response_headers = [ + { + name = "X-Custom-Header-1" + value = "You are not authorized to access this resource." + }, + { + name = "X-Custom-Header-2" + value = "Not authorized to access this resource." + } + ] + } + + rate_based_statement = { + limit = 2000 # Note this is by default in a 5-min span, ref: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#rate_based_statement + aggregate_key_type = "IP" + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "IPRateBased-metric" + sampled_requests_enabled = false + } + }, + { + # Note: custom responses can not be applied to AWS managed rule groups directly. Must use a label technique, ref: https://aws.amazon.com/blogs/security/how-to-customize-behavior-of-aws-managed-rules-for-aws-waf/ + name = "AWSManagedRulesBotControlRuleSet-rule-0" + priority = "0" + + # Note: override_action is for managed rule sets only, otherwise would be action + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS", + excluded_rule = [ + "SignalNonBrowserUserAgent", + "CategoryHttpLibrary", + "SignalAutomatedBrowser", + "CategoryMonitoring" + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesBotControlRuleSet-metric" + sampled_requests_enabled = false + } + } + ] +} diff --git a/modules/aws-waf/examples/wafv2-custom-response/outputs.tf b/modules/aws-waf/examples/wafv2-custom-response/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-custom-response/variables.tf b/modules/aws-waf/examples/wafv2-custom-response/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-custom-response/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-geo-rules/main.tf b/modules/aws-waf/examples/wafv2-geo-rules/main.tf new file mode 100644 index 0000000..1a9bea4 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-geo-rules/main.tf @@ -0,0 +1,69 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + + allow_default_action = true + + scope = "REGIONAL" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + name = "allow-nl-gb-us-traffic-only" + priority = "2" + action = "allow" + + geo_match_statement = { + country_codes = ["NL", "GB", "US"], + forwarded_ip_config = { + header_name = "X-Forwarded-For" + fallback_behavior = "NO_MATCH" + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-geo-rules/outputs.tf b/modules/aws-waf/examples/wafv2-geo-rules/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-geo-rules/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-geo-rules/variables.tf b/modules/aws-waf/examples/wafv2-geo-rules/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-geo-rules/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-ip-rules/main.tf b/modules/aws-waf/examples/wafv2-ip-rules/main.tf new file mode 100644 index 0000000..4cc8d0f --- /dev/null +++ b/modules/aws-waf/examples/wafv2-ip-rules/main.tf @@ -0,0 +1,184 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# IP set resources +##### +resource "aws_wafv2_ip_set" "block_ip_set" { + name = "${var.name_prefix}-generated-ips" + + scope = "REGIONAL" + ip_address_version = "IPV4" + + # generates a list of all /16s + addresses = formatlist("%s.0.0.0/16", range(0, 50)) +} + +resource "aws_wafv2_ip_set" "custom_ip_set" { + name = "${var.name_prefix}-custom-ip-set" + + scope = "REGIONAL" + ip_address_version = "IPV4" + + addresses = [ + "10.0.0.0/16", + "10.10.0.0/16" + ] +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + + allow_default_action = true + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "AWSManagedRulesCommonRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesCommonRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SizeRestrictions_QUERYSTRING", + "SizeRestrictions_BODY", + "GenericRFI_QUERYARGUMENTS" + ] + } + }, + { + name = "ip-rate-limit" + priority = "2" + action = "count" + + rate_based_statement = { + limit = 100 + aggregate_key_type = "IP" + + # Optional scope_down_statement to refine what gets rate limited + scope_down_statement = { + not_statement = { # not statement to rate limit everything except the following path + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/path/to/match" + priority = 0 + type = "NONE" + } + } + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + name = "ip-rate-limit-with-or-scope-down" + priority = "3" + action = "count" + + rate_based_statement = { + limit = 100 + aggregate_key_type = "IP" + + # Optional scope_down_statement to refine what gets rate limited + scope_down_statement = { + or_statement = { # OR and AND statements require 2 or more statements to function + statements = [ + { + byte_match_statement = { + field_to_match = { + uri_path = "{}" + } + positional_constraint = "STARTS_WITH" + search_string = "/api" + priority = 0 + type = "NONE" + } + }, + { + byte_match_statement = { + field_to_match = { + body = "{}" + } + positional_constraint = "CONTAINS" + search_string = "@gmail.com" + priority = 0 + type = "NONE" + } + }, + { + geo_match_statement = { + country_codes = ["NL", "GB", "US"] + } + } + ] + } + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + name = "allow-custom-ip-set" + priority = "4" + action = "count" + + ip_set_reference_statement = { + arn = aws_wafv2_ip_set.custom_ip_set.arn + } + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + }, + { + name = "block-ip-set" + priority = "5" + action = "block" + + ip_set_reference_statement = { + arn = aws_wafv2_ip_set.block_ip_set.arn + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "test-waf-setup-waf-ip-set-block-metrics" + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-ip-rules/outputs.tf b/modules/aws-waf/examples/wafv2-ip-rules/outputs.tf new file mode 100644 index 0000000..ea26ab6 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-ip-rules/outputs.tf @@ -0,0 +1,34 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} + +output "custom_ip_set_arn" { + description = "The ARN of the Custom IP Set" + value = aws_wafv2_ip_set.custom_ip_set.arn +} + +output "block_ip_set_arn" { + description = "The ARN of the Block IP Set" + value = aws_wafv2_ip_set.block_ip_set.arn +} diff --git a/modules/aws-waf/examples/wafv2-ip-rules/variables.tf b/modules/aws-waf/examples/wafv2-ip-rules/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-ip-rules/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-labelmatch-rules/main.tf b/modules/aws-waf/examples/wafv2-labelmatch-rules/main.tf new file mode 100644 index 0000000..b75b876 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-labelmatch-rules/main.tf @@ -0,0 +1,82 @@ +provider "aws" { + region = "eu-west-1" +} + +##### +# Web Application Firewall configuration +##### +module "waf" { + source = "../.." + + name_prefix = var.name_prefix + + allow_default_action = true + + scope = "REGIONAL" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "${var.name_prefix}-waf-setup-waf-main-metrics" + sampled_requests_enabled = false + } + + rules = [ + { + name = "AWSManagedRulesBotControlRuleSet-rule-1" + priority = "1" + + override_action = "none" + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "AWSManagedRulesBotControlRuleSet-metric" + sampled_requests_enabled = false + } + + managed_rule_group_statement = { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + excluded_rule = [ + "SignalNonBrowserUserAgent" + ] + } + }, + { + name = "block-specific-agent" + priority = "2" + action = "block" + + and_statement = { + statements = [ + { + label_match_statement = { + key = "awswaf:managed:aws:bot-control:signal:non_browser_user_agent" + scope = "LABEL" + } + }, + { + byte_match_statement = { + field_to_match = { + single_header = { + name = "user-agent" + } + } + positional_constraint = "CONTAINS" + search_string = "BadBot" + priority = 0 + type = "NONE" + } + } + ] + } + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = false + } + } + ] + + tags = { + "Environment" = "test" + } +} diff --git a/modules/aws-waf/examples/wafv2-labelmatch-rules/outputs.tf b/modules/aws-waf/examples/wafv2-labelmatch-rules/outputs.tf new file mode 100644 index 0000000..7a216ba --- /dev/null +++ b/modules/aws-waf/examples/wafv2-labelmatch-rules/outputs.tf @@ -0,0 +1,24 @@ +output "web_acl_name" { + description = "The name of the WAFv2 WebACL." + value = module.waf.web_acl_name +} + +output "web_acl_arn" { + description = "The ARN of the WAFv2 WebACL." + value = module.waf.web_acl_arn +} + +output "web_acl_capacity" { + description = "The web ACL capacity units (WCUs) currently being used by this web ACL." + value = module.waf.web_acl_capacity +} + +output "web_acl_visibility_config_name" { + description = "The web ACL visibility config name" + value = module.waf.web_acl_visibility_config_name +} + +output "web_acl_rule_names" { + description = "List of created rule names" + value = module.waf.web_acl_rule_names +} diff --git a/modules/aws-waf/examples/wafv2-labelmatch-rules/variables.tf b/modules/aws-waf/examples/wafv2-labelmatch-rules/variables.tf new file mode 100644 index 0000000..0519052 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-labelmatch-rules/variables.tf @@ -0,0 +1,5 @@ +variable "name_prefix" { + description = "A prefix used for naming resources." + type = string + default = "example" +} diff --git a/modules/aws-waf/examples/wafv2-logging-configuration/main.tf b/modules/aws-waf/examples/wafv2-logging-configuration/main.tf new file mode 100644 index 0000000..1112397 --- /dev/null +++ b/modules/aws-waf/examples/wafv2-logging-configuration/main.tf @@ -0,0 +1,198 @@ +provider "aws" { + region = "eu-west-1" +} + + + +##### +# Firehose configuration +##### + +resource "aws_s3_bucket" "bucket" { + bucket = "aws-waf-firehose-stream-test-bucket" + acl = "private" +} + +resource "aws_iam_role" "firehose" { + name = "firehose-stream-test-role" + + assume_role_policy = < and_Statement -> statement -> not_statement -> statement -> ip_set + + + dynamic "not_statement" { + for_each = length(lookup(statement.value, "not_statement", {})) == 0 ? [] : [lookup(statement.value, "not_statement", {})] + content { + statement { + # Scope down NOT ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(not_statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + # scope down NOT byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(not_statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # scope down NOT geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(not_statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # Scope down NOT label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(not_statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + } + } + } + } + } + } + } + + ### scope down OR statements (Requires at least two statements) + dynamic "or_statement" { + for_each = length(lookup(scope_down_statement.value, "or_statement", {})) == 0 ? [] : [lookup(scope_down_statement.value, "or_statement", {})] + content { + + dynamic "statement" { + for_each = lookup(or_statement.value, "statements", {}) + content { + # Scope down OR byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # Scope down OR geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # Scope down OR ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # Scope down OR label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + + # Scope down OR not_statement + dynamic "not_statement" { + for_each = length(lookup(statement.value, "not_statement", {})) == 0 ? [] : [lookup(statement.value, "not_statement", {})] + content { + statement { + # scope down NOT byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(not_statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # scope down NOT geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(not_statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # Scope down NOT label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(not_statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + } + } + } + } + } + } + } + } + } + } + } + + ### NOT STATEMENTS + dynamic "not_statement" { + for_each = length(lookup(rule.value, "not_statement", {})) == 0 ? [] : [lookup(rule.value, "not_statement", {})] + content { + statement { + + # NOT byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(not_statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # NOT geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(not_statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # NOT ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(not_statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # NOT label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(not_statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + + # NOT regex_pattern_set_reference_statement + dynamic "regex_pattern_set_reference_statement" { + for_each = length(lookup(not_statement.value, "regex_pattern_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "regex_pattern_set_reference_statement", {})] + content { + arn = lookup(regex_pattern_set_reference_statement.value, "arn") + dynamic "field_to_match" { + for_each = length(lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + text_transformation { + priority = lookup(regex_pattern_set_reference_statement.value, "priority") + type = lookup(regex_pattern_set_reference_statement.value, "type") + } + } + } + } + } + } + + ### AND STATEMENTS (Requires at least two statements) + dynamic "and_statement" { + for_each = length(lookup(rule.value, "and_statement", {})) == 0 ? [] : [lookup(rule.value, "and_statement", {})] + content { + + dynamic "statement" { + for_each = lookup(and_statement.value, "statements", {}) + content { + + # AND byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # AND geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # AND ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # AND label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + + # AND regex_pattern_set_reference_statement + dynamic "regex_pattern_set_reference_statement" { + for_each = length(lookup(statement.value, "regex_pattern_set_reference_statement", {})) == 0 ? [] : [lookup(statement.value, "regex_pattern_set_reference_statement", {})] + content { + arn = lookup(regex_pattern_set_reference_statement.value, "arn") + dynamic "field_to_match" { + for_each = length(lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + text_transformation { + priority = lookup(regex_pattern_set_reference_statement.value, "priority") + type = lookup(regex_pattern_set_reference_statement.value, "type") + } + } + } + + + ### AND not_statement + dynamic "not_statement" { + for_each = length(lookup(statement.value, "not_statement", {})) == 0 ? [] : [lookup(statement.value, "not_statement", {})] + content { + statement { + # AND not_statement byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(not_statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # AND not_statement geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(not_statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # AND not_statement ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(not_statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # AND not_statement label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(not_statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + } + } + } + + dynamic "size_constraint_statement" { + for_each = length(lookup(statement.value, "size_constraint_statement", {})) == 0 ? [] : [lookup(statement.value, "size_constraint_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(size_constraint_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(size_constraint_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + dynamic "single_query_argument" { + for_each = length(lookup(field_to_match.value, "single_query_argument", {})) == 0 ? [] : [lookup(field_to_match.value, "single_query_argument")] + content { + name = lower(lookup(single_query_argument.value, "name")) + } + } + } + } + comparison_operator = lookup(size_constraint_statement.value, "comparison_operator") + size = lookup(size_constraint_statement.value, "size") + text_transformation { + priority = lookup(size_constraint_statement.value, "priority") + type = lookup(size_constraint_statement.value, "type") + } + } + } + } + } + } + } + + ### OR STATEMENTS (Requires at least two statements) + dynamic "or_statement" { + for_each = length(lookup(rule.value, "or_statement", {})) == 0 ? [] : [lookup(rule.value, "or_statement", {})] + content { + + dynamic "statement" { + for_each = lookup(or_statement.value, "statements", {}) + content { + + # OR byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # OR geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # OR ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # OR label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + + # OR regex_pattern_set_reference_statement + dynamic "regex_pattern_set_reference_statement" { + for_each = length(lookup(statement.value, "regex_pattern_set_reference_statement", {})) == 0 ? [] : [lookup(statement.value, "regex_pattern_set_reference_statement", {})] + content { + arn = lookup(regex_pattern_set_reference_statement.value, "arn") + dynamic "field_to_match" { + for_each = length(lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + text_transformation { + priority = lookup(regex_pattern_set_reference_statement.value, "priority") + type = lookup(regex_pattern_set_reference_statement.value, "type") + } + } + } + + ### OR not_statement + dynamic "not_statement" { + for_each = length(lookup(statement.value, "not_statement", {})) == 0 ? [] : [lookup(statement.value, "not_statement", {})] + content { + statement { + # OR not_statement byte_match_statement + dynamic "byte_match_statement" { + for_each = length(lookup(not_statement.value, "byte_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "byte_match_statement", {})] + content { + dynamic "field_to_match" { + for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + positional_constraint = lookup(byte_match_statement.value, "positional_constraint") + search_string = lookup(byte_match_statement.value, "search_string") + text_transformation { + priority = lookup(byte_match_statement.value, "priority") + type = lookup(byte_match_statement.value, "type") + } + } + } + + # OR not_statement geo_match_statement + dynamic "geo_match_statement" { + for_each = length(lookup(not_statement.value, "geo_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "geo_match_statement", {})] + content { + country_codes = lookup(geo_match_statement.value, "country_codes") + dynamic "forwarded_ip_config" { + for_each = length(lookup(geo_match_statement.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(geo_match_statement.value, "forwarded_ip_config", {})] + content { + fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior") + header_name = lookup(forwarded_ip_config.value, "header_name") + } + } + } + } + + # OR not_statement ip_set_statement + dynamic "ip_set_reference_statement" { + for_each = length(lookup(not_statement.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "ip_set_reference_statement", {})] + content { + arn = lookup(ip_set_reference_statement.value, "arn") + } + } + + # OR not_statement label_match_statement + dynamic "label_match_statement" { + for_each = length(lookup(not_statement.value, "label_match_statement", {})) == 0 ? [] : [lookup(not_statement.value, "label_match_statement", {})] + content { + key = lookup(label_match_statement.value, "key") + scope = lookup(label_match_statement.value, "scope") + } + } + + # OR not_statement regex_pattern_set_reference_statement + dynamic "regex_pattern_set_reference_statement" { + for_each = length(lookup(not_statement.value, "regex_pattern_set_reference_statement", {})) == 0 ? [] : [lookup(not_statement.value, "regex_pattern_set_reference_statement", {})] + content { + arn = lookup(regex_pattern_set_reference_statement.value, "arn") + dynamic "field_to_match" { + for_each = length(lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(regex_pattern_set_reference_statement.value, "field_to_match", {})] + content { + dynamic "uri_path" { + for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + content {} + } + dynamic "all_query_arguments" { + for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + content {} + } + dynamic "body" { + for_each = length(lookup(field_to_match.value, "body", {})) == 0 ? [] : [lookup(field_to_match.value, "body")] + content {} + } + dynamic "method" { + for_each = length(lookup(field_to_match.value, "method", {})) == 0 ? [] : [lookup(field_to_match.value, "method")] + content {} + } + dynamic "query_string" { + for_each = length(lookup(field_to_match.value, "query_string", {})) == 0 ? [] : [lookup(field_to_match.value, "query_string")] + content {} + } + dynamic "single_header" { + for_each = length(lookup(field_to_match.value, "single_header", {})) == 0 ? [] : [lookup(field_to_match.value, "single_header")] + content { + name = lower(lookup(single_header.value, "name")) + } + } + } + } + text_transformation { + priority = lookup(regex_pattern_set_reference_statement.value, "priority") + type = lookup(regex_pattern_set_reference_statement.value, "type") + } + } + } + + } + } + } + } + } + } + } + } + + dynamic "visibility_config" { + for_each = length(lookup(rule.value, "visibility_config")) == 0 ? [] : [lookup(rule.value, "visibility_config", {})] + content { + cloudwatch_metrics_enabled = lookup(visibility_config.value, "cloudwatch_metrics_enabled", true) + metric_name = lookup(visibility_config.value, "metric_name", "${var.waf_web_acl_name}-default-rule-metric-name") + sampled_requests_enabled = lookup(visibility_config.value, "sampled_requests_enabled", true) + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = var.cw_metrics + metric_name = var.metric_name + sampled_requests_enabled = var.cw_metrics + } + +} + + + +resource "aws_wafv2_web_acl_logging_configuration" "main" { + count = var.enabled && var.create_logging_configuration ? 1 : 0 + + log_destination_configs = var.log_destination_configs + resource_arn = aws_wafv2_web_acl.waf_acl[0].arn + + dynamic "redacted_fields" { + for_each = var.redacted_fields + content { + dynamic "single_header" { + for_each = length(lookup(redacted_fields.value, "single_header", {})) == 0 ? [] : [lookup(redacted_fields.value, "single_header", {})] + content { + name = lookup(single_header.value, "name", null) + } + } + + } + } + + dynamic "logging_filter" { + for_each = length(var.logging_filter) == 0 ? [] : [var.logging_filter] + content { + default_behavior = lookup(logging_filter.value, "default_behavior", "KEEP") + + dynamic "filter" { + for_each = length(lookup(logging_filter.value, "filter", {})) == 0 ? [] : toset(lookup(logging_filter.value, "filter")) + content { + behavior = lookup(filter.value, "behavior") + requirement = lookup(filter.value, "requirement", "MEETS_ANY") + + dynamic "condition" { + for_each = length(lookup(filter.value, "condition", {})) == 0 ? [] : toset(lookup(filter.value, "condition")) + content { + dynamic "action_condition" { + for_each = length(lookup(condition.value, "action_condition", {})) == 0 ? [] : [lookup(condition.value, "action_condition", {})] + content { + action = lookup(action_condition.value, "action") + } + } + + dynamic "label_name_condition" { + for_each = length(lookup(condition.value, "label_name_condition", {})) == 0 ? [] : [lookup(condition.value, "label_name_condition", {})] + content { + label_name = lookup(label_name_condition.value, "label_name") + } + } + } + } + } + } + } + } +} + diff --git a/modules/aws-waf/outputs.tf b/modules/aws-waf/outputs.tf new file mode 100644 index 0000000..8b663bd --- /dev/null +++ b/modules/aws-waf/outputs.tf @@ -0,0 +1,9 @@ +output "web_acl_arn" { + description = "ARN of the Waf WebACL" + value = aws_wafv2_web_acl.waf_acl[0].arn +} + +output "web_acl_capacity" { + description = "Web ACL capacity units (WCUs) currently being used by this web ACL" + value = aws_wafv2_web_acl.waf_acl[0].capacity +} \ No newline at end of file diff --git a/modules/aws-waf/variables.tf b/modules/aws-waf/variables.tf new file mode 100644 index 0000000..103a366 --- /dev/null +++ b/modules/aws-waf/variables.tf @@ -0,0 +1,119 @@ +variable "create_waf" { + description = "condition to create" + default = true +} + +variable "waf_rule_name" { + description = "Name of the WAF rule" + default = "" +} + +variable "waf_web_acl_name" { + description = "Name of the WAF ACL" + default = "" +} + +variable "description" { + description = "Description of the IP set" + default = "" + +} + +variable "waf_rule_scope" { + description = "" + default = "REGIONAL" +} + + + + +variable "metric_name" { + description = "A friendly name of the CloudWatch metric." + default = "" + +} + +variable "cw_metrics" { + description = "CW metrics" + default = false +} + +variable "enabled" { + type = bool + description = "Whether to create the resources. Set to `false` to prevent the module from creating any resources" + default = true +} + + +variable "tags" { + description = "A map of tags (key-value pairs) passed to resources." + type = map(string) + default = {} +} + +variable "rules" { + description = "List of WAF rules." + type = any + default = [] +} + +variable "visibility_config" { + description = "Visibility config for WAFv2 web acl. https://www.terraform.io/docs/providers/aws/r/wafv2_web_acl.html#visibility-configuration" + type = map(string) + default = {} +} + +variable "create_logging_configuration" { + type = bool + description = "Whether to create logging configuration in order start logging from a WAFv2 Web ACL to Amazon Kinesis Data Firehose." + default = false +} + +variable "log_destination_configs" { + type = list(string) + description = "The Amazon Kinesis Data Firehose Amazon Resource Name (ARNs) that you want to associate with the web ACL. Currently, only 1 ARN is supported." + default = [] +} + +variable "redacted_fields" { + description = "The parts of the request that you want to keep out of the logs. Up to 100 `redacted_fields` blocks are supported." + type = any + default = [] +} + +variable "allow_default_action" { + type = bool + description = "Set to `true` for WAF to allow requests by default. Set to `false` for WAF to block requests by default." + default = true +} + +variable "block_default_action" { + type = bool + description = "Set to `true` for WAF to allow requests by default. Set to `false` for WAF to block requests by default." + default = true +} + + +variable "scope" { + type = string + description = "Specifies whether this is for an AWS CloudFront distribution or for a regional application. Valid values are CLOUDFRONT or REGIONAL. To work with CloudFront, you must also specify the region us-east-1 (N. Virginia) on the AWS provider." + default = "REGIONAL" +} + +variable "logging_filter" { + type = any + description = "A configuration block that specifies which web requests are kept in the logs and which are dropped. You can filter on the rule action and on the web request labels that were applied by matching rules during web ACL evaluation." + default = {} +} + + + +variable "custom_response_bodies" { + type = list(object({ + key = string + content = string + content_type = string + })) + description = "Custom response bodies to be referenced on a per rule basis. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl#custom-response-body" + default = [] +} \ No newline at end of file diff --git a/modules/terraform-aws-sqs/.gitignore b/modules/terraform-aws-sqs/.gitignore new file mode 100644 index 0000000..397af32 --- /dev/null +++ b/modules/terraform-aws-sqs/.gitignore @@ -0,0 +1,29 @@ +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# 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 + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/modules/terraform-aws-sqs/README.md b/modules/terraform-aws-sqs/README.md new file mode 100644 index 0000000..2325b3e --- /dev/null +++ b/modules/terraform-aws-sqs/README.md @@ -0,0 +1,259 @@ +# AWS SQS Terraform module + +Terraform module which creates SQS resources on AWS. + +## Usage + +### FIFO Queue + +```hcl +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + + name = "fifo" + + fifo_queue = true + + tags = { + Environment = "dev" + } +} +``` + +### Queue Encrypted w/ Customer Managed KMS Key + +```hcl +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + + name = "cmk" + + kms_master_key_id = "0d1ba9e8-9421-498a-9c8a-01e9772b2924" + kms_data_key_reuse_period_seconds = 3600 + + tags = { + Environment = "dev" + } +} +``` + +### Queue w/ Dead Letter Queue + +```hcl +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + + name = "example" + + create_dlq = true + redrive_policy = { + # default is 5 for this module + maxReceiveCount = 10 + } + + tags = { + Environment = "dev" + } +} +``` + +### Subscribe Queue to SNS Topic + +```hcl +module "sns" { + source = "terraform-aws-modules/sns/aws" + version = ">= 5.0" + + name = "pub-sub" + + topic_policy_statements = { + sqs = { + sid = "SQSSubscribe" + actions = [ + "sns:Subscribe", + "sns:Receive", + ] + + principals = [{ + type = "AWS" + identifiers = ["*"] + }] + + conditions = [{ + test = "StringLike" + variable = "sns:Endpoint" + values = [module.sqs.queue_arn] + }] + } + } + + subscriptions = { + sqs = { + protocol = "sqs" + endpoint = module.sqs.queue_arn + } + } + + tags = { + Environment = "dev" + } +} + +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + + name = "pub-sub" + + create_queue_policy = true + queue_policy_statements = { + sns = { + sid = "SNSPublish" + actions = ["sqs:SendMessage"] + + principals = [ + { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + ] + + conditions = [{ + test = "ArnEquals" + variable = "aws:SourceArn" + values = [module.sns.topic_arn] + }] + } + } + + tags = { + Environment = "dev" + } +} +``` + +## Examples + +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-sqs/tree/master/examples/complete) + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "sqs" { + source = "terraform-aws-modules/sqs/aws" + + # Disable creation of all resources + create = false + + # Enable creation of queue policy + create_queue_policy = true + + # Enable creation of dead letter queue + create_dlq = true + + # Enable creation of dead letter queue policy + create_dlq_queue_policy = true + + # ... omitted +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.36 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_sqs_queue.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue_policy.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_sqs_queue_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_sqs_queue_redrive_allow_policy.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_redrive_allow_policy) | resource | +| [aws_sqs_queue_redrive_allow_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_redrive_allow_policy) | resource | +| [aws_sqs_queue_redrive_policy.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_redrive_policy) | resource | +| [aws_sqs_queue_redrive_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_redrive_policy) | resource | +| [aws_iam_policy_document.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [content\_based\_deduplication](#input\_content\_based\_deduplication) | Enables content-based deduplication for FIFO queues | `bool` | `null` | no | +| [create](#input\_create) | Whether to create SQS queue | `bool` | `true` | no | +| [create\_dlq](#input\_create\_dlq) | Determines whether to create SQS dead letter queue | `bool` | `false` | no | +| [create\_dlq\_queue\_policy](#input\_create\_dlq\_queue\_policy) | Whether to create SQS queue policy | `bool` | `false` | no | +| [create\_queue\_policy](#input\_create\_queue\_policy) | Whether to create SQS queue policy | `bool` | `false` | no | +| [deduplication\_scope](#input\_deduplication\_scope) | Specifies whether message deduplication occurs at the message group or queue level | `string` | `null` | no | +| [delay\_seconds](#input\_delay\_seconds) | The time in seconds that the delivery of all messages in the queue will be delayed. An integer from 0 to 900 (15 minutes) | `number` | `null` | no | +| [dlq\_content\_based\_deduplication](#input\_dlq\_content\_based\_deduplication) | Enables content-based deduplication for FIFO queues | `bool` | `null` | no | +| [dlq\_deduplication\_scope](#input\_dlq\_deduplication\_scope) | Specifies whether message deduplication occurs at the message group or queue level | `string` | `null` | no | +| [dlq\_delay\_seconds](#input\_dlq\_delay\_seconds) | The time in seconds that the delivery of all messages in the queue will be delayed. An integer from 0 to 900 (15 minutes) | `number` | `null` | no | +| [dlq\_kms\_data\_key\_reuse\_period\_seconds](#input\_dlq\_kms\_data\_key\_reuse\_period\_seconds) | The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again. An integer representing seconds, between 60 seconds (1 minute) and 86,400 seconds (24 hours) | `number` | `null` | no | +| [dlq\_kms\_master\_key\_id](#input\_dlq\_kms\_master\_key\_id) | The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK | `string` | `null` | no | +| [dlq\_message\_retention\_seconds](#input\_dlq\_message\_retention\_seconds) | The number of seconds Amazon SQS retains a message. Integer representing seconds, from 60 (1 minute) to 1209600 (14 days) | `number` | `null` | no | +| [dlq\_name](#input\_dlq\_name) | This is the human-readable name of the queue. If omitted, Terraform will assign a random name | `string` | `null` | no | +| [dlq\_queue\_policy\_statements](#input\_dlq\_queue\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [dlq\_receive\_wait\_time\_seconds](#input\_dlq\_receive\_wait\_time\_seconds) | The time for which a ReceiveMessage call will wait for a message to arrive (long polling) before returning. An integer from 0 to 20 (seconds) | `number` | `null` | no | +| [dlq\_redrive\_allow\_policy](#input\_dlq\_redrive\_allow\_policy) | The JSON policy to set up the Dead Letter Queue redrive permission, see AWS docs. | `any` | `{}` | no | +| [dlq\_sqs\_managed\_sse\_enabled](#input\_dlq\_sqs\_managed\_sse\_enabled) | Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys | `bool` | `true` | no | +| [dlq\_tags](#input\_dlq\_tags) | A mapping of additional tags to assign to the dead letter queue | `map(string)` | `{}` | no | +| [dlq\_visibility\_timeout\_seconds](#input\_dlq\_visibility\_timeout\_seconds) | The visibility timeout for the queue. An integer from 0 to 43200 (12 hours) | `number` | `null` | no | +| [fifo\_queue](#input\_fifo\_queue) | Boolean designating a FIFO queue | `bool` | `false` | no | +| [fifo\_throughput\_limit](#input\_fifo\_throughput\_limit) | Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group | `string` | `null` | no | +| [kms\_data\_key\_reuse\_period\_seconds](#input\_kms\_data\_key\_reuse\_period\_seconds) | The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again. An integer representing seconds, between 60 seconds (1 minute) and 86,400 seconds (24 hours) | `number` | `null` | no | +| [kms\_master\_key\_id](#input\_kms\_master\_key\_id) | The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK | `string` | `null` | no | +| [max\_message\_size](#input\_max\_message\_size) | The limit of how many bytes a message can contain before Amazon SQS rejects it. An integer from 1024 bytes (1 KiB) up to 262144 bytes (256 KiB) | `number` | `null` | no | +| [message\_retention\_seconds](#input\_message\_retention\_seconds) | The number of seconds Amazon SQS retains a message. Integer representing seconds, from 60 (1 minute) to 1209600 (14 days) | `number` | `null` | no | +| [name](#input\_name) | This is the human-readable name of the queue. If omitted, Terraform will assign a random name | `string` | `null` | no | +| [override\_dlq\_queue\_policy\_documents](#input\_override\_dlq\_queue\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [override\_queue\_policy\_documents](#input\_override\_queue\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [queue\_policy\_statements](#input\_queue\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [receive\_wait\_time\_seconds](#input\_receive\_wait\_time\_seconds) | The time for which a ReceiveMessage call will wait for a message to arrive (long polling) before returning. An integer from 0 to 20 (seconds) | `number` | `null` | no | +| [redrive\_allow\_policy](#input\_redrive\_allow\_policy) | The JSON policy to set up the Dead Letter Queue redrive permission, see AWS docs. | `any` | `{}` | no | +| [redrive\_policy](#input\_redrive\_policy) | The JSON policy to set up the Dead Letter Queue, see AWS docs. Note: when specifying maxReceiveCount, you must specify it as an integer (5), and not a string ("5") | `any` | `{}` | no | +| [source\_dlq\_queue\_policy\_documents](#input\_source\_dlq\_queue\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [source\_queue\_policy\_documents](#input\_source\_queue\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [sqs\_managed\_sse\_enabled](#input\_sqs\_managed\_sse\_enabled) | Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys | `bool` | `true` | no | +| [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(string)` | `{}` | no | +| [use\_name\_prefix](#input\_use\_name\_prefix) | Determines whether `name` is used as a prefix | `bool` | `false` | no | +| [visibility\_timeout\_seconds](#input\_visibility\_timeout\_seconds) | The visibility timeout for the queue. An integer from 0 to 43200 (12 hours) | `number` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [dead\_letter\_queue\_arn](#output\_dead\_letter\_queue\_arn) | The ARN of the SQS queue | +| [dead\_letter\_queue\_id](#output\_dead\_letter\_queue\_id) | The URL for the created Amazon SQS queue | +| [dead\_letter\_queue\_name](#output\_dead\_letter\_queue\_name) | The name of the SQS queue | +| [dead\_letter\_queue\_url](#output\_dead\_letter\_queue\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [queue\_arn](#output\_queue\_arn) | The ARN of the SQS queue | +| [queue\_id](#output\_queue\_id) | The URL for the created Amazon SQS queue | +| [queue\_name](#output\_queue\_name) | The name of the SQS queue | +| [queue\_url](#output\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-sqs/graphs/contributors). + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-sqs/tree/master/LICENSE) for full details. diff --git a/modules/terraform-aws-sqs/examples/README.md b/modules/terraform-aws-sqs/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/modules/terraform-aws-sqs/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/modules/terraform-aws-sqs/examples/complete/README.md b/modules/terraform-aws-sqs/examples/complete/README.md new file mode 100644 index 0000000..ca621a1 --- /dev/null +++ b/modules/terraform-aws-sqs/examples/complete/README.md @@ -0,0 +1,131 @@ +# Complete SQS Queue Example + +Configuration in this directory creates: +- Queue using module default settings +- FIFO (first-in, first-out) queue +- Unencrypted queue (encryption disabled) +- Queue encrypted with customer managed KMS key +- Queue encrypted with default SQS SSE (server-side encryption) w/ separate dead-letter queue + - Dead letter queue created in separate module definition +- Queue with dead-letter queue created in the same module defintion w/ queue policies for both the source queue and dead-letter queue +- Disabled queue (no resources created) + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.36 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.36 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cmk\_encrypted\_sqs](#module\_cmk\_encrypted\_sqs) | ../../ | n/a | +| [default\_sqs](#module\_default\_sqs) | ../../ | n/a | +| [disabled\_sqs](#module\_disabled\_sqs) | ../../ | n/a | +| [fifo\_sqs](#module\_fifo\_sqs) | ../../ | n/a | +| [sqs\_with\_dlq](#module\_sqs\_with\_dlq) | ../../ | n/a | +| [sse\_encrypted\_dlq\_sqs](#module\_sse\_encrypted\_dlq\_sqs) | ../../ | n/a | +| [sse\_encrypted\_sqs](#module\_sse\_encrypted\_sqs) | ../../ | n/a | +| [unencrypted\_sqs](#module\_unencrypted\_sqs) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cmk\_encrypted\_sqs\_dlq\_arn](#output\_cmk\_encrypted\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [cmk\_encrypted\_sqs\_dlq\_id](#output\_cmk\_encrypted\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [cmk\_encrypted\_sqs\_dlq\_name](#output\_cmk\_encrypted\_sqs\_dlq\_name) | The name of the SQS queue | +| [cmk\_encrypted\_sqs\_dlq\_url](#output\_cmk\_encrypted\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [cmk\_encrypted\_sqs\_queue\_arn](#output\_cmk\_encrypted\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [cmk\_encrypted\_sqs\_queue\_id](#output\_cmk\_encrypted\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [cmk\_encrypted\_sqs\_queue\_name](#output\_cmk\_encrypted\_sqs\_queue\_name) | The name of the SQS queue | +| [cmk\_encrypted\_sqs\_queue\_url](#output\_cmk\_encrypted\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [default\_sqs\_dlq\_arn](#output\_default\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [default\_sqs\_dlq\_id](#output\_default\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [default\_sqs\_dlq\_name](#output\_default\_sqs\_dlq\_name) | The name of the SQS queue | +| [default\_sqs\_dlq\_url](#output\_default\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [default\_sqs\_queue\_arn](#output\_default\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [default\_sqs\_queue\_id](#output\_default\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [default\_sqs\_queue\_name](#output\_default\_sqs\_queue\_name) | The name of the SQS queue | +| [default\_sqs\_queue\_url](#output\_default\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [disabled\_sqs\_dlq\_arn](#output\_disabled\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [disabled\_sqs\_dlq\_id](#output\_disabled\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [disabled\_sqs\_dlq\_name](#output\_disabled\_sqs\_dlq\_name) | The name of the SQS queue | +| [disabled\_sqs\_dlq\_url](#output\_disabled\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [disabled\_sqs\_queue\_arn](#output\_disabled\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [disabled\_sqs\_queue\_id](#output\_disabled\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [disabled\_sqs\_queue\_name](#output\_disabled\_sqs\_queue\_name) | The name of the SQS queue | +| [disabled\_sqs\_queue\_url](#output\_disabled\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [fifo\_sqs\_dlq\_arn](#output\_fifo\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [fifo\_sqs\_dlq\_id](#output\_fifo\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [fifo\_sqs\_dlq\_name](#output\_fifo\_sqs\_dlq\_name) | The name of the SQS queue | +| [fifo\_sqs\_dlq\_url](#output\_fifo\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [fifo\_sqs\_queue\_arn](#output\_fifo\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [fifo\_sqs\_queue\_id](#output\_fifo\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [fifo\_sqs\_queue\_name](#output\_fifo\_sqs\_queue\_name) | The name of the SQS queue | +| [fifo\_sqs\_queue\_url](#output\_fifo\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [sqs\_with\_dlq\_dlq\_arn](#output\_sqs\_with\_dlq\_dlq\_arn) | The ARN of the SQS queue | +| [sqs\_with\_dlq\_dlq\_id](#output\_sqs\_with\_dlq\_dlq\_id) | The URL for the created Amazon SQS queue | +| [sqs\_with\_dlq\_dlq\_name](#output\_sqs\_with\_dlq\_dlq\_name) | The name of the SQS queue | +| [sqs\_with\_dlq\_dlq\_url](#output\_sqs\_with\_dlq\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [sqs\_with\_dlq\_queue\_arn](#output\_sqs\_with\_dlq\_queue\_arn) | The ARN of the SQS queue | +| [sqs\_with\_dlq\_queue\_id](#output\_sqs\_with\_dlq\_queue\_id) | The URL for the created Amazon SQS queue | +| [sqs\_with\_dlq\_queue\_name](#output\_sqs\_with\_dlq\_queue\_name) | The name of the SQS queue | +| [sqs\_with\_dlq\_queue\_url](#output\_sqs\_with\_dlq\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [sse\_encrypted\_dlq\_sqs\_dlq\_arn](#output\_sse\_encrypted\_dlq\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [sse\_encrypted\_dlq\_sqs\_dlq\_id](#output\_sse\_encrypted\_dlq\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [sse\_encrypted\_dlq\_sqs\_dlq\_name](#output\_sse\_encrypted\_dlq\_sqs\_dlq\_name) | The name of the SQS queue | +| [sse\_encrypted\_dlq\_sqs\_dlq\_url](#output\_sse\_encrypted\_dlq\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [sse\_encrypted\_dlq\_sqs\_queue\_arn](#output\_sse\_encrypted\_dlq\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [sse\_encrypted\_dlq\_sqs\_queue\_id](#output\_sse\_encrypted\_dlq\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [sse\_encrypted\_dlq\_sqs\_queue\_name](#output\_sse\_encrypted\_dlq\_sqs\_queue\_name) | The name of the SQS queue | +| [sse\_encrypted\_dlq\_sqs\_queue\_url](#output\_sse\_encrypted\_dlq\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [sse\_encrypted\_sqs\_dlq\_arn](#output\_sse\_encrypted\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [sse\_encrypted\_sqs\_dlq\_id](#output\_sse\_encrypted\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [sse\_encrypted\_sqs\_dlq\_name](#output\_sse\_encrypted\_sqs\_dlq\_name) | The name of the SQS queue | +| [sse\_encrypted\_sqs\_dlq\_url](#output\_sse\_encrypted\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [sse\_encrypted\_sqs\_queue\_arn](#output\_sse\_encrypted\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [sse\_encrypted\_sqs\_queue\_id](#output\_sse\_encrypted\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [sse\_encrypted\_sqs\_queue\_name](#output\_sse\_encrypted\_sqs\_queue\_name) | The name of the SQS queue | +| [sse\_encrypted\_sqs\_queue\_url](#output\_sse\_encrypted\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | +| [unencrypted\_sqs\_dlq\_arn](#output\_unencrypted\_sqs\_dlq\_arn) | The ARN of the SQS queue | +| [unencrypted\_sqs\_dlq\_id](#output\_unencrypted\_sqs\_dlq\_id) | The URL for the created Amazon SQS queue | +| [unencrypted\_sqs\_dlq\_name](#output\_unencrypted\_sqs\_dlq\_name) | The name of the SQS queue | +| [unencrypted\_sqs\_dlq\_url](#output\_unencrypted\_sqs\_dlq\_url) | Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue | +| [unencrypted\_sqs\_queue\_arn](#output\_unencrypted\_sqs\_queue\_arn) | The ARN of the SQS queue | +| [unencrypted\_sqs\_queue\_id](#output\_unencrypted\_sqs\_queue\_id) | The URL for the created Amazon SQS queue | +| [unencrypted\_sqs\_queue\_name](#output\_unencrypted\_sqs\_queue\_name) | The name of the SQS queue | +| [unencrypted\_sqs\_queue\_url](#output\_unencrypted\_sqs\_queue\_url) | Same as `queue_id`: The URL for the created Amazon SQS queue | + diff --git a/modules/terraform-aws-sqs/examples/complete/main.tf b/modules/terraform-aws-sqs/examples/complete/main.tf new file mode 100644 index 0000000..4fdfc3b --- /dev/null +++ b/modules/terraform-aws-sqs/examples/complete/main.tf @@ -0,0 +1,173 @@ +provider "aws" { + region = local.region +} + +data "aws_caller_identity" "current" {} + +locals { + name = "ex-${basename(path.cwd)}" + region = "eu-west-1" + + tags = { + Name = local.name + Example = "complete" + Repository = "github.com/terraform-aws-modules/terraform-aws-sqs" + } +} + +################################################################################ +# SQS Module +################################################################################ + +module "default_sqs" { + source = "../../" + + name = "${local.name}-default" + + tags = local.tags +} + +module "fifo_sqs" { + source = "../../" + + # `.fifo` is automatically appended to the name + # This also means that `use_name_prefix` cannot be used on FIFO queues + name = local.name + fifo_queue = true + + # Dead letter queue + create_dlq = true + redrive_policy = { + # default is 5 for this module + maxReceiveCount = 10 + } + + tags = local.tags +} + +module "unencrypted_sqs" { + source = "../../" + + name = "${local.name}-unencrypted" + sqs_managed_sse_enabled = false + + tags = local.tags +} + +module "cmk_encrypted_sqs" { + source = "../../" + + name = "${local.name}-cmk" + use_name_prefix = true + + kms_master_key_id = aws_kms_key.this.id + kms_data_key_reuse_period_seconds = 3600 + + # Dead letter queue + create_dlq = true + redrive_policy = { + # default is 5 for this module + maxReceiveCount = 10 + } + + tags = local.tags +} + +module "sse_encrypted_sqs" { + source = "../../" + + name = "${local.name}-sse" + sqs_managed_sse_enabled = true + + # Dead letter queue + redrive_policy = { + deadLetterTargetArn = module.sse_encrypted_dlq_sqs.queue_arn + maxReceiveCount = 10 + } + + tags = local.tags +} + +module "sse_encrypted_dlq_sqs" { + source = "../../" + + # This is a separate queue used as a dead letter queue for the above example + # instead of the module creating both the queue and dead letter queue together + + name = "${local.name}-sse-dlq" + sqs_managed_sse_enabled = true + + # Dead letter queue + dlq_redrive_allow_policy = { + sourceQueueArns = [module.sse_encrypted_sqs.queue_arn] + } + + tags = local.tags +} + +module "sqs_with_dlq" { + source = "../../" + + # This creates both the queue and the dead letter queue together + + name = "${local.name}-sqs-with-dlq" + + # Policy + # Not required - just showing example + create_queue_policy = true + queue_policy_statements = { + account = { + sid = "AccountReadWrite" + actions = [ + "sqs:SendMessage", + "sqs:ReceiveMessage", + ] + principals = [ + { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + ] + } + } + + # Dead letter queue + create_dlq = true + redrive_policy = { + # default is 5 for this module + maxReceiveCount = 10 + } + + # Dead letter queue policy + # Not required - just showing example + create_dlq_queue_policy = true + dlq_queue_policy_statements = { + account = { + sid = "AccountReadWrite" + actions = [ + "sqs:SendMessage", + "sqs:ReceiveMessage", + ] + principals = [ + { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + ] + } + } + + tags = local.tags +} + +module "disabled_sqs" { + source = "../../" + + create = false +} + +################################################################################ +# Supporting resources +################################################################################ + +resource "aws_kms_key" "this" {} diff --git a/modules/terraform-aws-sqs/examples/complete/outputs.tf b/modules/terraform-aws-sqs/examples/complete/outputs.tf new file mode 100644 index 0000000..5753ccc --- /dev/null +++ b/modules/terraform-aws-sqs/examples/complete/outputs.tf @@ -0,0 +1,327 @@ +# Default +output "default_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.default_sqs.queue_id +} + +output "default_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.default_sqs.queue_arn +} + +output "default_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.default_sqs.queue_url +} + +output "default_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.default_sqs.queue_name +} + +output "default_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.default_sqs.dead_letter_queue_id +} + +output "default_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.default_sqs.dead_letter_queue_arn +} + +output "default_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.default_sqs.dead_letter_queue_url +} + +output "default_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.default_sqs.dead_letter_queue_name +} + +# FIFO +output "fifo_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.fifo_sqs.queue_id +} + +output "fifo_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.fifo_sqs.queue_arn +} + +output "fifo_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.fifo_sqs.queue_url +} + +output "fifo_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.fifo_sqs.queue_name +} + +output "fifo_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.fifo_sqs.dead_letter_queue_id +} + +output "fifo_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.fifo_sqs.dead_letter_queue_arn +} + +output "fifo_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.fifo_sqs.dead_letter_queue_url +} + +output "fifo_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.fifo_sqs.dead_letter_queue_name +} + +# Unencrypted +output "unencrypted_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.unencrypted_sqs.queue_id +} + +output "unencrypted_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.unencrypted_sqs.queue_arn +} + +output "unencrypted_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.unencrypted_sqs.queue_url +} + +output "unencrypted_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.unencrypted_sqs.queue_name +} + +output "unencrypted_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.unencrypted_sqs.dead_letter_queue_id +} + +output "unencrypted_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.unencrypted_sqs.dead_letter_queue_arn +} + +output "unencrypted_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.unencrypted_sqs.dead_letter_queue_url +} + +output "unencrypted_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.unencrypted_sqs.dead_letter_queue_name +} + +# CMK Encrypted +output "cmk_encrypted_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.cmk_encrypted_sqs.queue_id +} + +output "cmk_encrypted_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.cmk_encrypted_sqs.queue_arn +} + +output "cmk_encrypted_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.cmk_encrypted_sqs.queue_url +} + +output "cmk_encrypted_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.cmk_encrypted_sqs.queue_name +} + +output "cmk_encrypted_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.cmk_encrypted_sqs.dead_letter_queue_id +} + +output "cmk_encrypted_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.cmk_encrypted_sqs.dead_letter_queue_arn +} + +output "cmk_encrypted_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.cmk_encrypted_sqs.dead_letter_queue_url +} + +output "cmk_encrypted_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.cmk_encrypted_sqs.dead_letter_queue_name +} + +# SSE Encrypted +output "sse_encrypted_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sse_encrypted_sqs.queue_id +} + +output "sse_encrypted_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.sse_encrypted_sqs.queue_arn +} + +output "sse_encrypted_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.sse_encrypted_sqs.queue_url +} + +output "sse_encrypted_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.sse_encrypted_sqs.queue_name +} + +output "sse_encrypted_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sse_encrypted_sqs.dead_letter_queue_id +} + +output "sse_encrypted_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.sse_encrypted_sqs.dead_letter_queue_arn +} + +output "sse_encrypted_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.sse_encrypted_sqs.dead_letter_queue_url +} + +output "sse_encrypted_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.sse_encrypted_sqs.dead_letter_queue_name +} + +# SSE Encrypted Dead Letter Quue +output "sse_encrypted_dlq_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sse_encrypted_dlq_sqs.queue_id +} + +output "sse_encrypted_dlq_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.sse_encrypted_dlq_sqs.queue_arn +} + +output "sse_encrypted_dlq_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.sse_encrypted_dlq_sqs.queue_url +} + +output "sse_encrypted_dlq_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.sse_encrypted_dlq_sqs.queue_name +} + +output "sse_encrypted_dlq_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sse_encrypted_dlq_sqs.dead_letter_queue_id +} + +output "sse_encrypted_dlq_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.sse_encrypted_dlq_sqs.dead_letter_queue_arn +} + +output "sse_encrypted_dlq_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.sse_encrypted_dlq_sqs.dead_letter_queue_url +} + +output "sse_encrypted_dlq_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.sse_encrypted_dlq_sqs.dead_letter_queue_name +} + +# With Dead Letter Queue +output "sqs_with_dlq_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sqs_with_dlq.queue_id +} + +output "sqs_with_dlq_queue_arn" { + description = "The ARN of the SQS queue" + value = module.sqs_with_dlq.queue_arn +} + +output "sqs_with_dlq_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.sqs_with_dlq.queue_url +} + +output "sqs_with_dlq_queue_name" { + description = "The name of the SQS queue" + value = module.sqs_with_dlq.queue_name +} + +output "sqs_with_dlq_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.sqs_with_dlq.dead_letter_queue_id +} + +output "sqs_with_dlq_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.sqs_with_dlq.dead_letter_queue_arn +} + +output "sqs_with_dlq_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.sqs_with_dlq.dead_letter_queue_url +} + +output "sqs_with_dlq_dlq_name" { + description = "The name of the SQS queue" + value = module.sqs_with_dlq.dead_letter_queue_name +} + +# Disabled +output "disabled_sqs_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = module.disabled_sqs.queue_id +} + +output "disabled_sqs_queue_arn" { + description = "The ARN of the SQS queue" + value = module.disabled_sqs.queue_arn +} + +output "disabled_sqs_queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = module.disabled_sqs.queue_url +} + +output "disabled_sqs_queue_name" { + description = "The name of the SQS queue" + value = module.disabled_sqs.queue_name +} + +output "disabled_sqs_dlq_id" { + description = "The URL for the created Amazon SQS queue" + value = module.disabled_sqs.dead_letter_queue_id +} + +output "disabled_sqs_dlq_arn" { + description = "The ARN of the SQS queue" + value = module.disabled_sqs.dead_letter_queue_arn +} + +output "disabled_sqs_dlq_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = module.disabled_sqs.dead_letter_queue_url +} + +output "disabled_sqs_dlq_name" { + description = "The name of the SQS queue" + value = module.disabled_sqs.dead_letter_queue_name +} diff --git a/modules/terraform-aws-sqs/examples/complete/variables.tf b/modules/terraform-aws-sqs/examples/complete/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/terraform-aws-sqs/examples/complete/versions.tf b/modules/terraform-aws-sqs/examples/complete/versions.tf new file mode 100644 index 0000000..803290c --- /dev/null +++ b/modules/terraform-aws-sqs/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.36" + } + } +} diff --git a/modules/terraform-aws-sqs/main.tf b/modules/terraform-aws-sqs/main.tf new file mode 100644 index 0000000..37f1ceb --- /dev/null +++ b/modules/terraform-aws-sqs/main.tf @@ -0,0 +1,231 @@ +################################################################################ +# Queue +################################################################################ + +locals { + name = try(trimsuffix(var.name, ".fifo"), "") +} + +resource "aws_sqs_queue" "this" { + count = var.create ? 1 : 0 + + content_based_deduplication = var.content_based_deduplication + deduplication_scope = var.deduplication_scope + delay_seconds = var.delay_seconds + fifo_queue = var.fifo_queue + fifo_throughput_limit = var.fifo_throughput_limit + kms_data_key_reuse_period_seconds = var.kms_data_key_reuse_period_seconds + kms_master_key_id = var.kms_master_key_id + max_message_size = var.max_message_size + message_retention_seconds = var.message_retention_seconds + name = var.use_name_prefix ? null : (var.fifo_queue ? "${local.name}.fifo" : local.name) + name_prefix = var.use_name_prefix ? "${local.name}-" : null + receive_wait_time_seconds = var.receive_wait_time_seconds + sqs_managed_sse_enabled = var.kms_master_key_id != null ? null : var.sqs_managed_sse_enabled + visibility_timeout_seconds = var.visibility_timeout_seconds + + tags = var.tags +} + +################################################################################ +# Queue Policy +################################################################################ + +data "aws_iam_policy_document" "this" { + count = var.create && var.create_queue_policy ? 1 : 0 + + source_policy_documents = var.source_queue_policy_documents + override_policy_documents = var.override_queue_policy_documents + + dynamic "statement" { + for_each = var.queue_policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, [aws_sqs_queue.this[0].arn]) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_sqs_queue_policy" "this" { + count = var.create && var.create_queue_policy ? 1 : 0 + + queue_url = aws_sqs_queue.this[0].url + policy = data.aws_iam_policy_document.this[0].json +} + +################################################################################ +# Re-drive Policy +################################################################################ + +resource "aws_sqs_queue_redrive_policy" "this" { + count = var.create && !var.create_dlq && length(var.redrive_policy) > 0 ? 1 : 0 + + queue_url = aws_sqs_queue.this[0].url + redrive_policy = jsonencode(var.redrive_policy) +} + +resource "aws_sqs_queue_redrive_policy" "dlq" { + count = var.create && var.create_dlq ? 1 : 0 + + queue_url = aws_sqs_queue.this[0].url + redrive_policy = jsonencode( + merge( + { + deadLetterTargetArn = aws_sqs_queue.dlq[0].arn + maxReceiveCount = 5 + }, + var.redrive_policy + ) + ) +} + +################################################################################ +# Dead Letter Queue +################################################################################ + +locals { + stripped_dlq_name = try(trimsuffix(var.dlq_name, ".fifo"), "") + inter_dlq_name = try(coalesce(local.stripped_dlq_name, "${local.name}-dlq"), "") + dlq_name = var.fifo_queue && !var.use_name_prefix ? "${local.inter_dlq_name}.fifo" : local.inter_dlq_name + + dlq_kms_master_key_id = try(coalesce(var.dlq_kms_master_key_id, var.kms_master_key_id), null) + dlq_sqs_managed_sse_enabled = coalesce(var.dlq_sqs_managed_sse_enabled, var.sqs_managed_sse_enabled) +} + +resource "aws_sqs_queue" "dlq" { + count = var.create && var.create_dlq ? 1 : 0 + + content_based_deduplication = try(coalesce(var.dlq_content_based_deduplication, var.content_based_deduplication), null) + deduplication_scope = try(coalesce(var.dlq_deduplication_scope, var.deduplication_scope), null) + delay_seconds = try(coalesce(var.dlq_delay_seconds, var.delay_seconds), null) + # If source queue is FIFO, DLQ must also be FIFO and vice versa + fifo_queue = var.fifo_queue + fifo_throughput_limit = var.fifo_throughput_limit + kms_data_key_reuse_period_seconds = try(coalesce(var.dlq_kms_data_key_reuse_period_seconds, var.kms_data_key_reuse_period_seconds), null) + kms_master_key_id = local.dlq_kms_master_key_id + max_message_size = var.max_message_size + message_retention_seconds = try(coalesce(var.dlq_message_retention_seconds, var.message_retention_seconds), null) + name = var.use_name_prefix ? null : local.dlq_name + name_prefix = var.use_name_prefix ? "${local.dlq_name}-" : null + receive_wait_time_seconds = try(coalesce(var.dlq_receive_wait_time_seconds, var.receive_wait_time_seconds), null) + sqs_managed_sse_enabled = local.dlq_kms_master_key_id != null ? null : local.dlq_sqs_managed_sse_enabled + visibility_timeout_seconds = try(coalesce(var.dlq_visibility_timeout_seconds, var.visibility_timeout_seconds), null) + + tags = merge(var.tags, var.dlq_tags) +} + +################################################################################ +# Queue Policy +################################################################################ + +data "aws_iam_policy_document" "dlq" { + count = var.create && var.create_dlq && var.create_dlq_queue_policy ? 1 : 0 + + source_policy_documents = var.source_dlq_queue_policy_documents + override_policy_documents = var.override_dlq_queue_policy_documents + + dynamic "statement" { + for_each = var.dlq_queue_policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, [aws_sqs_queue.dlq[0].arn]) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_sqs_queue_policy" "dlq" { + count = var.create && var.create_dlq && var.create_dlq_queue_policy ? 1 : 0 + + queue_url = aws_sqs_queue.dlq[0].url + policy = data.aws_iam_policy_document.dlq[0].json +} + +################################################################################ +# Re-drive Allow Policy +################################################################################ + +resource "aws_sqs_queue_redrive_allow_policy" "this" { + count = var.create && !var.create_dlq && length(var.redrive_allow_policy) > 0 ? 1 : 0 + + queue_url = aws_sqs_queue.this[0].url + redrive_allow_policy = jsonencode(var.redrive_allow_policy) +} + +resource "aws_sqs_queue_redrive_allow_policy" "dlq" { + count = var.create && var.create_dlq ? 1 : 0 + + queue_url = aws_sqs_queue.dlq[0].url + redrive_allow_policy = jsonencode(merge( + { + redrivePermission = "byQueue", + sourceQueueArns = [aws_sqs_queue.this[0].arn] + }, + var.dlq_redrive_allow_policy) + ) +} diff --git a/modules/terraform-aws-sqs/outputs.tf b/modules/terraform-aws-sqs/outputs.tf new file mode 100644 index 0000000..32a1a5e --- /dev/null +++ b/modules/terraform-aws-sqs/outputs.tf @@ -0,0 +1,47 @@ +################################################################################ +# Queue +################################################################################ + +output "queue_id" { + description = "The URL for the created Amazon SQS queue" + value = try(aws_sqs_queue.this[0].id, null) +} + +output "queue_arn" { + description = "The ARN of the SQS queue" + value = try(aws_sqs_queue.this[0].arn, null) +} + +output "queue_url" { + description = "Same as `queue_id`: The URL for the created Amazon SQS queue" + value = try(aws_sqs_queue.this[0].url, null) +} + +output "queue_name" { + description = "The name of the SQS queue" + value = try(aws_sqs_queue.this[0].name, null) +} + +################################################################################ +# Dead Letter Queue +################################################################################ + +output "dead_letter_queue_id" { + description = "The URL for the created Amazon SQS queue" + value = try(aws_sqs_queue.dlq[0].id, null) +} + +output "dead_letter_queue_arn" { + description = "The ARN of the SQS queue" + value = try(aws_sqs_queue.dlq[0].arn, null) +} + +output "dead_letter_queue_url" { + description = "Same as `dead_letter_queue_id`: The URL for the created Amazon SQS queue" + value = try(aws_sqs_queue.dlq[0].url, null) +} + +output "dead_letter_queue_name" { + description = "The name of the SQS queue" + value = try(aws_sqs_queue.dlq[0].name, null) +} diff --git a/modules/terraform-aws-sqs/variables.tf b/modules/terraform-aws-sqs/variables.tf new file mode 100644 index 0000000..dd2a69a --- /dev/null +++ b/modules/terraform-aws-sqs/variables.tf @@ -0,0 +1,249 @@ +variable "create" { + description = "Whether to create SQS queue" + type = bool + default = true +} + +################################################################################ +# Queue +################################################################################ + +variable "content_based_deduplication" { + description = "Enables content-based deduplication for FIFO queues" + type = bool + default = null +} + +variable "deduplication_scope" { + description = "Specifies whether message deduplication occurs at the message group or queue level" + type = string + default = null +} + +variable "delay_seconds" { + description = "The time in seconds that the delivery of all messages in the queue will be delayed. An integer from 0 to 900 (15 minutes)" + type = number + default = null +} + +variable "fifo_queue" { + description = "Boolean designating a FIFO queue" + type = bool + default = false +} + +variable "fifo_throughput_limit" { + description = "Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group" + type = string + default = null +} + +variable "kms_data_key_reuse_period_seconds" { + description = "The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again. An integer representing seconds, between 60 seconds (1 minute) and 86,400 seconds (24 hours)" + type = number + default = null +} + +variable "kms_master_key_id" { + description = "The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK" + type = string + default = null +} + +variable "max_message_size" { + description = "The limit of how many bytes a message can contain before Amazon SQS rejects it. An integer from 1024 bytes (1 KiB) up to 262144 bytes (256 KiB)" + type = number + default = null +} + +variable "message_retention_seconds" { + description = "The number of seconds Amazon SQS retains a message. Integer representing seconds, from 60 (1 minute) to 1209600 (14 days)" + type = number + default = null +} + +variable "name" { + description = "This is the human-readable name of the queue. If omitted, Terraform will assign a random name" + type = string + default = null +} + +variable "use_name_prefix" { + description = "Determines whether `name` is used as a prefix" + type = bool + default = false +} + +variable "receive_wait_time_seconds" { + description = "The time for which a ReceiveMessage call will wait for a message to arrive (long polling) before returning. An integer from 0 to 20 (seconds)" + type = number + default = null +} + +variable "redrive_allow_policy" { + description = "The JSON policy to set up the Dead Letter Queue redrive permission, see AWS docs." + type = any + default = {} +} + +variable "redrive_policy" { + description = "The JSON policy to set up the Dead Letter Queue, see AWS docs. Note: when specifying maxReceiveCount, you must specify it as an integer (5), and not a string (\"5\")" + type = any + default = {} +} + +variable "sqs_managed_sse_enabled" { + description = "Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys" + type = bool + default = true +} + +variable "visibility_timeout_seconds" { + description = "The visibility timeout for the queue. An integer from 0 to 43200 (12 hours)" + type = number + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Queue Policy +################################################################################ + +variable "create_queue_policy" { + description = "Whether to create SQS queue policy" + type = bool + default = false +} + +variable "source_queue_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_queue_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "queue_policy_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Dead Letter Queue +################################################################################ + +variable "create_dlq" { + description = "Determines whether to create SQS dead letter queue" + type = bool + default = false +} + +variable "dlq_content_based_deduplication" { + description = "Enables content-based deduplication for FIFO queues" + type = bool + default = null +} + +variable "dlq_deduplication_scope" { + description = "Specifies whether message deduplication occurs at the message group or queue level" + type = string + default = null +} + +variable "dlq_delay_seconds" { + description = "The time in seconds that the delivery of all messages in the queue will be delayed. An integer from 0 to 900 (15 minutes)" + type = number + default = null +} + +variable "dlq_kms_data_key_reuse_period_seconds" { + description = "The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again. An integer representing seconds, between 60 seconds (1 minute) and 86,400 seconds (24 hours)" + type = number + default = null +} + +variable "dlq_kms_master_key_id" { + description = "The ID of an AWS-managed customer master key (CMK) for Amazon SQS or a custom CMK" + type = string + default = null +} + +variable "dlq_message_retention_seconds" { + description = "The number of seconds Amazon SQS retains a message. Integer representing seconds, from 60 (1 minute) to 1209600 (14 days)" + type = number + default = null +} + +variable "dlq_name" { + description = "This is the human-readable name of the queue. If omitted, Terraform will assign a random name" + type = string + default = null +} + +variable "dlq_receive_wait_time_seconds" { + description = "The time for which a ReceiveMessage call will wait for a message to arrive (long polling) before returning. An integer from 0 to 20 (seconds)" + type = number + default = null +} + +variable "dlq_redrive_allow_policy" { + description = "The JSON policy to set up the Dead Letter Queue redrive permission, see AWS docs." + type = any + default = {} +} + +variable "dlq_sqs_managed_sse_enabled" { + description = "Boolean to enable server-side encryption (SSE) of message content with SQS-owned encryption keys" + type = bool + default = true +} + +variable "dlq_visibility_timeout_seconds" { + description = "The visibility timeout for the queue. An integer from 0 to 43200 (12 hours)" + type = number + default = null +} + +variable "dlq_tags" { + description = "A mapping of additional tags to assign to the dead letter queue" + type = map(string) + default = {} +} + +################################################################################ +# Dead Letter Queue Policy +################################################################################ + +variable "create_dlq_queue_policy" { + description = "Whether to create SQS queue policy" + type = bool + default = false +} + +variable "source_dlq_queue_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_dlq_queue_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "dlq_queue_policy_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} diff --git a/modules/terraform-aws-sqs/versions.tf b/modules/terraform-aws-sqs/versions.tf new file mode 100644 index 0000000..803290c --- /dev/null +++ b/modules/terraform-aws-sqs/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.36" + } + } +} diff --git a/modules/terraform-aws-sqs/wrappers/README.md b/modules/terraform-aws-sqs/wrappers/README.md new file mode 100644 index 0000000..b159568 --- /dev/null +++ b/modules/terraform-aws-sqs/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/sqs/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-sqs.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/sqs/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/terraform-aws-sqs/wrappers/main.tf b/modules/terraform-aws-sqs/wrappers/main.tf new file mode 100644 index 0000000..f6cd2b9 --- /dev/null +++ b/modules/terraform-aws-sqs/wrappers/main.tf @@ -0,0 +1,45 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + content_based_deduplication = try(each.value.content_based_deduplication, var.defaults.content_based_deduplication, null) + deduplication_scope = try(each.value.deduplication_scope, var.defaults.deduplication_scope, null) + delay_seconds = try(each.value.delay_seconds, var.defaults.delay_seconds, null) + fifo_queue = try(each.value.fifo_queue, var.defaults.fifo_queue, false) + fifo_throughput_limit = try(each.value.fifo_throughput_limit, var.defaults.fifo_throughput_limit, null) + kms_data_key_reuse_period_seconds = try(each.value.kms_data_key_reuse_period_seconds, var.defaults.kms_data_key_reuse_period_seconds, null) + kms_master_key_id = try(each.value.kms_master_key_id, var.defaults.kms_master_key_id, null) + max_message_size = try(each.value.max_message_size, var.defaults.max_message_size, null) + message_retention_seconds = try(each.value.message_retention_seconds, var.defaults.message_retention_seconds, null) + name = try(each.value.name, var.defaults.name, null) + use_name_prefix = try(each.value.use_name_prefix, var.defaults.use_name_prefix, false) + receive_wait_time_seconds = try(each.value.receive_wait_time_seconds, var.defaults.receive_wait_time_seconds, null) + redrive_allow_policy = try(each.value.redrive_allow_policy, var.defaults.redrive_allow_policy, {}) + redrive_policy = try(each.value.redrive_policy, var.defaults.redrive_policy, {}) + sqs_managed_sse_enabled = try(each.value.sqs_managed_sse_enabled, var.defaults.sqs_managed_sse_enabled, true) + visibility_timeout_seconds = try(each.value.visibility_timeout_seconds, var.defaults.visibility_timeout_seconds, null) + tags = try(each.value.tags, var.defaults.tags, {}) + create_queue_policy = try(each.value.create_queue_policy, var.defaults.create_queue_policy, false) + source_queue_policy_documents = try(each.value.source_queue_policy_documents, var.defaults.source_queue_policy_documents, []) + override_queue_policy_documents = try(each.value.override_queue_policy_documents, var.defaults.override_queue_policy_documents, []) + queue_policy_statements = try(each.value.queue_policy_statements, var.defaults.queue_policy_statements, {}) + create_dlq = try(each.value.create_dlq, var.defaults.create_dlq, false) + dlq_content_based_deduplication = try(each.value.dlq_content_based_deduplication, var.defaults.dlq_content_based_deduplication, null) + dlq_deduplication_scope = try(each.value.dlq_deduplication_scope, var.defaults.dlq_deduplication_scope, null) + dlq_delay_seconds = try(each.value.dlq_delay_seconds, var.defaults.dlq_delay_seconds, null) + dlq_kms_data_key_reuse_period_seconds = try(each.value.dlq_kms_data_key_reuse_period_seconds, var.defaults.dlq_kms_data_key_reuse_period_seconds, null) + dlq_kms_master_key_id = try(each.value.dlq_kms_master_key_id, var.defaults.dlq_kms_master_key_id, null) + dlq_message_retention_seconds = try(each.value.dlq_message_retention_seconds, var.defaults.dlq_message_retention_seconds, null) + dlq_name = try(each.value.dlq_name, var.defaults.dlq_name, null) + dlq_receive_wait_time_seconds = try(each.value.dlq_receive_wait_time_seconds, var.defaults.dlq_receive_wait_time_seconds, null) + dlq_redrive_allow_policy = try(each.value.dlq_redrive_allow_policy, var.defaults.dlq_redrive_allow_policy, {}) + dlq_sqs_managed_sse_enabled = try(each.value.dlq_sqs_managed_sse_enabled, var.defaults.dlq_sqs_managed_sse_enabled, true) + dlq_visibility_timeout_seconds = try(each.value.dlq_visibility_timeout_seconds, var.defaults.dlq_visibility_timeout_seconds, null) + dlq_tags = try(each.value.dlq_tags, var.defaults.dlq_tags, {}) + create_dlq_queue_policy = try(each.value.create_dlq_queue_policy, var.defaults.create_dlq_queue_policy, false) + source_dlq_queue_policy_documents = try(each.value.source_dlq_queue_policy_documents, var.defaults.source_dlq_queue_policy_documents, []) + override_dlq_queue_policy_documents = try(each.value.override_dlq_queue_policy_documents, var.defaults.override_dlq_queue_policy_documents, []) + dlq_queue_policy_statements = try(each.value.dlq_queue_policy_statements, var.defaults.dlq_queue_policy_statements, {}) +} diff --git a/modules/terraform-aws-sqs/wrappers/outputs.tf b/modules/terraform-aws-sqs/wrappers/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/modules/terraform-aws-sqs/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/terraform-aws-sqs/wrappers/variables.tf b/modules/terraform-aws-sqs/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/modules/terraform-aws-sqs/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/terraform-aws-sqs/wrappers/versions.tf b/modules/terraform-aws-sqs/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/modules/terraform-aws-sqs/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/release-please-config.json b/release-please-config.json index 240d510..382ab0b 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,6 +8,555 @@ "bump-patch-for-minor-pre-major": false, "draft": false, "prerelease": false + }, + "modules/aws-acm": { + "component": "aws-acm", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-alb-master": { + "component": "aws-alb-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-amplify": { + "component": "aws-amplify", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-api-gateway": { + "component": "aws-api-gateway", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-apigateway-v2-master": { + "component": "aws-apigateway-v2-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-centralized-logging": { + "component": "aws-centralized-logging", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-client-vpn": { + "component": "aws-client-vpn", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cloudfront": { + "component": "aws-cloudfront", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cloudtrail": { + "component": "aws-cloudtrail", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cloudtrail-s3-bucket": { + "component": "aws-cloudtrail-s3-bucket", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cloudwatch": { + "component": "aws-cloudwatch", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cloudwatch-event-rule-master": { + "component": "aws-cloudwatch-event-rule-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-cognito-user-pool": { + "component": "aws-cognito-user-pool", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-dax": { + "component": "aws-dax", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-dynamodb-table": { + "component": "aws-dynamodb-table", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ec2-instance": { + "component": "aws-ec2-instance", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ecr": { + "component": "aws-ecr", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ecs": { + "component": "aws-ecs", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ecs-load-balancer": { + "component": "aws-ecs-load-balancer", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ecs-service": { + "component": "aws-ecs-service", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ecs-task-definition": { + "component": "aws-ecs-task-definition", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-efs": { + "component": "aws-efs", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-eks": { + "component": "aws-eks", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-eks-addons": { + "component": "aws-eks-addons", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-eks-rancher-importer": { + "component": "aws-eks-rancher-importer", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-eks-rancher-joiner": { + "component": "aws-eks-rancher-joiner", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-elasticache-redis": { + "component": "aws-elasticache-redis", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-elasticsearch": { + "component": "aws-elasticsearch", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-guardduty-master": { + "component": "aws-guardduty-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-iam": { + "component": "aws-iam", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-key-pair": { + "component": "aws-key-pair", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-kms": { + "component": "aws-kms", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-lambda": { + "component": "aws-lambda", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-mongodbatlas-access": { + "component": "aws-mongodbatlas-access", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-mongodbatlas-auth": { + "component": "aws-mongodbatlas-auth", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-mongodbatlas-cluster": { + "component": "aws-mongodbatlas-cluster", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-mongodbatlas-database-user": { + "component": "aws-mongodbatlas-database-user", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-mq": { + "component": "aws-mq", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-msk-apache-kafka-cluster-master": { + "component": "aws-msk-apache-kafka-cluster-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-rds": { + "component": "aws-rds", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-rds-aurora": { + "component": "aws-rds-aurora", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-rds-global-cluster": { + "component": "aws-rds-global-cluster", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-route53-health-check-main": { + "component": "aws-route53-health-check-main", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-route53-records": { + "component": "aws-route53-records", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-route53-zones": { + "component": "aws-route53-zones", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-s3": { + "component": "aws-s3", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-secrets-manager": { + "component": "aws-secrets-manager", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-security-group": { + "component": "aws-security-group", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-security-hub": { + "component": "aws-security-hub", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-self-signed-cert": { + "component": "aws-self-signed-cert", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-shield": { + "component": "aws-shield", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-sns": { + "component": "aws-sns", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-sns-master": { + "component": "aws-sns-master", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-ssm-parameter-store": { + "component": "aws-ssm-parameter-store", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-transit-gateway": { + "component": "aws-transit-gateway", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-vpc": { + "component": "aws-vpc", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-vpc-peering-accepter": { + "component": "aws-vpc-peering-accepter", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-vpc-peering-requester": { + "component": "aws-vpc-peering-requester", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-waf": { + "component": "aws-waf", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/aws-waf-ip-set": { + "component": "aws-waf-ip-set", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + }, + "modules/terraform-aws-sqs": { + "component": "terraform-aws-sqs", + "changelog-path": "CHANGELOG.md", + "release-type": "terraform-module", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false } }, "separate-pull-requests": true,