Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,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 +200,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 +223,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 = module.this.id
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 = module.this.id
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
68 changes: 49 additions & 19 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -202,28 +202,58 @@ 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, 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.

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({
scopes = list(string)
}))
tailscale_tailnet_key = optional(object({
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