diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..07b4535 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,122 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/cloudposse/awsutils" { + version = "0.19.0" + constraints = ">= 0.8.0" + hashes = [ + "h1:1CVglOJAw/x4yxVsV0KAPKvtrp/KyhCeGQdU6Ur/6pY=", + "zh:12d449b2dae49ce419e36022bf2456c77a717408b8908a382916b5a512cf64d5", + "zh:68d2228af5df9cd3e44d04781797ccfc313f4054841910af5e0b30eab61f3781", + "zh:9e4e9f0f6d0606165b17a4c0f2055745befee6e398e47a2112bf70f8e79077e7", + "zh:b4ff0225246328a194995588090fdb472c703e3f7cb625113e0bd47bfa26ece6", + "zh:bee6c6263fb1df83694af86d6cdabb60209c7cd2d1158e385670a9a909040abc", + "zh:f72d2e4c5883c00f4c5753c8fa9cd62102e2097718a735091a118660073344ff", + ] +} + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.44.0" + constraints = ">= 2.0.0, >= 3.0.0, >= 3.29.0, >= 3.68.0, >= 3.72.0, >= 4.0.0, >= 5.0.0, ~> 5.0" + hashes = [ + "h1:K3sX+P4wofRNcVsnYW4PIhxHijd3w/ZD5AO7yWFPT6A=", + "zh:1224a42bb04574785549b89815d98bda11f6e9992352fc6c36c5622f3aea91c0", + "zh:2a8d1095a2f1ab097f516d9e7e0d289337849eebb3fcc34f075070c65063f4fa", + "zh:46cce11150eb4934196d9bff693b72d0494c85917ceb3c2914d5ff4a785af861", + "zh:4a7c15d585ee747d17f4b3904851cd95cfbb920fa197aed3df78e8d7ef9609b6", + "zh:508f1a85a0b0f93bf26341207d809bd55b60c8fdeede40097d91f30111fc6f5d", + "zh:52f968ffc21240213110378d0ffb298cbd23e9157a6d01dfac5a4360492d69c2", + "zh:5e9846b48ef03eb59541049e81b15cae8bc7696a3779ae4a5412fdce60bb24e0", + "zh:850398aecaf7dc0231fc320fdd6dffe41836e07a54c8c7b40eb28e7525d3c0a9", + "zh:8f87eeb05bdd1b873b6cfb3898dfad6402ac180dfa3c8f9754df8f85dcf92ca6", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:c726b87cd6ed111536f875dccedecff21abc802a4087264515ffab113cac36dc", + "zh:d57ea706d2f98b93c7b05b0c6bc3420de8e8cf2d0b6703085dc15ed239b2cc49", + "zh:d5d1a21246e68c2a7a04c5619eb0ad5a81644f644c432cb690537b816a156de2", + "zh:e869904cac41114b7e4ee66bcd2ce4585ed15ca842040a60cb47119f69472c91", + "zh:f1a09f2f3ea72cbe795b865cf31ad9b1866a536a8050cf0bb93d3fa51069582e", + ] +} + +provider "registry.terraform.io/hashicorp/awscc" { + version = "0.73.0" + constraints = "~> 0.9, >= 0.15.0" + hashes = [ + "h1:spGZnkmLrlLlziM/RebL2PJK6T9zwaqudUsd5w3CYa0=", + "zh:00cb92b0d7090dd0f029fbe0ef20d0bde30c0de73035f712e7eab52af7640e29", + "zh:102a39787207d20bb9be71682b0f8a47fcaee5ab01121f48f4c0e554887faecc", + "zh:1f418ac09dcb706feabaed283a233c4aa52f5021c0bacacd4eae13aac211545e", + "zh:372baa3d8728484641cf260eb29330a0a7f8ebfab09ac5b0cbdb769a445aef54", + "zh:4093d4ebc72d80be6b93e104e864fe08b846f6246331fb5b1b6c76aee605d2fc", + "zh:419818ff160a19ba7c8d03c0c65c999251e0b82ec64322cde265f7de7fdf0107", + "zh:4a513c84f9bf0f641fb3cf2576c11deeffb6044e93a7de26aba4d1ca2ea970a5", + "zh:513fbbf52984168ac11d00e4e6de5a997b77a3aebd4f9a89260a41380a86d21d", + "zh:a4970fb805328766559527b957dca00828b09498e65ff75d8484bfd4a8a3f7e6", + "zh:b73196de95f2b21fba69611b2a72c12cf994255f91bdb20abbcb09e21ca86131", + "zh:c541424e3d80fa9e2043f46c3110e0ec0bff3dfced3aebc9fd53b4c77970acdf", + "zh:dafab903d2293a7e268a2075bad105f82c9095910cdb3271696a2e2c7ee5b8b0", + "zh:db31e82a8417b04363fc6f18f828ba3c67875585168cc0740d12d03f34f2f58d", + "zh:f7bdb5b066577082f5a10db4f44c7e145d727408df1176d15f239052a1332364", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.2" + constraints = ">= 3.0.0" + hashes = [ + "h1:IMVAUHKoydFrlPrl9OzasDnw/8ntZFerCC9iXw1rXQY=", + "zh:3248aae6a2198f3ec8394218d05bd5e42be59f43a3a7c0b71c66ec0df08b69e7", + "zh:32b1aaa1c3013d33c245493f4a65465eab9436b454d250102729321a44c8ab9a", + "zh:38eff7e470acb48f66380a73a5c7cdd76cc9b9c9ba9a7249c7991488abe22fe3", + "zh:4c2f1faee67af104f5f9e711c4574ff4d298afaa8a420680b0cb55d7bbc65606", + "zh:544b33b757c0b954dbb87db83a5ad921edd61f02f1dc86c6186a5ea86465b546", + "zh:696cf785090e1e8cf1587499516b0494f47413b43cb99877ad97f5d0de3dc539", + "zh:6e301f34757b5d265ae44467d95306d61bef5e41930be1365f5a8dcf80f59452", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:913a929070c819e59e94bb37a2a253c228f83921136ff4a7aa1a178c7cce5422", + "zh:aa9015926cd152425dbf86d1abdbc74bfe0e1ba3d26b3db35051d7b9ca9f72ae", + "zh:bb04798b016e1e1d49bcc76d62c53b56c88c63d6f2dfe38821afef17c416a0e1", + "zh:c23084e1b23577de22603cff752e59128d83cfecc2e6819edadd8cf7a10af11e", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.0" + constraints = ">= 3.0.0" + hashes = [ + "h1:I8MBeauYA8J8yheLJ8oSMWqB0kovn16dF/wKZ1QTdkk=", + "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d", + "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211", + "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829", + "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d", + "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17", + "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21", + "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839", + "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0", + "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c", + "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.5" + constraints = ">= 4.0.0" + hashes = [ + "h1:zeG5RmggBZW/8JWIVrdaeSJa0OG62uFX5HY1eE8SjzY=", + "zh:01cfb11cb74654c003f6d4e32bbef8f5969ee2856394a96d127da4949c65153e", + "zh:0472ea1574026aa1e8ca82bb6df2c40cd0478e9336b7a8a64e652119a2fa4f32", + "zh:1a8ddba2b1550c5d02003ea5d6cdda2eef6870ece86c5619f33edd699c9dc14b", + "zh:1e3bb505c000adb12cdf60af5b08f0ed68bc3955b0d4d4a126db5ca4d429eb4a", + "zh:6636401b2463c25e03e68a6b786acf91a311c78444b1dc4f97c539f9f78de22a", + "zh:76858f9d8b460e7b2a338c477671d07286b0d287fd2d2e3214030ae8f61dd56e", + "zh:a13b69fb43cb8746793b3069c4d897bb18f454290b496f19d03c3387d1c9a2dc", + "zh:a90ca81bb9bb509063b736842250ecff0f886a91baae8de65c8430168001dad9", + "zh:c4de401395936e41234f1956ebadbd2ed9f414e6908f27d578614aaa529870d4", + "zh:c657e121af8fde19964482997f0de2d5173217274f6997e16389e7707ed8ece8", + "zh:d68b07a67fbd604c38ec9733069fbf23441436fecf554de6c75c032f82e1ef19", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/README.md b/README.md index 37bf231..d7f4323 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,38 @@ -![Github Actions](../../actions/workflows/terraform.yml/badge.svg) +# Terraform AWS VPN -# Terraform +This repository creates an AWS Client VPN Endpoint for the AWS Organization, which is connected to the AWS Transit Gateway. -## Description + -Add a description of the module here +## Prerequisites -## Usage +AWS SSO must be configured appropriately for the AWS Organization, for the Client VPN to be able to authenticate users. -Add example usage here +**Steps:** -```hcl -module "example" { - source = "appvia//aws" - version = "0.0.1" +1. Login to the AWS Account where AWS SSO is configured (`https://.awsapps.com/start#/` => ``) +2. Navigate to IAM Identity Center +3. On the left-hand column, navigate to `Applications` and then `Add application` +4. Tick `Add a custom SAML 2.0 application` and press `Next` +5. Provide a friendly display name for the application, e.g. `AWS Client VPN` +6. `Application start URL` can later be changed to the VPN self-service portal URL, once provisioned +7. At the bottom under `Application metadata`, specify: + 1. `Application ACS URL: http://127.0.0.1:35001` + 2. `Application SAML audience: urn:amazon:webservices:clientvpn` +8. Press `Submit` +9. Press `Assign Users` and then assign any Users or Groups who should have access to the VPN (or select all Groups for now) +10. At the top right, press `Actions` and then `Edit attribute mappings` + 1. For `Subject`, set the string value to `${user:email}` and format as `emailAddress` + 2. Add `memberOf`, set the string value to `${user:groups}` and format as `unspecified` +11. Press `Save changes` +12. Go back to `Actions` and then `Edit configuration` +13. Press `Download` to retrieve the `IAM Identity Center SAML metadata file` and store it in this repository in the `metadata` directory +14. Repeat all the steps for the `AWS Client VPN Self Service Portal`, with one change: + 1. For the `Application ACS URL`, provide the value `https://self-service.clientvpn.amazonaws.com/api/auth/sso/saml` - # insert variables here -} -``` +Once the above steps are complete, the Terraform can be applied via the GitHub CI Pipeline. -## Update Documentation +## Updating Docs The `terraform-docs` utility is used to generate this README. Follow the below steps to update: @@ -27,32 +40,116 @@ The `terraform-docs` utility is used to generate this README. Follow the below s 2. Fetch the `terraform-docs` binary (https://terraform-docs.io/user-guide/installation/) 3. Run `terraform-docs markdown table --output-file ${PWD}/README.md --output-mode inject .` +## Adding new authorization rule + +By default, all VPN access is denied, regardless of provided routing. You are required to explicitly allow access to given CIDR ranges to different SSO groups through a set of authorization rules. In order to add a new rule when the SSO Group exists already, you need to do the following: + +1. Check if the data resource was created to extract the group ID in your terraform values + +```hcl +variable "sso_groups" { + description = "SSO groups to create VPN rules for" + type = list(string) + default = [] +} +``` + +2. Add a new authorization rule explicitly in `main.tf` specifying what CIDR range is allowed for each group. Only one CIDR is allowed per rule: + +```hcl + authorization_rules = [ + { + access_group_id = data.aws_identitystore_group.groups["NAME OF THE GROUP"].group_id + description = "Allow VPN access to all internal services for Cloud Admin users" + name = "allow-all-cloud-admin" + target_network_cidr = "10.0.0.0/8" # All internal access + }, + ] +``` + +## Troubleshooting + +### Can't access required CIDRs over VPN? + +If you have added an authorization rule, but can't access the network over VPN, make sure that: + +- you have disconnected/reconnected to your VPN client (you may need to wait a couple of minutes or disconnect/reconnect a couple of times) +- you are part of the correct group +- the group ID is correct (You can find it in the Identity Center in AWS Audit Account and comapre to added rules for Client VPN in Remote Access AWS Account) +- the group has been added to both VPN applications in Identity Center in AWS Audit Account +- the resource you are trying to access has correct security group rules. + +### Want to add a new SSO group and permissions to access VPN? + +When adding a new group to SSO, there are following steps to complete: + +- Add a new group to the AWS SSO Application within [Google Admin](https://admin.google.com/u/1/ac/apps/saml/45189681917) +- Add the new group to [terraform-aws-identity](https://github.com/CUSTOMER_ORG/terraform-aws-vpn/tree/main) repository. +- Add a new group to VPN applications in Identity Center in AWS Audit Account +- Specify the allowed CIDR ranges via new authorization rule for the new group in this repository. + +## References + +AWS Blog: [AWS SSO and AWS Client VPN setup](https://aws.amazon.com/blogs/networking-and-content-delivery/using-aws-sso-with-aws-client-vpn-for-authentication-and-authorization/) +AWS Docs: [SAML-based IDP configuration](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/federated-authentication.html) +Appvia's SSO processes: [IAM Management](https://appvia.atlassian.net/wiki/spaces/SE/pages/928546817/Identity+Access+Management) + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.7 | -| [aws](#requirement\_aws) | >= 5.0.0 | -| [awscc](#requirement\_awscc) | >= 0.24.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | ~> 5.0 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [client\_vpn](#module\_client\_vpn) | cloudposse/ec2-client-vpn/aws | 1.0.0 | +| [vpc](#module\_vpc) | appvia/network/aws | 0.1.3 | ## Resources -No resources. +| Name | Type | +|------|------| +| [aws_iam_saml_provider.vpn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | +| [aws_iam_saml_provider.vpn_portal](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_saml_provider) | resource | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [authorizations\_rules](#input\_authorizations\_rules) | Authorization rules for the VPN |
list(object({
access_group_id = string
description = string
name = string
target_network_cidr = string
}))
| n/a | yes | +| [availability\_zones](#input\_availability\_zones) | Amount of availability zones to use for the VPC | `number` | n/a | yes | +| [name](#input\_name) | Name of the VPN | `string` | n/a | yes | +| [private\_subnet\_netmask](#input\_private\_subnet\_netmask) | Netmask length for the private subnets | `number` | n/a | yes | +| [saml\_provider\_document](#input\_saml\_provider\_document) | Document for the SAML provider | `string` | n/a | yes | +| [saml\_provider\_portal\_document](#input\_saml\_provider\_portal\_document) | Document for the SAML provider portal | `string` | n/a | yes | +| [tags](#input\_tags) | Tags to apply to all resources | `map(string)` | n/a | yes | +| [vpn\_log\_stream\_name](#input\_vpn\_log\_stream\_name) | Name of the CloudWatch log stream for the VPN | `string` | n/a | yes | +| [vpn\_org\_name](#input\_vpn\_org\_name) | Name of the organization for the VPN | `string` | n/a | yes | +| [client\_cidr](#input\_client\_cidr) | CIDR block for the VPN clients | `string` | `"172.16.0.0/16"` | no | +| [enable\_ipam](#input\_enable\_ipam) | Enable IPAM for the VPC | `bool` | `false` | no | +| [enable\_transit\_gateway](#input\_enable\_transit\_gateway) | Enable transit gateway for the VPC | `bool` | `true` | no | +| [ipam\_pool\_id](#input\_ipam\_pool\_id) | The ID of the IPAM pool to use for the VPC | `string` | `null` | no | +| [saml\_provider\_name](#input\_saml\_provider\_name) | Name of the SAML provider | `string` | `"Client_VPN"` | no | +| [saml\_provider\_portal\_name](#input\_saml\_provider\_portal\_name) | Name of the SAML provider portal | `string` | `"Client_VPN_Portal"` | no | +| [transit\_gateway\_id](#input\_transit\_gateway\_id) | ID of the transit gateway to use for the VPC | `string` | `""` | no | +| [vpc\_cidr](#input\_vpc\_cidr) | CIDR block for the VPC, when not using IPAM | `string` | `null` | no | +| [vpc\_netmask](#input\_vpc\_netmask) | Netmask length for the VPN VPC, when using IPAM | `number` | `0` | no | +| [vpn\_log\_retention](#input\_vpn\_log\_retention) | Number of days to retain VPN logs | `number` | `7` | no | ## Outputs -No outputs. - +| Name | Description | +|------|-------------| +| [client\_configuration](#output\_client\_configuration) | VPN Client Configuration data. | +| [vpn\_endpoint\_dns\_name](#output\_vpn\_endpoint\_dns\_name) | The DNS Name of the Client VPN Endpoint Connection. | + \ No newline at end of file diff --git a/docs/architecture.png b/docs/architecture.png new file mode 100644 index 0000000..0073115 Binary files /dev/null and b/docs/architecture.png differ diff --git a/examples/basic/README.md b/examples/basic/README.md index 8828d2e..4c991f8 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -5,23 +5,49 @@ |------|---------| | [terraform](#requirement\_terraform) | >= 1.0.0 | | [aws](#requirement\_aws) | >= 5.0.0 | -| [awscc](#requirement\_awscc) | >= 0.11.0 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0.0 | ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [vpn](#module\_vpn) | appvia/vpn/aws | 0.0.2 | ## Resources -No resources. +| Name | Type | +|------|------| +| [aws_identitystore_group.groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/identitystore_group) | data source | +| [aws_ssoadmin_instances.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssoadmin_instances) | data source | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [availability\_zones](#input\_availability\_zones) | Amount of availability zones to use for the VPC | `number` | n/a | yes | +| [name](#input\_name) | Name of the VPN | `string` | n/a | yes | +| [private\_subnet\_netmask](#input\_private\_subnet\_netmask) | Netmask length for the private subnets | `number` | n/a | yes | +| [saml\_provider\_document](#input\_saml\_provider\_document) | Document for the SAML provider | `string` | n/a | yes | +| [saml\_provider\_portal\_document](#input\_saml\_provider\_portal\_document) | Document for the SAML provider portal | `string` | n/a | yes | +| [tags](#input\_tags) | Tags to apply to all resources | `map(string)` | n/a | yes | +| [vpn\_log\_stream\_name](#input\_vpn\_log\_stream\_name) | Name of the CloudWatch log stream for the VPN | `string` | n/a | yes | +| [vpn\_org\_name](#input\_vpn\_org\_name) | Name of the organization for the VPN | `string` | n/a | yes | +| [client\_cidr](#input\_client\_cidr) | CIDR block for the VPN clients | `string` | `"172.16.0.0/16"` | no | +| [enable\_ipam](#input\_enable\_ipam) | Enable IPAM for the VPC | `bool` | `false` | no | +| [enable\_transit\_gateway](#input\_enable\_transit\_gateway) | Enable transit gateway for the VPC | `bool` | `true` | no | +| [ipam\_pool\_id](#input\_ipam\_pool\_id) | The ID of the IPAM pool to use for the VPC | `string` | `null` | no | +| [saml\_provider\_name](#input\_saml\_provider\_name) | Name of the SAML provider | `string` | `"Client_VPN"` | no | +| [saml\_provider\_portal\_name](#input\_saml\_provider\_portal\_name) | Name of the SAML provider portal | `string` | `"Client_VPN_Portal"` | no | +| [sso\_groups](#input\_sso\_groups) | SSO groups to create VPN rules for | `list(string)` | `[]` | no | +| [transit\_gateway\_id](#input\_transit\_gateway\_id) | ID of the transit gateway to use for the VPC | `string` | `""` | no | +| [vpc\_cidr](#input\_vpc\_cidr) | CIDR block for the VPC, when not using IPAM | `string` | `null` | no | +| [vpc\_netmask](#input\_vpc\_netmask) | Netmask length for the VPN VPC, when using IPAM | `number` | `0` | no | +| [vpn\_log\_retention](#input\_vpn\_log\_retention) | Number of days to retain VPN logs | `number` | `7` | no | ## Outputs diff --git a/examples/basic/main.tf b/examples/basic/main.tf index b2619ce..534e37e 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -3,3 +3,94 @@ # per use-case. The code below should not be copied directly but referenced in order # to build your own root module that invokes this module ##################################################################################### + +## Get the SSO Instance +data "aws_ssoadmin_instances" "current" {} + +## Retrieve and checks the sso groups +data "aws_identitystore_group" "groups" { + for_each = toset(var.sso_groups) + + identity_store_id = local.identity_store_id + + alternate_identifier { + unique_attribute { + attribute_path = "DisplayName" + attribute_value = each.value + } + } +} + +locals { + ## The instance ARN for the identity store + instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] + ## The identity store ID + identity_store_id = tolist(data.aws_ssoadmin_instances.this.identity_store_ids)[0] + + networks = { + "all-internal" = "10.0.0.0/8" + "gh-runners" = "10.24.0.0/24" + "pool-development-1" = "10.8.0.0/13" + "pool-development-2" = "10.16.0.0/13" + "pool-operations" = "10.24.0.0/15" + "pool-production" = "10.0.0.0/13" + "wayfinder" = "10.24.8.0/21" + } + + team_authorization_rules = { + "administrators" = [ + { + access_group_id = data.aws_identitystore_group.groups["Cloud Admins"].group_id + description = "Allow VPN access to all internal services for Cloud Admin users" + name = "cloud-admin-allow-all" + target_network_cidr = local.networks.all-internal + } + ], + "all-users" = [ + { + access_group_id = data.aws_identitystore_group.groups["Cloud Users"].group_id + description = "Allow VPN access to Wayfinder for all Cloud Users" + name = "cloud-users-allow-wayfinder" + target_network_cidr = local.networks.wayfinder + } + ], + } + + authorization_rules = flatten([ + for team, rules in local.team_authorization_rules : [ + for rule in rules : { + access_group_id = rule.access_group_id + description = rule.description + name = rule.name + target_network_cidr = rule.target_network_cidr + } + ] + ]) +} + +## Provision the AWS VPN +module "vpn" { + source = "appvia/vpn/aws" + version = "0.0.2" + + availability_zones = var.availability_zones + authorization_rules = local.authorization_rules + client_cidr = var.client_cidr + enable_ssm = var.enable_ssm + identity_store_id = var.identity_store_id + ipam_pool_name = var.ipam_pool_name + name = var.name + private_subnet_netmasks = var.private_subnet_netmasks + public_subnet_netmasks = var.public_subnet_netmasks + saml_provider_document = file("${path.module}/metadata/saml.xml") + saml_provider_name = var.saml_provider_name + saml_provider_portal_document = file("${path.module}/metadata/saml_portal.xml") + saml_provider_portal_name = var.saml_provider_portal_name + sso_groups = var.sso_groups + tags = var.tags + transit_gateway_id = var.transit_gateway_id + vpc_netmask = var.vpc_netmask + vpn_log_retention = var.vpn_log_retention + vpn_log_stream_name = var.vpn_log_stream_name + vpn_org_name = var.vpn_org_name +} diff --git a/examples/basic/terraform.tf b/examples/basic/terraform.tf index 7bc4a1e..c3db407 100644 --- a/examples/basic/terraform.tf +++ b/examples/basic/terraform.tf @@ -7,9 +7,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.0.0" } - awscc = { - source = "hashicorp/awscc" - version = ">= 0.11.0" - } } } diff --git a/examples/basic/values/production.tfvars b/examples/basic/values/production.tfvars new file mode 100644 index 0000000..fadd75c --- /dev/null +++ b/examples/basic/values/production.tfvars @@ -0,0 +1,19 @@ +availability_zones = 2 +identity_store_id = "" +public_subnet_netmask = 27 +private_subnet_netmask = 27 +transit_subnet_netmask = 28 +vpc_netmask = 24 +vpn_log_retention = 7 +vpn_log_stream_name = "-client-vpn" +vpn_name = "-vpn" +vpn_org_name = " Ltd" + +tags = { + BusinessCriticality = "High" + Environment = "Production" + Owner = "SupportTeam" + Project = "Operations" + Repository = "https://github.com//terraform-aws-vpn" + Provisioner = "Terraform" +} diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index e69de29..3c46e64 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -0,0 +1,106 @@ +variable "availability_zones" { + description = "Amount of availability zones to use for the VPC" + type = number +} + +variable "saml_provider_document" { + description = "Document for the SAML provider" + type = string +} + +variable "saml_provider_name" { + description = "Name of the SAML provider" + type = string + default = "Client_VPN" +} + +variable "saml_provider_portal_document" { + description = "Document for the SAML provider portal" + type = string +} + +variable "saml_provider_portal_name" { + description = "Name of the SAML provider portal" + type = string + default = "Client_VPN_Portal" +} + +variable "client_cidr" { + description = "CIDR block for the VPN clients" + type = string + default = "172.16.0.0/16" +} + +variable "enable_ipam" { + description = "Enable IPAM for the VPC" + type = bool + default = false +} + +variable "enable_transit_gateway" { + description = "Enable transit gateway for the VPC" + type = bool + default = true +} + +variable "ipam_pool_id" { + description = "The ID of the IPAM pool to use for the VPC" + type = string + default = null +} + +variable "private_subnet_netmask" { + description = "Netmask length for the private subnets" + type = number +} + +variable "sso_groups" { + description = "SSO groups to create VPN rules for" + type = list(string) + default = [] +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) +} + +variable "transit_gateway_id" { + description = "ID of the transit gateway to use for the VPC" + type = string + default = "" +} + +variable "vpc_cidr" { + description = "CIDR block for the VPC, when not using IPAM" + type = string + default = null +} + +variable "vpc_netmask" { + description = "Netmask length for the VPN VPC, when using IPAM" + type = number + default = 0 + +} + +variable "vpn_log_retention" { + description = "Number of days to retain VPN logs" + type = number + default = 7 +} + +variable "vpn_log_stream_name" { + description = "Name of the CloudWatch log stream for the VPN" + type = string +} + +variable "name" { + description = "Name of the VPN" + type = string +} + +variable "vpn_org_name" { + description = "Name of the organization for the VPN" + type = string +} diff --git a/main.tf b/main.tf index e69de29..917cfd3 100644 --- a/main.tf +++ b/main.tf @@ -0,0 +1,67 @@ + +locals { + ## The vpc id for the VPN + vpc_id = module.vpc.vpc_id +} + +## Provision the VPC for VPN +module "vpc" { + source = "appvia/network/aws" + version = "0.1.3" + + name = var.name + availability_zones = var.availability_zones + enable_ipam = var.enable_ipam + enable_transit_gateway = var.enable_transit_gateway + ipam_pool_id = var.ipam_pool_id + private_subnet_netmask = var.private_subnet_netmask + tags = var.tags + transit_gateway_id = var.transit_gateway_id + vpc_cidr = var.vpc_cidr + vpc_netmask = var.vpc_netmask +} + +## Provision the SAML provider +resource "aws_iam_saml_provider" "vpn" { + name = var.saml_provider_name + saml_metadata_document = var.saml_provider_document + tags = var.tags +} + +## Provision the SAML provider for the self-service portal +resource "aws_iam_saml_provider" "vpn_portal" { + name = var.saml_provider_portal_name + saml_metadata_document = var.saml_provider_portal_document + tags = var.tags +} + +## Provision the VPN +# tfsec:ignore:aws-cloudwatch-log-group-customer-key +module "client_vpn" { + source = "cloudposse/ec2-client-vpn/aws" + version = "1.0.0" + + associated_subnets = module.vpc.public_subnet_list + authentication_type = "federated-authentication" + authorization_rules = var.authorizations_rules + client_cidr = var.client_cidr + logging_enabled = true + logging_stream_name = var.vpn_log_stream_name + name = var.name + organization_name = var.vpn_org_name + retention_in_days = var.vpn_log_retention + saml_provider_arn = aws_iam_saml_provider.vpn.arn + self_service_portal_enabled = true + self_service_saml_provider_arn = aws_iam_saml_provider.vpn_portal.arn + split_tunnel = true + tags = var.tags + vpc_id = local.vpc_id + + additional_routes = [ + for subnet in module.vpc.public_subnet_attributes_by_az : { + description = "Route to all internal services" + destination_cidr_block = "10.0.0.0/8" + target_vpc_subnet_id = subnet.id + } + ] +} diff --git a/outputs.tf b/outputs.tf index e69de29..e462624 100644 --- a/outputs.tf +++ b/outputs.tf @@ -0,0 +1,9 @@ +output "vpn_endpoint_dns_name" { + value = module.client_vpn.vpn_endpoint_dns_name + description = "The DNS Name of the Client VPN Endpoint Connection." +} + +output "client_configuration" { + value = module.client_vpn.client_configuration + description = "VPN Client Configuration data." +} diff --git a/terraform.tf b/terraform.tf index 327bae4..bc82a35 100644 --- a/terraform.tf +++ b/terraform.tf @@ -1,15 +1,11 @@ terraform { - required_version = ">= 1.0.7" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.0.0" - } - awscc = { - source = "hashicorp/awscc" - version = ">= 0.24.0" + version = "~> 5.0" } } } diff --git a/variables.tf b/variables.tf index e69de29..cf615f9 100644 --- a/variables.tf +++ b/variables.tf @@ -0,0 +1,110 @@ +variable "authorizations_rules" { + description = "Authorization rules for the VPN" + type = list(object({ + access_group_id = string + description = string + name = string + target_network_cidr = string + })) +} + +variable "availability_zones" { + description = "Amount of availability zones to use for the VPC" + type = number +} + +variable "saml_provider_document" { + description = "Document for the SAML provider" + type = string +} + +variable "saml_provider_name" { + description = "Name of the SAML provider" + type = string + default = "Client_VPN" +} + +variable "saml_provider_portal_document" { + description = "Document for the SAML provider portal" + type = string +} + +variable "saml_provider_portal_name" { + description = "Name of the SAML provider portal" + type = string + default = "Client_VPN_Portal" +} + +variable "client_cidr" { + description = "CIDR block for the VPN clients" + type = string + default = "172.16.0.0/16" +} + +variable "enable_ipam" { + description = "Enable IPAM for the VPC" + type = bool + default = false +} + +variable "enable_transit_gateway" { + description = "Enable transit gateway for the VPC" + type = bool + default = true +} + +variable "ipam_pool_id" { + description = "The ID of the IPAM pool to use for the VPC" + type = string + default = null +} + +variable "private_subnet_netmask" { + description = "Netmask length for the private subnets" + type = number +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) +} + +variable "transit_gateway_id" { + description = "ID of the transit gateway to use for the VPC" + type = string + default = "" +} + +variable "vpc_cidr" { + description = "CIDR block for the VPC, when not using IPAM" + type = string + default = null +} + +variable "vpc_netmask" { + description = "Netmask length for the VPN VPC, when using IPAM" + type = number + default = 0 + +} + +variable "vpn_log_retention" { + description = "Number of days to retain VPN logs" + type = number + default = 7 +} + +variable "vpn_log_stream_name" { + description = "Name of the CloudWatch log stream for the VPN" + type = string +} + +variable "name" { + description = "Name of the VPN" + type = string +} + +variable "vpn_org_name" { + description = "Name of the organization for the VPN" + type = string +}