こんにちは!
インフラもコードもハードも大好き、零壱(ゼロイチ)テクトです。

最近異様にハマっているOpenWrt弄りですが。
VPN接続するため、OpenWrtに限らずPCやスマホなどに
「Tailscale」という、超絶簡単なのに超絶安心できるVPNアプリを
手持ちの端末にことごとくインストールしています。
インストールした台数が増えてきた為、NW情報を管理しようと思ったのですが
WEBページを見ながらポチポチとコピペで管理するのは面倒です。
調べたら「Tailscale API」があると分かったので
PowerShellでAPIにアクセスする勉強として
Tailscale APIで機器の情報を取得するコードを書きました。
※2025/06/09追記
改良案に出していた一定期間過ぎた端末の削除について
削除するコードも書いたので追記しました。
1.概要/仕様
・事前にTailscaleのサイトにログインして
「APIキーの発行」、「Tailnetのネットワーク名」をメモする事
・情報はCSV加工を前提に1つずつ取得する事
・「Exit Node」/「Subnet Router」も取得する事
・上記に伴いAPI URLで「fields=all」を付ける事
・Onlineステータスを取得できないので5分以内の接続をオンラインとする
2.事前準備
・Tailscaleを導入後の作業として扱うので、すでにアカウントはあるものとします。
■APIキー発行/取得
・Tailscaleの公式サイトにログインします。
●Tailscale公式:https://tailscale.com/
・ログイン後サイトのメニューの「Settings」をクリック
・「Settings」画面の左下「Keys」をクリック
・「Keys」画面の右下「API access tokens」の項目
・「Generate access token」ボタンをクリック
・API tokenのダイアログ表示される
・「Description」は適当なキーワードを入力
・「Expiration」でAPIの期限を入力
・「Generate access token」ボタンをクリック
・APIキーが表示されるので、無くさないようメモ
■ Tailnetネットワーク名取得
・TOPページのメニューから「DNS」をクリック
・「DNS」画面の「Tailnet name」にある、自身のネックワーク名をメモ
3.コード/端末情報一覧
# ***** 設定値:APIキー/Tailnet名 *****
$tailscaleApiKey = "tskey-api-xxxxxxxxxxxxxxx" # Tailscale APIキー
$tailnetName = "xxxxxxxxxxxxxxxxx.net" # Tailnet名
# ***** APIエンドポイント *****
$apiUrl = "https://api.tailscale.com/api/v2/tailnet/$tailnetName/devices?fields=all"
# ***** APIリクエスト用ヘッダーの定義 *****
$headers = @{
"Authorization" = "Bearer $tailscaleApiKey"
"Accept" = "application/json"
}
try {
Write-Host "Tailscale APIからデバイス情報を取得中..." -ForegroundColor Cyan
# APIへリクエスト送信/デバイス情報取得
$response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ContentType "application/json"
$devices = $response.devices
# デバイス数が1以上で処理
if ($devices.Count -gt 0) {
# 各デバイスをループ処理/情報を整形して表示
$devices | ForEach-Object {
# 変数にデバイスオブジェクトを代入
$device = $_
# ******** 各デバイスのステータス表示 ********
Write-Host "----------------------------------"
#現在時刻から最終アクセス時刻の差分計算
$timeSinceLastSeen = (Get-Date) - [DateTime]$device.lastseen
# オンライン閾値を設定:5分以下=Online/5分以上=Offline
# ※TailscaleAPIでオンライン判定が無いため5分以内をオンラインとみなす
if ($timeSinceLastSeen.TotalMinutes -lt 5) {
Write-Host "Active: ● Online (Last: $($timeSinceLastSeen.TotalSeconds -as [int])Sec ago)" -ForegroundColor Green
} else {
Write-Host "Active: ✖ Offline " -ForegroundColor Red
}
# 各種取得デバイス情報
Write-Host "LAST SEEN (UTC): $($device.lastseen) +9:00(JST)"
Write-Host "hostname: $($device.hostname)"
Write-Host "User: $($device.user)"
Write-Host "Name: $($device.name)"
Write-Host "TailscaleIP: $($device.addresses -join ', ')"
Write-Host "OS: $($device.os)"
Write-Host "clientVersion: $($device.clientVersion)"
# Exit Node 有無判定
if ($device.enabledRoutes) {
Write-Host "[*] Exit Node" -ForegroundColor Yellow
Write-Host "EnabledRoutes: $($device.enabledRoutes -join ', ')"
}
# Subnet Router 有無判定
if ($device.advertisedRoutes) {
Write-Host "[*] Subnet Router" -ForegroundColor Yellow
Write-Host "Advertised Subnets: $($device.advertisedRoutes -join ', ')"
}
}
Write-Host "----------------------------------"
} else {
# 取得デバイス数がゼロの場合
Write-Host "------ デバイス情報なし" -ForegroundColor Yellow
}
}
catch {
# エラー処理
Write-Host "API呼び出し中/エラー発生" -ForegroundColor Red
Write-Host "エラーメッセージ: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "ステータスコード: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red
if ($_.Exception.Response) {
Write-Host "詳細: $($_.Exception.Response.Content)" -ForegroundColor Red
}
}
Write-Host "スクリプト終了------処理完了日時[$(Get-Date)]" -ForegroundColor Cyan
3.結果/端末情報一覧
上記のように登録したTailnetから各デバイスの情報一覧を取得できました。
4.コード/デバイス削除 (2025/06/09追記)
改良案で挙げていた「一定期間使ってないデバイスがあったら削除
」について
実際にテスト接続して邪魔なデバイスを削除するためにコードを書いたので
追記掲載いたします。
1ヶ月以上Tailnetに接続していないデバイスを抽出し削除するか表示します。
# ***** 設定値:APIキー/Tailnet名 *****
$tailscaleApiKey = "tskey-api-xxxxxxxxxxxxxxx" # Tailscale APIキー
$tailnetName = "xxxxxxxxxxxxxxxxx.net" # Tailnet名
# ***** APIエンドポイント *****
$apiUrl = "https://api.tailscale.com/api/v2/tailnet/$tailnetName/devices?fields=all"
# ***** APIリクエスト用ヘッダーの定義 *****
$headers = @{
"Authorization" = "Bearer $tailscaleApiKey"
"Accept" = "application/json"
}
try {
Write-Host "Tailscale APIからデバイス情報を取得中..." -ForegroundColor Cyan
# APIへリクエスト送信/デバイス情報取得
$response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ContentType "application/json"
$devices = $response.devices
# デバイス数が1以上で処理
if ($devices.Count -gt 0) {
# 各デバイスをループ処理
# 過去1ヶ月以上アクセスがないデバイスのみフィルタ
$devices | Where-Object { [DateTime]$_.LastSeen -lt (Get-Date).AddMonths(-1) } | ForEach-Object {
#$devices | Where-Object { [DateTime]$_.LastSeen -lt (Get-Date).AddDays(-7) } | ForEach-Object { #1ヶ月未満用
# 情報を整形して表示
# 変数にデバイスオブジェクトを代入
$device = $_
# ******** 各デバイスのステータス表示 ********
Write-Host "----------------------------------"
#現在時刻から最終アクセス時刻の差分計算(nullは無い前提)
$timeSinceLastSeen = (Get-Date) - [DateTime]$device.LastSeen
# オンライン閾値を設定:5分以下=Online/5分以上=Offline
# ※TailscaleAPIでオンライン判定が無いため5分以内をオンラインとみなす
if ($timeSinceLastSeen.TotalMinutes -lt 5) {
Write-Host "Active: ● Online (Last: $($timeSinceLastSeen.TotalSeconds -as [int])Sec ago)" -ForegroundColor Green
} else {
Write-Host "Active: ✖ Offline " -ForegroundColor Red
}
# 各種取得デバイス情報
Write-Host "LAST SEEN (UTC): $($device.LastSeen) +9:00(JST)"
Write-Host "hostname: $($device.hostname)"
Write-Host "User: $($device.user)"
Write-Host "Name: $($device.name)"
Write-Host "TailscaleIP: $($device.addresses -join ', ')"
Write-Host "OS: $($device.os)"
Write-Host "clientVersion: $($device.clientVersion)"
# Exit Node 有無判定
if ($device.enabledRoutes) {
Write-Host "[*] Exit Node" -ForegroundColor Yellow
Write-Host "enabledRoutes: $($device.enabledRoutes -join ', ')"
}
# Subnet Router 有無判定
if ($device.advertisedRoutes) {
Write-Host "[*] Subnet Router" -ForegroundColor Yellow
Write-Host "Advertised Subnets: $($device.advertisedRoutes -join ', ')"
}
# tag付与用/デバイスID確認
Write-Host "[#] IDs==========" -ForegroundColor Cyan
Write-Host "ID: $($device.id)"
Write-Host "NodeID: $($device.nodeid)"
Write-Host "-----------------"
# デバイス削除メッセージ/Y&N
Write-Host "このクライアント[ $($device.hostname) ]を Tailscale から削除しますか? [y]=Delete , [n]=Cancel" -ForegroundColor Yellow
$deleteYn = Read-Host
# キー判定/( nキーと明示しているが y以外は全部キャンセル)
if ($deleteYn.ToString().ToUpper() -eq 'y') {
Write-Host "削除を実行します..." -ForegroundColor Red
$deleteUrl = "https://api.tailscale.com/api/v2/device/$($device.nodeId)"
try {
# デバイス削除/APIアクセス
Invoke-RestMethod -Uri $deleteUrl -Method Delete -Headers $headers -ContentType "application/json" -ErrorAction Stop # エラー捕捉用Stop
Write-Host "クライアント '$($device.hostname)' (NodeID: $($device.nodeId)) を正常に削除しました。" -ForegroundColor Red
}
catch {
Write-Host "クライアント '$($device.hostname)' の削除中にエラーが発生しました。" -ForegroundColor Red
Write-Host "エラーメッセージ: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "クライアント '$($device.hostname)' の削除をキャンセルしました。" -ForegroundColor Gray
}
}
Write-Host "----------------------------------"
} else {
# 取得デバイス数がゼロの場合
Write-Host "------ デバイス情報なし" -ForegroundColor Yellow
}
}
catch {
# エラー処理
Write-Host "API呼び出し中にエラーが発生しました。" -ForegroundColor Red
Write-Host "エラーメッセージ: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "ステータスコード: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red
}
Write-Host "スクリプト終了------処理完了日時[$(Get-Date)]" -ForegroundColor Cyan
5.結果/デバイス削除
・WEB上でも無事削除されているのを確認できました。
6.備考
・✅ 改良案としては、これを弄るよりこのコードを定期的に実行して
ステータスを監視して、それに応じて設定を変更したり
一定期間で残ったデバイスを自動で削除するなり
監視操作として発展させた方が良いと思いました。
→一定期間接続していないデバイスをフィルタして
削除できるコードを追記しました。
・PowerShellでAPI接続して操作する方法は理解しました。
・全角文字を使ってますが、オンラインステータスを色分けしました。
・オンラインステータスは、本来WireGuardのセッションを考えると
ほぼ数秒以内にアクセスできていればオンラインですが
切れても接続を継続する仕組みを考えると、まあ5分ぐらいが妥当と思いました。
・情報は一つずつ取得できたので、必要に応じてCSVに変換できます。
APIドキュメントにある情報は一通り取得可能です。
・WEB画面で出来る事は大抵APIで可能です。
情報取得さえできてしまえば、NodeIDを利用して
変更、削除、追加情報も可能です。
5.参考/参照
■ Tailscale:API公式ドキュメント
https://tailscale.com/api#description/overview
■ Github/tailscale-client-go-v2
https://github.com/tailscale/tailscale-client-go-v2/blob/main/README.md
※GO言語によるTailscale APIの操作を網羅したコード集。感謝。
それでは
どこかの誰かの何かの足しになれば幸いです。
01000010 01011001 01000101