diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3bd2c..422d342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -383,3 +383,7 @@ - flowlogs-transit-gateway - remove splunk - add outputs (for generating subscription external to module) + +* 2.9.16 -- 2024-05-08 + - tag-shared-vpc-resources + - fix nacl tagging diff --git a/tag-shared-vpc-resources/README.md b/tag-shared-vpc-resources/README.md index d11680d..bb5792f 100644 --- a/tag-shared-vpc-resources/README.md +++ b/tag-shared-vpc-resources/README.md @@ -219,3 +219,227 @@ No modules. ## Outputs No outputs. + + +# About aws-vpc-setup :: tag-shared-vpc-resources + +This code reads a list of shared VPC resources, through RAM, available to the account and region, and will use the EC2 tag resouce to +set tags within this account. + +This is necessary because resources shared by RAM do not include tags in the shared accounts. This is handled per +account and region. + +It is required to have access to the appropriate remote account. Currently, it's the standard profile name ({account\_id}-{account\_alias}),but we will set this +up to use one's specific account profile with an assume role to get at the target account where the resources are shared. + +For VPCs, it is expected that the account shared is the Network Production account, but any account and profile may be used if it shares VPC resources. + +This reads tags and updates them in the local account and region from these resources: + +* vpcs +* dhcp-option-sets +* subnets +* route-tables +* network-acls +* transit-gateway + +## How it works + +We get a list of VPCs, subnets, route tables and network ACLs which are owned by the network source account. +For each of these, other than network ACLs, we read each item with a `data` resource, and then the tags and owner on the +the resource in our account and region + +For DHCP options sets, it reads the associated `dhcp_option_set` on the shared VPC. It uses that to obtain the tags +and set them accordingly. + +Network ACLS are different. There is no data resource to get a specific network ACL. We read the list of network ACLs, but then +pass that into a `null_resource` to call the `aws` CLI. This gets a JSON file, which is then parsed and Tag and OwnerID extracted +to apply tags on the local network ACLs. There are enhancements issues for this missing resource in the terraform aws provider +[1](https://github.com/hashicorp/terraform-provider-aws/issues/19754), +[2](https://github.com/hashicorp/terraform-provider-aws/issues/4260). + +# Usage + +```hcl +# use of `tf-control` wrappers sets this value to your Linux username $USER +variable "os_username" { + type = string + default = null +} + +provider "aws" { + alias = "network_account" + region = var.region + profile = var.profile + assume_role { + role_arn = "arn:aws-us-gov:iam::057405694017:role/r-inf-tf-remote-shared-vpc" + session_name = var.os_username + } +} + +module "tag_shared" { + source = "git@github.e.it.census.gov:terraform-modules/aws-vpc-setup.git//tag-shared-vpc-resources?ref=tf-upgrade" + providers = { + aws = aws + aws.network_account = aws.network_account + } + + ## optional, with defaults + ### tag_enabled_vpcs = true + ### tag_enabled_dhcp_options = true + ### tag_enabled_route_tables = true + ### tag_enabled_subnets = true + ### tag_enabled_network_acls = true + ### tag_enabled_transit_gateway = true +} +``` + +**NOTE** +This is a multi-step module, if using the `tag_enabled_network_acls=true` (the default). This is because there is no Terraform +resource to get a network ACL (described above). As such, you must execute a `tf-apply` twice, once to create a file +indicating the AWS CLI has been run to obtain the data, and the second to tag the local network acls. + +## Access Requirements + +You need to have these AWS CLI IAM profiles and accounts available: + +* AWS Profile for account where VPC resources are shared (primarily the ability to tag resoruces) +* The ability from your current profile to assume the role `r-inf-tf-remote-shared-vpc` in the Network account (this would be a different +account for SA, Lab, or EW; that will be updated once such stuff is available). A provider alias will be defined as shown +in the example (though the role ARN may differ in partition or account). + +## Application Requirements + +This requires the `aws` CLI v2. + +## Input Variables + +* tag\_enbled\_* + +## Output Variables + +This module does not have any outputs. + +# Terraform Directions + +Run this once per account and region. This module call belongs in `vpc/{region}/shared-vpc-setup`. This may be re-run when new +subnets and VPCs are shared to an account, or if tags change in the source account, and they need to be updated in this particular +account. + +```script +tf-run apply +``` + +## tf-run.data + +To use this with `tf-run`, you can will need to either use the module name twice, or use the statement `ALL` twice (preferred). + +* example with module name twice + +```script +VERSION 1.1.0 +REMOTE-STATE +COMMAND tf-directory-setup.py -l none -f +COMMAND setup-new-directory.sh +COMMAND tf-init -upgrade +module.tag_shared +module.tag_shared +ALL +COMMAND tf-directory-setup.py -l s3 +``` + +* example with ALL twice (recommended) + +```script +VERSION 1.1.0 +REMOTE-STATE +COMMAND tf-directory-setup.py -l none -f +COMMAND setup-new-directory.sh +COMMAND tf-init -upgrade +ALL +ALL +COMMAND tf-directory-setup.py -l s3 +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [aws](#requirement\_aws) | >= 3.66.0 | +| [ldap](#requirement\_ldap) | >= 0.5.4 | +| [local](#requirement\_local) | >= 1.0.0 | +| [null](#requirement\_null) | >= 3.0 | +| [random](#requirement\_random) | >= 3.0 | +| [template](#requirement\_template) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.66.0 | +| [aws.network\_account](#provider\_aws.network\_account) | >= 3.66.0 | +| [local](#provider\_local) | >= 1.0.0 | +| [null](#provider\_null) | >= 3.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ec2_tag.dhcp_options](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_tag.network_acls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_tag.route_tables](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_tag.subnets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_tag.transit_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [aws_ec2_tag.vpcs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | +| [null_resource.network_acl](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.network_acls](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.setup_directory](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_arn.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_arn.network_account](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_caller_identity.network_account](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_ec2_transit_gateway.transit_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_transit_gateway) | data source | +| [aws_iam_account_alias.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_account_alias) | data source | +| [aws_network_acls.network_acls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/network_acls) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_route_table.route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route_table) | data source | +| [aws_route_tables.route_tables](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route_tables) | data source | +| [aws_subnet.subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | +| [aws_subnets.subnets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnets) | data source | +| [aws_vpc.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | +| [aws_vpc_dhcp_options.dhcp_options](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc_dhcp_options) | data source | +| [aws_vpcs.vpcs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpcs) | data source | +| [local_file.network_acl](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_alias](#input\_account\_alias) | AWS Account Alias (default: will pull from current account\_alias) | `string` | `""` | no | +| [account\_id](#input\_account\_id) | AWS Account ID (default: will pull from current user) | `string` | `""` | no | +| [create](#input\_create) | Flag to indicate whether to create the resources or not (default: true) | `bool` | `true` | no | +| [override\_prefixes](#input\_override\_prefixes) | Override built-in prefixes by component. This should be used primarily for common infrastructure things | `map(string)` | `{}` | no | +| [profile](#input\_profile) | AWS profile of the account in which this is running | `string` | n/a | yes | +| [role\_arn](#input\_role\_arn) | AWS Role ARN of the target account, the network account where the shared VPC resources are configured, from which to pull tag data | `string` | n/a | yes | +| [tag\_enabled\_dhcp\_options](#input\_tag\_enabled\_dhcp\_options) | Flag to tag or not tag shared VPC DHCP option sets | `bool` | `true` | no | +| [tag\_enabled\_network\_acls](#input\_tag\_enabled\_network\_acls) | Flag to tag or not tag shared Network ACLs | `bool` | `true` | no | +| [tag\_enabled\_route\_tables](#input\_tag\_enabled\_route\_tables) | Flag to tag or not tag shared VPC route tables | `bool` | `true` | no | +| [tag\_enabled\_subnets](#input\_tag\_enabled\_subnets) | Flag to tag or not tag shared VPC subnets | `bool` | `true` | no | +| [tag\_enabled\_transit\_gateway](#input\_tag\_enabled\_transit\_gateway) | Flag to tag or not tag shared VPC Transit Gateway (not currently possible in AWS; this has no effect) | `bool` | `true` | no | +| [tag\_enabled\_vpcs](#input\_tag\_enabled\_vpcs) | Flag to tag or not tag shared VPCs | `bool` | `true` | no | +| [tags](#input\_tags) | AWS Tags to apply to appropriate resources (S3, KMS). Do not include safeguard tags here, use the data\_safeguard field for such things. | `map(string)` | `{}` | no | +| [vpc\_environment](#input\_vpc\_environment) | VPC environment purpose (infrastructure, common, shared, dev, stage, ite, prod) | `string` | `null` | no | +| [vpc\_full\_name](#input\_vpc\_full\_name) | VPC full name component (vpc{index}-{vpc\_name}) | `string` | `null` | no | +| [vpc\_index](#input\_vpc\_index) | VPC index number (integer starting at 1) | `number` | `null` | no | +| [vpc\_name](#input\_vpc\_name) | VPC name component used through the VPC descrbing its purpose (ex: dice-dev) | `string` | `null` | no | +| [vpc\_short\_name](#input\_vpc\_short\_name) | VPC short name component (vpc{index}) | `string` | `null` | no | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/tag-shared-vpc-resources/tag-network-acls.tf b/tag-shared-vpc-resources/tag-network-acls.tf index 7b80b16..a7edc48 100644 --- a/tag-shared-vpc-resources/tag-network-acls.tf +++ b/tag-shared-vpc-resources/tag-network-acls.tf @@ -79,11 +79,17 @@ resource "null_resource" "network_acls" { ## } locals { + # _nacl_enabled = var.tag_enabled_network_acls + # _network_acls = local._nacl_enabled ? { for k, v in data.local_file.network_acl : k => jsondecode(v.content) } : {} + # network_acls = fileexists(null_resource.network_acls.triggers.filename) ? { for k, v in local._network_acls : k => lookup(v, "NetworkAcls", [{ "Tags" : [], "OwnerId" : "" }])[0] } : {} + # network_acls_tags = fileexists(null_resource.network_acls.triggers.filename) ? { for k, v in local.network_acls : k => merge({ for t in v.Tags : t.Key => t.Value }, { "boc:vpc:owner_id" = v.OwnerId }) } : {} + # network_acls_tags_map = fileexists(null_resource.network_acls.triggers.filename) ? flatten([for k, v in local.network_acls_tags : [for tk, tv in v : { label = format("%v__%v", k, tk), network_acl_id = k, key = tk, value = tv }]]) : [] + _nacl_enabled = var.tag_enabled_network_acls - _network_acls = local._nacl_enabled ? { for k, v in data.local_file.network_acl : k => jsondecode(v.content) } : {} - network_acls = fileexists(null_resource.network_acls.triggers.filename) ? { for k, v in local._network_acls : k => lookup(v, "NetworkAcls", [{ "Tags" : [], "OwnerId" : "" }])[0] } : {} - network_acls_tags = fileexists(null_resource.network_acls.triggers.filename) ? { for k, v in local.network_acls : k => merge({ for t in v.Tags : t.Key => t.Value }, { "boc:vpc:owner_id" = v.OwnerId }) } : {} - network_acls_tags_map = fileexists(null_resource.network_acls.triggers.filename) ? flatten([for k, v in local.network_acls_tags : [for tk, tv in v : { label = format("%v__%v", k, tk), network_acl_id = k, key = tk, value = tv }]]) : [] + _network_acls = { for k, v in data.local_file.network_acl : k => jsondecode(v.content) if local._nacl_enabled } + network_acls = { for k, v in local._network_acls : k => lookup(v, "NetworkAcls", [{ "Tags" : [], "OwnerId" : "" }])[0] } + network_acls_tags = { for k, v in local.network_acls : k => merge({ for t in v.Tags : t.Key => t.Value }, { "boc:vpc:owner_id" = v.OwnerId }) } + network_acls_tags_map = flatten([for k, v in local.network_acls_tags : [for tk, tv in v : { label = format("%v__%v", k, tk), network_acl_id = k, key = tk, value = tv }]]) } resource "aws_ec2_tag" "network_acls" {