Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ module "tailscale" {
vpc_id = module.vpc.vpc_id
subnet_ids = module.subnets.private_subnet_ids
advertise_routes = [module.vpc.vpc_cidr_block]

ephemeral = true
}
```

Expand Down Expand Up @@ -182,6 +180,7 @@ The above configuration ensures that the subnet router can establish direct conn
|------|------|
| [aws_iam_role_policy_attachment.cw_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [tailscale_oauth_client.default](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/oauth_client) | resource |
| [tailscale_tailnet_key.default](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/tailnet_key) | resource |

## Inputs
Expand All @@ -199,16 +198,15 @@ The above configuration ensures that the subnet router can establish direct conn
| <a name="input_architecture"></a> [architecture](#input\_architecture) | The architecture of the AMI (e.g., x86\_64, arm64) | `string` | `"arm64"` | no |
| <a name="input_associate_public_ip_address"></a> [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Associate public IP address with subnet router | `bool` | `null` | no |
| <a name="input_attributes"></a> [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,<br/>in the order they appear in the list. New attributes are appended to the<br/>end of the list. The elements of the list are joined by the `delimiter`<br/>and treated as a single ID element. | `list(string)` | `[]` | no |
| <a name="input_authkey_config"></a> [authkey\_config](#input\_authkey\_config) | Configuration for the auth key used in `tailscale up` command.<br/><br/>One of `tailscale_oauth_client` or `tailscale_tailnet_key` must be set.<br/><br/>For both options, the `description` is taken from `module.this.id` and the `tags` from what is passed to the `tailscale up command` via `--advertise-tags=<tags>` flag.<br/><br/>Minimal `scopes` required for `tailscale_oauth_client` are `["auth_keys", "devices:core", "devices:routes", "dns"]`.<br/><br/>For additional information, please visit:<br/>- [tailscale up command](https://tailscale.com/docs/reference/tailscale-cli/up)<br/>- [Terraform tailscale\_oauth\_client](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/oauth_client)<br/>- [Terraform tailscale\_tailnet\_key](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/tailnet_key) | <pre>object({<br/> tailscale_oauth_client = optional(object({<br/> scopes = list(string)<br/> }))<br/> tailscale_tailnet_key = optional(object({<br/> ephemeral = bool<br/> expiry = number<br/> preauthorized = bool<br/> reusable = bool<br/> }))<br/> })</pre> | <pre>{<br/> "tailscale_tailnet_key": {<br/> "ephemeral": false,<br/> "expiry": 7776000,<br/> "preauthorized": true,<br/> "reusable": true<br/> }<br/>}</pre> | no |
| <a name="input_context"></a> [context](#input\_context) | Single object for setting entire context at once.<br/>See description of individual variables for details.<br/>Leave string and numeric variables as `null` to use default value.<br/>Individual variable settings (non-null) override settings in context object,<br/>except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | <pre>{<br/> "additional_tag_map": {},<br/> "attributes": [],<br/> "delimiter": null,<br/> "descriptor_formats": {},<br/> "enabled": true,<br/> "environment": null,<br/> "id_length_limit": null,<br/> "label_key_case": null,<br/> "label_order": [],<br/> "label_value_case": null,<br/> "labels_as_tags": [<br/> "unset"<br/> ],<br/> "name": null,<br/> "namespace": null,<br/> "regex_replace_chars": null,<br/> "stage": null,<br/> "tags": {},<br/> "tenant": null<br/>}</pre> | no |
| <a name="input_create_run_shell_document"></a> [create\_run\_shell\_document](#input\_create\_run\_shell\_document) | Whether or not to create the SSM-SessionManagerRunShell SSM Document. | `bool` | `true` | no |
| <a name="input_delimiter"></a> [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.<br/>Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| <a name="input_descriptor_formats"></a> [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.<br/>Map of maps. Keys are names of descriptors. Values are maps of the form<br/>`{<br/> format = string<br/> labels = list(string)<br/>}`<br/>(Type is `any` so the map values can later be enhanced to provide additional options.)<br/>`format` is a Terraform format string to be passed to the `format()` function.<br/>`labels` is a list of labels, in order, to pass to `format()` function.<br/>Label values will be normalized before being passed to `format()` so they will be<br/>identical to how they appear in `id`.<br/>Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no |
| <a name="input_desired_capacity"></a> [desired\_capacity](#input\_desired\_capacity) | Desired number of instances in the Auto Scaling Group | `number` | `1` | no |
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| <a name="input_ephemeral"></a> [ephemeral](#input\_ephemeral) | Indicates if the key is ephemeral. | `bool` | `false` | no |
| <a name="input_exit_node_enabled"></a> [exit\_node\_enabled](#input\_exit\_node\_enabled) | Advertise Tailscale Subnet Router EC2 instance as exit node. Defaults to false. | `bool` | `false` | no |
| <a name="input_expiry"></a> [expiry](#input\_expiry) | The expiry of the auth key in seconds. | `number` | `7776000` | no |
| <a name="input_id_length_limit"></a> [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).<br/>Set to `0` for unlimited length.<br/>Set to `null` for keep the existing setting, which defaults to `0`.<br/>Does not affect `id_full`. | `number` | `null` | no |
| <a name="input_instance_type"></a> [instance\_type](#input\_instance\_type) | The instance type to use for the Tailscale Subnet Router EC2 instance. | `string` | `"t4g.nano"` | no |
| <a name="input_journald_max_retention_sec"></a> [journald\_max\_retention\_sec](#input\_journald\_max\_retention\_sec) | The maximum time to store journal entries. | `string` | `"7d"` | no |
Expand All @@ -223,10 +221,8 @@ The above configuration ensures that the subnet router can establish direct conn
| <a name="input_monitoring_enabled"></a> [monitoring\_enabled](#input\_monitoring\_enabled) | Enable detailed monitoring of instances | `bool` | `true` | no |
| <a name="input_name"></a> [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.<br/>This is the only ID element not also included as a `tag`.<br/>The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
| <a name="input_namespace"></a> [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 |
| <a name="input_preauthorized"></a> [preauthorized](#input\_preauthorized) | Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default. | `bool` | `true` | no |
| <a name="input_primary_tag"></a> [primary\_tag](#input\_primary\_tag) | The primary tag to apply to the Tailscale Subnet Router machine. Do not include the `tag:` prefix. This must match the OAuth client's tag. If not provided, the module will use the module's ID as the primary tag, which is configured in context.tf | `string` | `null` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.<br/>Characters matching the regex will be removed from the ID elements.<br/>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_reusable"></a> [reusable](#input\_reusable) | Indicates if the key is reusable or single-use. | `bool` | `true` | no |
| <a name="input_session_logging_enabled"></a> [session\_logging\_enabled](#input\_session\_logging\_enabled) | To enable CloudWatch and S3 session logging or not.<br/> Note this does not apply to SSH sessions as AWS cannot log those sessions. | `bool` | `true` | no |
| <a name="input_session_logging_kms_key_alias"></a> [session\_logging\_kms\_key\_alias](#input\_session\_logging\_kms\_key\_alias) | Alias name for `session_logging` KMS Key.<br/> This is only applied if 2 conditions are met: (1) `session_logging_kms_key_arn` is unset,<br/> (2) `session_logging_encryption_enabled` = true. | `string` | `"alias/session_logging"` | no |
| <a name="input_session_logging_ssm_document_name"></a> [session\_logging\_ssm\_document\_name](#input\_session\_logging\_ssm\_document\_name) | Name for `session_logging` SSM document.<br/> This is only applied if 2 conditions are met: (1) `session_logging_enabled` = true,<br/> (2) `create_run_shell_document` = true. | `string` | `"SSM-SessionManagerRunShell-Tailscale"` | no |
Expand Down
28 changes: 20 additions & 8 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ locals {
tailscale_set_extra_flags_enabled = length(var.tailscale_set_extra_flags) > 0

userdata = templatefile("${path.module}/userdata.sh.tmpl", {
authkey = tailscale_tailnet_key.default.key
authkey = coalesce(
one(tailscale_oauth_client.default[*].key),
one(tailscale_tailnet_key.default[*].key),
)
exit_node_enabled = var.exit_node_enabled
hostname = module.this.id
routes = join(",", var.advertise_routes)
Expand Down Expand Up @@ -72,14 +75,23 @@ module "tailscale_subnet_router" {
user_data = base64encode(length(var.user_data) > 0 ? var.user_data : local.userdata)
}

resource "tailscale_tailnet_key" "default" {
reusable = var.reusable
ephemeral = var.ephemeral
preauthorized = var.preauthorized
expiry = var.expiry
resource "tailscale_oauth_client" "default" {
count = var.authkey_config.tailscale_oauth_client != null ? 1 : 0

description = var.authkey_config.tailscale_oauth_client.description
scopes = var.authkey_config.tailscale_oauth_client.scopes
tags = local.tailscale_tags
}

# A device is automatically tagged when it is authenticated with this key.
tags = local.tailscale_tags
resource "tailscale_tailnet_key" "default" {
count = var.authkey_config.tailscale_tailnet_key != null ? 1 : 0

description = var.authkey_config.tailscale_tailnet_key.description
ephemeral = var.authkey_config.tailscale_tailnet_key.ephemeral
expiry = var.authkey_config.tailscale_tailnet_key.expiry
preauthorized = var.authkey_config.tailscale_tailnet_key.preauthorized
reusable = var.authkey_config.tailscale_tailnet_key.reusable
tags = local.tailscale_tags
}

module "ssm_state" {
Expand Down
70 changes: 51 additions & 19 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -202,28 +202,60 @@ variable "advertise_routes" {
}
}

variable "expiry" {
default = 7776000
type = number
description = "The expiry of the auth key in seconds."
}
variable "authkey_config" {
default = {
"tailscale_tailnet_key" = {
"ephemeral" = false,
"expiry" = 7776000,
"preauthorized" = true,
"reusable" = true,
}
}

variable "preauthorized" {
default = true
type = bool
description = "Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default."
}
description = <<-EOT
Configuration for the auth key used in `tailscale up` command.

variable "ephemeral" {
default = false
type = bool
description = "Indicates if the key is ephemeral."
}
One of `tailscale_oauth_client` or `tailscale_tailnet_key` must be set.

variable "reusable" {
default = true
type = bool
description = "Indicates if the key is reusable or single-use."
For both options, `tags` are configured by the module and are the same that are passed to `tailscale up` command via `--advertise-tags=<tags>` flag.

Minimal `scopes` required for `tailscale_oauth_client` are `["auth_keys", "devices:core", "devices:routes", "dns"]`.

For additional information, please visit:
- [tailscale up command](https://tailscale.com/docs/reference/tailscale-cli/up)
- [Terraform tailscale_oauth_client](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/oauth_client)
- [Terraform tailscale_tailnet_key](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/tailnet_key)
EOT

type = object({
tailscale_oauth_client = optional(object({
description = string
scopes = list(string)
}))
tailscale_tailnet_key = optional(object({
description = string
ephemeral = bool
expiry = number
preauthorized = bool
reusable = bool
}))
})

validation {
condition = (
var.authkey_config.tailscale_oauth_client == null && var.authkey_config.tailscale_tailnet_key != null ||
var.authkey_config.tailscale_oauth_client != null && var.authkey_config.tailscale_tailnet_key == null
)
error_message = "Exactly one of 'tailscale_oauth_client' or 'tailscale_tailnet_key' must be defined in authkey_config."
}

validation {
condition = var.authkey_config.tailscale_oauth_client == null ? true : setintersection(
var.authkey_config.tailscale_oauth_client.scopes,
["auth_keys", "devices:core", "devices:routes", "dns"],
) == toset(["auth_keys", "devices:core", "devices:routes", "dns"])
error_message = "The 'tailscale_oauth_client.scopes' must include at least: auth_keys, devices:core, devices:routes and dns."
}
}

variable "tailscaled_extra_flags" {
Expand Down
2 changes: 1 addition & 1 deletion versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ terraform {
}
tailscale = {
source = "tailscale/tailscale"
version = ">= 0.13.7"
version = ">= 0.20.0"
}
}
}