Skip to content

Commit 8c015d5

Browse files
feat!: support oauth client for the authkey (#89)
## what - Allowing the use of both OAuth client keys and API tokens when running the `tailscale up` command ## why - OAuth keys are long lived and their permissions scoped, which makes them easier to manage ## references Closes #86 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added OAuth client authentication option for Tailscale connections * Consolidated authentication configuration into a single unified interface * **Changes** * Individual authentication parameters are now configured through a single authkey_config block * Authentication now attempts OAuth client first, then falls back to tailnet key <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 69c3125 commit 8c015d5

File tree

4 files changed

+74
-34
lines changed

4 files changed

+74
-34
lines changed

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ module "tailscale" {
5252
vpc_id = module.vpc.vpc_id
5353
subnet_ids = module.subnets.private_subnet_ids
5454
advertise_routes = [module.vpc.vpc_cidr_block]
55-
56-
ephemeral = true
5755
}
5856
```
5957

@@ -182,6 +180,7 @@ The above configuration ensures that the subnet router can establish direct conn
182180
|------|------|
183181
| [aws_iam_role_policy_attachment.cw_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
184182
| [aws_iam_role_policy_attachment.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
183+
| [tailscale_oauth_client.default](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/oauth_client) | resource |
185184
| [tailscale_tailnet_key.default](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/tailnet_key) | resource |
186185
187186
## Inputs
@@ -199,16 +198,15 @@ The above configuration ensures that the subnet router can establish direct conn
199198
| <a name="input_architecture"></a> [architecture](#input\_architecture) | The architecture of the AMI (e.g., x86\_64, arm64) | `string` | `"arm64"` | no |
200199
| <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 |
201200
| <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 |
201+
| <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 |
202202
| <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 |
203203
| <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 |
204204
| <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 |
205205
| <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 |
206206
| <a name="input_desired_capacity"></a> [desired\_capacity](#input\_desired\_capacity) | Desired number of instances in the Auto Scaling Group | `number` | `1` | no |
207207
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
208208
| <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 |
209-
| <a name="input_ephemeral"></a> [ephemeral](#input\_ephemeral) | Indicates if the key is ephemeral. | `bool` | `false` | no |
210209
| <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 |
211-
| <a name="input_expiry"></a> [expiry](#input\_expiry) | The expiry of the auth key in seconds. | `number` | `7776000` | no |
212210
| <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 |
213211
| <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 |
214212
| <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 |
@@ -223,10 +221,8 @@ The above configuration ensures that the subnet router can establish direct conn
223221
| <a name="input_monitoring_enabled"></a> [monitoring\_enabled](#input\_monitoring\_enabled) | Enable detailed monitoring of instances | `bool` | `true` | no |
224222
| <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 |
225223
| <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 |
226-
| <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 |
227224
| <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 |
228225
| <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 |
229-
| <a name="input_reusable"></a> [reusable](#input\_reusable) | Indicates if the key is reusable or single-use. | `bool` | `true` | no |
230226
| <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 |
231227
| <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 |
232228
| <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 |

main.tf

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ locals {
1515
tailscale_set_extra_flags_enabled = length(var.tailscale_set_extra_flags) > 0
1616

1717
userdata = templatefile("${path.module}/userdata.sh.tmpl", {
18-
authkey = tailscale_tailnet_key.default.key
18+
authkey = coalesce(
19+
one(tailscale_oauth_client.default[*].key),
20+
one(tailscale_tailnet_key.default[*].key),
21+
)
1922
exit_node_enabled = var.exit_node_enabled
2023
hostname = module.this.id
2124
routes = join(",", var.advertise_routes)
@@ -72,14 +75,23 @@ module "tailscale_subnet_router" {
7275
user_data = base64encode(length(var.user_data) > 0 ? var.user_data : local.userdata)
7376
}
7477

75-
resource "tailscale_tailnet_key" "default" {
76-
reusable = var.reusable
77-
ephemeral = var.ephemeral
78-
preauthorized = var.preauthorized
79-
expiry = var.expiry
78+
resource "tailscale_oauth_client" "default" {
79+
count = var.authkey_config.tailscale_oauth_client != null ? 1 : 0
80+
81+
description = var.authkey_config.tailscale_oauth_client.description
82+
scopes = var.authkey_config.tailscale_oauth_client.scopes
83+
tags = local.tailscale_tags
84+
}
8085

81-
# A device is automatically tagged when it is authenticated with this key.
82-
tags = local.tailscale_tags
86+
resource "tailscale_tailnet_key" "default" {
87+
count = var.authkey_config.tailscale_tailnet_key != null ? 1 : 0
88+
89+
description = var.authkey_config.tailscale_tailnet_key.description
90+
ephemeral = var.authkey_config.tailscale_tailnet_key.ephemeral
91+
expiry = var.authkey_config.tailscale_tailnet_key.expiry
92+
preauthorized = var.authkey_config.tailscale_tailnet_key.preauthorized
93+
reusable = var.authkey_config.tailscale_tailnet_key.reusable
94+
tags = local.tailscale_tags
8395
}
8496

8597
module "ssm_state" {

variables.tf

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -202,28 +202,60 @@ variable "advertise_routes" {
202202
}
203203
}
204204

205-
variable "expiry" {
206-
default = 7776000
207-
type = number
208-
description = "The expiry of the auth key in seconds."
209-
}
205+
variable "authkey_config" {
206+
default = {
207+
"tailscale_tailnet_key" = {
208+
"ephemeral" = false,
209+
"expiry" = 7776000,
210+
"preauthorized" = true,
211+
"reusable" = true,
212+
}
213+
}
210214

211-
variable "preauthorized" {
212-
default = true
213-
type = bool
214-
description = "Determines whether or not the machines authenticated by the key will be authorized for the tailnet by default."
215-
}
215+
description = <<-EOT
216+
Configuration for the auth key used in `tailscale up` command.
216217
217-
variable "ephemeral" {
218-
default = false
219-
type = bool
220-
description = "Indicates if the key is ephemeral."
221-
}
218+
One of `tailscale_oauth_client` or `tailscale_tailnet_key` must be set.
222219
223-
variable "reusable" {
224-
default = true
225-
type = bool
226-
description = "Indicates if the key is reusable or single-use."
220+
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.
221+
222+
Minimal `scopes` required for `tailscale_oauth_client` are `["auth_keys", "devices:core", "devices:routes", "dns"]`.
223+
224+
For additional information, please visit:
225+
- [tailscale up command](https://tailscale.com/docs/reference/tailscale-cli/up)
226+
- [Terraform tailscale_oauth_client](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/oauth_client)
227+
- [Terraform tailscale_tailnet_key](https://registry.terraform.io/providers/tailscale/tailscale/latest/docs/resources/tailnet_key)
228+
EOT
229+
230+
type = object({
231+
tailscale_oauth_client = optional(object({
232+
description = string
233+
scopes = list(string)
234+
}))
235+
tailscale_tailnet_key = optional(object({
236+
description = string
237+
ephemeral = bool
238+
expiry = number
239+
preauthorized = bool
240+
reusable = bool
241+
}))
242+
})
243+
244+
validation {
245+
condition = (
246+
var.authkey_config.tailscale_oauth_client == null && var.authkey_config.tailscale_tailnet_key != null ||
247+
var.authkey_config.tailscale_oauth_client != null && var.authkey_config.tailscale_tailnet_key == null
248+
)
249+
error_message = "Exactly one of 'tailscale_oauth_client' or 'tailscale_tailnet_key' must be defined in authkey_config."
250+
}
251+
252+
validation {
253+
condition = var.authkey_config.tailscale_oauth_client == null ? true : setintersection(
254+
var.authkey_config.tailscale_oauth_client.scopes,
255+
["auth_keys", "devices:core", "devices:routes", "dns"],
256+
) == toset(["auth_keys", "devices:core", "devices:routes", "dns"])
257+
error_message = "The 'tailscale_oauth_client.scopes' must include at least: auth_keys, devices:core, devices:routes and dns."
258+
}
227259
}
228260

229261
variable "tailscaled_extra_flags" {

versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ terraform {
88
}
99
tailscale = {
1010
source = "tailscale/tailscale"
11-
version = ">= 0.13.7"
11+
version = ">= 0.20.0"
1212
}
1313
}
1414
}

0 commit comments

Comments
 (0)