diff --git a/README.md b/README.md index 5babc72..34ec16a 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,71 @@ # Terraform AWS Tailscale Module -This module is used to deploy a [Tailscale powered image](https://tailscale.com) to create access from VPC to the Tailscale Cloud. -## To deploy this module: +This module is used to deploy a [Tailscale](https://tailscale.com) router instance to set up access from VPC to the +Tailscale Cloud. + +Module logic is the following: + +## Usage + +_Please refer to [Tailscale Configuration](#tailscale-configuration) first_ + +```terraform +module "tailscale" { + source = "registry.terraform.io/hazelops/tailscale/aws" + version = "~>0.2" + allowed_cidr_blocks = ["0.0.0.0/0"] # Please lock this down to your specific CIDR + ec2_key_pair_name = "default-key" + env = "prod" + subnets = ["subnet-0000000", "subnet-0000000"] + vpc_id = "vpc-0000000" + api_token = "00000000000000000000000000" # Please don't store secrets in plain text +} + +``` + +## Tailscale Configuration + +1. Create [Tailscale API access token](https://tailscale.com/kb/1252/key-secret-management#api-access-tokens) +2. Add tag to the [ACL control list](https://login.tailscale.com/admin/acls/file). ACL should look like this: - 1. Create [Tailscale API access token](https://tailscale.com/kb/1252/key-secret-management#api-access-tokens) - 2. Add tag to the [ACL control list](https://login.tailscale.com/admin/acls/file). ACL should look like this: ```json { "acls": [ { "action": "accept", "ports": [ - "*:*", + "*:*" ], "users": [ - "*", - ], - }, + "*" + ] + } ], "tagOwners": { - "tag:": [], - }, + "tag:": [] + } } - ``` - **_The tag must be added to the ACL to disable automatic key expiration!_** - - Default parameter for tag is `tag:`. - - You could found more examples in [Tailscale manual](https://tailscale.com/kb/1068/acl-tags#defining-a-tag). +``` - 3. Create AWS SSM Parameter using obtained Tailscale API access token. For example, use the following path pattern: `/global/tailscale_api_token`. For more information please refer to [AWS manual](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-create-console.html). - 4. Add data source to Terraform code like in the [example configuration main.tf file](./examples/minimum/main.tf). - 5. In the module call parameters, set `api_token` variable like in the [example configuration main.tf file](./examples/minimum/main.tf). - 6. Alternatively Tailscale API token could be set as string, but this is very unsafe, therefore it is **_highly not recommended_** to do this. +**_The tag must be added to the ACL to disable automatic key expiration!_** +Default parameter for tag is `tag:`. +More examples can be found in [Tailscale manual](https://tailscale.com/kb/1068/acl-tags#defining-a-tag). -## Possible problems on module destroy: +3. Create AWS SSM Parameter using the obtained Tailscale API access token. For example, use the following path + pattern: `/global/tailscale_api_token`. For more information please refer + to [AWS manual](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-create-console.html). +4. Add data source to Terraform code like in the [example configuration main.tf file](./examples/minimum/main.tf). +5. In the module call parameters, set `api_token` variable like in + the [example configuration main.tf file](./examples/minimum/main.tf). +6. Alternatively Tailscale API token could be set as string, but this is very unsafe, therefore it is * + *_highly not recommended_** to do this. + +## Troubleshooting The following error may occur during module removal: + ```text Error: Provider configuration not present @@ -58,24 +85,25 @@ To remove it, run the following code: terraform state rm module.tailscale.tailscale_tailnet_key.this ``` + ## Requirements -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >=1.2.0 | -| [aws](#requirement\_aws) | >=4.30.0 | -| [local](#requirement\_local) | ~> 1.2 | -| [tailscale](#requirement\_tailscale) | 0.13.13 | -| [template](#requirement\_template) | >=2.2 | +| Name | Version | +|---------------------------------------------------------------------------|----------| +| [terraform](#requirement\_terraform) | >=1.2.0 | +| [aws](#requirement\_aws) | >=4.30.0 | +| [local](#requirement\_local) | ~> 1.2 | +| [tailscale](#requirement\_tailscale) | 0.13.13 | +| [template](#requirement\_template) | >=2.2 | ## Providers -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >=4.30.0 | -| [tailscale](#provider\_tailscale) | 0.13.13 | -| [template](#provider\_template) | >=2.2 | +| Name | Version | +|---------------------------------------------------------------------|----------| +| [aws](#provider\_aws) | >=4.30.0 | +| [tailscale](#provider\_tailscale) | 0.13.13 | +| [template](#provider\_template) | >=2.2 | ## Modules @@ -83,47 +111,49 @@ No modules. ## Resources -| Name | Type | -|------|------| -| [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | 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 | -| [tailscale_tailnet_key.this](https://registry.terraform.io/providers/tailscale/tailscale/0.13.13/docs/resources/tailnet_key) | resource | -| [aws_ami.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | -| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [template_file.ec2_user_data](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source | +| Name | Type | +|-----------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | 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 | +| [tailscale_tailnet_key.this](https://registry.terraform.io/providers/tailscale/tailscale/0.13.13/docs/resources/tailnet_key) | resource | +| [aws_ami.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [template_file.ec2_user_data](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source | ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of network subnets that are allowed. According to PCI-DSS, CIS AWS and SOC2 providing a default wide-open CIDR is not secure. | `list(string)` | n/a | yes | -| [ami\_id](#input\_ami\_id) | Optional AMI ID for Tailscale instance. Otherwise latest Amazon Linux will be used. | `string` | `""` | no | -| [api\_token](#input\_api\_token) | Set Tailscale API access token here | `string` | n/a | yes | -| [asg](#input\_asg) | Scaling settings of an Auto Scaling Group | `map` |
{
"max_size": 1,
"min_size": 1
}
| no | -| [ec2\_key\_pair\_name](#input\_ec2\_key\_pair\_name) | n/a | `string` | n/a | yes | -| [env](#input\_env) | n/a | `string` | n/a | yes | -| [ext\_security\_groups](#input\_ext\_security\_groups) | External security groups to add to the Tailscale instance | `list(any)` | `[]` | no | -| [instance\_type](#input\_instance\_type) | Set type of Tailscale instance | `string` | `"t3.nano"` | no | -| [key\_ephemeral](#input\_key\_ephemeral) | Indicates if the key is ephemeral | `bool` | `true` | no | -| [key\_expiry](#input\_key\_expiry) | The expiry of the key in seconds. Defaults to 7776000 (90 days) | `number` | `7776000` | no | -| [key\_preauthorized](#input\_key\_preauthorized) | Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default | `bool` | `true` | no | -| [key\_reusable](#input\_key\_reusable) | Indicates if the key is reusable or single-use | `bool` | `true` | no | -| [monitoring\_enabled](#input\_monitoring\_enabled) | Enable monitoring for the Auto Scaling Group | `bool` | `true` | no | -| [name](#input\_name) | Set a name for Tailscale instance | `string` | `"tailscale-router"` | no | -| [public\_ip\_enabled](#input\_public\_ip\_enabled) | Enable Public IP for Tailscale instance | `bool` | `false` | no | -| [ssm\_role\_arn](#input\_ssm\_role\_arn) | SSM role to attach to a Tailscale instance | `string` | `"arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"` | no | -| [subnets](#input\_subnets) | Subnets where the Taiscale instance will be placed. It is recommended to use a private subnet for better security. | `list(string)` | n/a | yes | -| [tags](#input\_tags) | A device is automatically tagged when it is authenticated with this key | `list(string)` | `[]` | no | -| [vpc\_id](#input\_vpc\_id) | n/a | `string` | n/a | yes | +| Name | Description | Type | Default | Required | +|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------|--------------------------------------------------------------|:--------:| +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of network subnets that are allowed. According to PCI-DSS, CIS AWS and SOC2 providing a default wide-open CIDR is not secure. | `list(string)` | n/a | yes | +| [ami\_id](#input\_ami\_id) | Optional AMI ID for Tailscale instance. Otherwise latest Amazon Linux will be used. | `string` | `""` | no | +| [api\_token](#input\_api\_token) | Set Tailscale API access token here | `string` | n/a | yes | +| [asg](#input\_asg) | Scaling settings of an Auto Scaling Group | `map` |
{
"max_size": 1,
"min_size": 1
}
| no | +| [ec2\_key\_pair\_name](#input\_ec2\_key\_pair\_name) | n/a | `string` | n/a | yes | +| [env](#input\_env) | n/a | `string` | n/a | yes | +| [ext\_security\_groups](#input\_ext\_security\_groups) | External security groups to add to the Tailscale instance | `list(any)` | `[]` | no | +| [instance\_type](#input\_instance\_type) | Set type of Tailscale instance | `string` | `"t3.nano"` | no | +| [key\_ephemeral](#input\_key\_ephemeral) | Indicates if the key is ephemeral | `bool` | `true` | no | +| [key\_expiry](#input\_key\_expiry) | The expiry of the key in seconds. Defaults to 7776000 (90 days) | `number` | `7776000` | no | +| [key\_preauthorized](#input\_key\_preauthorized) | Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default | `bool` | `true` | no | +| [key\_reusable](#input\_key\_reusable) | Indicates if the key is reusable or single-use | `bool` | `true` | no | +| [monitoring\_enabled](#input\_monitoring\_enabled) | Enable monitoring for the Auto Scaling Group | `bool` | `true` | no | +| [name](#input\_name) | Set a name for Tailscale instance | `string` | `"tailscale-router"` | no | +| [public\_ip\_enabled](#input\_public\_ip\_enabled) | Enable Public IP for Tailscale instance | `bool` | `false` | no | +| [ssm\_role\_arn](#input\_ssm\_role\_arn) | SSM role to attach to a Tailscale instance | `string` | `"arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"` | no | +| [subnets](#input\_subnets) | Subnets where the Taiscale instance will be placed. It is recommended to use a private subnet for better security. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | A device is automatically tagged when it is authenticated with this key | `list(string)` | `[]` | no | +| [vpc\_id](#input\_vpc\_id) | n/a | `string` | n/a | yes | ## Outputs -| Name | Description | -|------|-------------| -| [autoscaling\_group\_id](#output\_autoscaling\_group\_id) | n/a | -| [name](#output\_name) | n/a | -| [security\_group\_id](#output\_security\_group\_id) | n/a | +| Name | Description | +|------------------------------------------------------------------------------------------------------|-------------| +| [autoscaling\_group\_id](#output\_autoscaling\_group\_id) | n/a | +| [name](#output\_name) | n/a | +| [security\_group\_id](#output\_security\_group\_id) | n/a | + + diff --git a/data.tf b/data.tf index 3a63e44..4e7c1ee 100644 --- a/data.tf +++ b/data.tf @@ -1,6 +1,6 @@ # Generate userdata for Tailscale instance data "template_file" "ec2_user_data" { - template = file("${path.module}/ec2_user_data.yml.tpl") + template = file("${path.module}/templates/ec2_user_data.tpl.yml") vars = { auth_key = tailscale_tailnet_key.this.key @@ -9,9 +9,8 @@ data "template_file" "ec2_user_data" { } } -# Download latest AMI info for Amazon Linux 2023 +# Get latest AMI info for Amazon Linux 2023 data "aws_ami" "this" { - most_recent = true filter { diff --git a/examples/minimal/main.tf b/examples/minimal/main.tf index 3f9fe2b..28599d3 100644 --- a/examples/minimal/main.tf +++ b/examples/minimal/main.tf @@ -7,7 +7,7 @@ variable "vpc_cidr_block" {} variable "ssh_key_id" {} variable "aws_key_name" {} -# Obtain Tailscale auth key from AWS SSM Parameter Store +# Obtain Tailscale api key from AWS SSM Parameter Store data "aws_ssm_parameter" "tailscale_api_token" { name = "/${var.env}/global/tailscale_api_token" } diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..f559bfb --- /dev/null +++ b/locals.tf @@ -0,0 +1,4 @@ +locals { + name = "${var.env}-${var.name}" + tags = concat(["tag:${var.env}"], var.tags) +} diff --git a/output.tf b/output.tf index aedaaa8..6257232 100644 --- a/output.tf +++ b/output.tf @@ -2,7 +2,7 @@ output "security_group_id" { value = element(aws_security_group.this.*.id, 0) } -output "autoscaling_group_id"{ +output "autoscaling_group_id" { value = aws_autoscaling_group.this.id } diff --git a/security.tf b/security.tf index 86f1644..4c40519 100644 --- a/security.tf +++ b/security.tf @@ -21,7 +21,7 @@ resource "aws_security_group" "this" { } tags = { - Name = local.name + Name = local.name } lifecycle { diff --git a/ec2_user_data.yml.tpl b/templates/ec2_user_data.tpl.yml similarity index 100% rename from ec2_user_data.yml.tpl rename to templates/ec2_user_data.tpl.yml diff --git a/variables.tf b/variables.tf index 97c4875..dcd8dde 100644 --- a/variables.tf +++ b/variables.tf @@ -1,12 +1,15 @@ variable "env" { - type = string + type = string + description = "Environment name (typically dev/prod)" } variable "vpc_id" { - type = string + type = string + description = "VPC ID where the Tailscale instance will be placed" } variable "ec2_key_pair_name" { - type = string + type = string + description = "EC2 key pair name to use for Tailscale instance" } variable "subnets" { @@ -16,26 +19,26 @@ variable "subnets" { variable "ami_id" { type = string + description = "Optional AMI ID for Tailscale instance. Otherwise latest Amazon Linux will be used. One might want to lock this down to avoid unexpected upgrades." default = "" - description = "Optional AMI ID for Tailscale instance. Otherwise latest Amazon Linux will be used." } variable "name" { type = string default = "tailscale-router" - description = "Set a name for Tailscale instance" + description = "Name for Tailscale instance" } variable "instance_type" { type = string default = "t3.nano" - description = "Set type of Tailscale instance" + description = "Type of Tailscale instance" } variable "public_ip_enabled" { type = bool default = false - description = "Enable Public IP for Tailscale instance" + description = "Wheter to enable a public IP for Tailscale instance" } variable "ext_security_groups" { @@ -56,6 +59,7 @@ variable "ssm_role_arn" { } variable "asg" { + type = map(any) default = { min_size = 1 max_size = 1 @@ -66,45 +70,40 @@ variable "asg" { variable "monitoring_enabled" { type = bool default = true - description = "Enable monitoring for the Auto Scaling Group" + description = "Whether to enable monitoring for the Auto Scaling Group" } variable "api_token" { type = string - description = "Set Tailscale API access token here" + description = "Tailscale API access token" } variable "key_expiry" { type = number default = 7776000 - description = "The expiry of the key in seconds. Defaults to 7776000 (90 days)" + description = "Expiry of the key in seconds. Defaults to 7776000 (90 days)" } variable "key_reusable" { type = bool default = true - description = "Indicates if the key is reusable or single-use" + description = "Indicates whether the key is reusable" } variable "key_ephemeral" { type = bool default = true - description = "Indicates if the key is ephemeral" + description = "Indicates whether the key is ephemeral" } variable "key_preauthorized" { type = bool default = true - description = "Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default" + description = "Determines whether or not the machines authenticated by the key will be authorized for the Tailnet by default" } variable "tags" { type = list(string) default = [] - description = "A device is automatically tagged when it is authenticated with this key" -} - -locals { - name = "${var.env}-${var.name}" - tags = concat(["tag:${var.env}"], var.tags) + description = "List of tags for the Tailnet device. It would be automatically tagged when it is authenticated with this key" } diff --git a/versions.tf b/versions.tf index be2e3e0..872e445 100644 --- a/versions.tf +++ b/versions.tf @@ -1,7 +1,7 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" + source = "hashicorp/aws" version = ">=4.30.0" } template = {