TerraformでTailscaleの構築をしてみる
はじめに
QualiArts - Qiita Advent Calendar 2025 - Qiita、22日目担当の鈴木光です。
今回、いわゆるマネージドWireGuardであるTailscaleを構築する機会があったため、備忘録として残しておきます。
TailscaleはVPNを簡単に構築・管理できるサービスです。この記事ではTerraformを使用してTailscaleのアクセス制御ポリシーを管理する方法と、ロールベースのアクセス制御を実装した事例を紹介します。
Tailscale Provider
Tailscaleでは公式より Tailscale Provider(tailscale/tailscale)が提供されているので、こちらを使ってリソース管理を行います。
出典: Terraform Registry - tailscale/tailscale
実装の概要
Tailscale Grantsポリシー
この実装では、TailscaleのGrantsポリシーを使用しています。Grantsは、従来のACLから移行された新しいアクセス制御方式です。
出典: Tailscale公式ドキュメント - Migrate from ACLs to grants
ロールベースアクセス制御の仕組み
以下の4つの要素で構成されています
- ロール定義: 各ロールがアクセス可能なプロジェクトタグを定義
- ユーザーアサイン: 各ユーザーに1つのロールを割り当て
- グループ生成: ロールごとにカスタムグループを自動生成
- タグオーナー設定: プロジェクトタグを管理する権限を持つグループを定義
実装詳細
main.tfの構成
main.tfファイルは以下の構造になっています。
1. Locals定義
locals { # ロール定義: プロジェクトタグを持つロール roles = { "admin" = ["projectA", "projectB"] "projectA-developer" = ["projectA"] "projectB-developer" = ["projectB"] } # ユーザーとロールのアサイン(1ユーザーに1ロール) user_role_assignments = { "user1@example.com" = "admin" "user2@example.com" = "admin" "user3@example.com" = "projectA-developer" "user4@example.com" = "projectB-developer" } }
2. 動的なグループ生成
Terraformのfor式を使用して、ロールごとにユーザーをグループ化します。
role_users = { for role_name, _ in local.roles : role_name => [ for email, role in local.user_role_assignments : email if role == role_name ] }
3. タグオーナーのマッピング
Tailscaleでは、タグオーナー(tagOwners)がタグをマシンに割り当てる権限を持ちます。この実装では、すべてのプロジェクトタグをgroup:adminが所有するように設定しています。
# タグオーナーのマッピングを作成 # プロジェクトタグはadminグループが所有 tag_owners = { for project_tag in local.all_project_tags : "tag:${project_tag}" => ["group:admin"] }
この設定により、group:adminのメンバーのみが、プロジェクトタグ(例: tag:projectA、tag:projectB)をマシンに割り当てることができます。
タグオーナーの概念は、Tailscaleのアクセス制御において重要な役割を果たします。タグをマシンに割り当てる権限を制限することで、意図しないタグの割り当てを防ぎ、アクセス制御ポリシーの整合性を保つことができます。
4. タグとロールのマッピング(tag_to_roles)
各プロジェクトタグに対して、アクセス可能なロール(admin以外)をマッピングするtag_to_rolesを定義しています。
# タグごとにアクセス可能なロール(admin以外)をマッピング tag_to_roles = { for tag in local.all_project_tags : tag => [ for role_name, tags in local.roles : role_name if contains(tags, tag) && role_name != "admin" ] }
各プロジェクトタグに対して、そのタグにアクセス権限を持つロール(adminを除く)を自動的に特定できます。例えば、projectAタグにはprojectA-developerロールがマッピングされます。
tag_to_rolesは、後述するGrantsルールの生成時に使用され、各タグに対して適切なロールのユーザーがアクセスできるようにします。adminロールは常に全タグにアクセスできるため、このマッピングからは除外されています。
5. Tailscale ACLリソース
tailscale_aclリソースを使用して、Grantsポリシーを定義します。
resource "tailscale_acl" "this" { acl = jsonencode({ groups = { for role_name, users in local.role_users : "group:${role_name}" => users } tagOwners = local.tag_owners grants = concat( # ExitNodeへのアクセスルール [...], # プロジェクトタグへのアクセスルール [...] ) }) }
アクセス制御ルール
実装では、以下の2種類のアクセスルールを定義しています。
1. ExitNodeアクセスルール
プロジェクトごとに、ExitNode経由でのインターネットアクセスとプライベートIPレンジへのアクセスを許可します。
[ for tag in local.all_project_tags : { src = concat(["group:admin"], [for role in local.tag_to_roles[tag] : "group:${role}"]) dst = concat(["autogroup:internet"], local.private_ip_ranges) via = ["tag:${tag}"] ip = ["*"] } ]
2. プロジェクトタグへのアクセスルール
各プロジェクトタグに対して、該当するロールのユーザーがアクセスできるようにします。
[ for tag in local.all_project_tags : { src = concat(["group:admin"], [for role in local.tag_to_roles[tag] : "group:${role}"]) dst = ["tag:${tag}"] ip = ["*"] } ]
SSHルール
管理者グループ(group:admin)のメンバーが、自身のマシンに対してSSHアクセスできるように設定されています。
ssh = [ { action = "accept" src = ["group:admin"] dst = ["autogroup:self"] users = ["autogroup:nonroot", "root"] } ]
自動承認設定
プライベートIPレンジのルートとExitNodeの自動承認を設定しています。
autoApprovers = { routes = { for ip_range in local.private_ip_ranges : ip_range => [for tag in local.all_project_tags : "tag:${tag}"] } exitNode = [for tag in local.all_project_tags : "tag:${tag}"] }
まとめ
Terraformを使用してTailscaleのアクセス制御を管理することで、IaCの原則に従った運用が可能になります。 また、ロールベースアクセス制御を実装することでユーザー管理が簡潔になり、スケーラブルなアクセス制御が実現できるようになりました。
この実装にはTailscale Premiumプラン以上が必要ですが、カスタムグループ機能を活用することで柔軟で保守性の高いアクセス制御システムを構築できるため、皆様の参考になれば幸いです。