Tailscale SSHをあとから有効にする際のスクリプト

以前、Tailscale SSHについて紹介しました。最初にSSH化するならコマンドにtailscale upする際にオプションを付け、tailscale up --sshで実行するだけと簡単なのですが、Tailscaleのサービスを色々設定したあとに追加する場合は、適切にオプションを指定しないと既存の環境が壊れてしまう可能性があるのでスクリプトを組んでみました。

読み取る設定一覧

debug prefs のキーtailscale up オプション
RouteAll--accept-routes
CorpDNS--accept-dns
ShieldsUp--shields-up
ExitNodeID/IP--exit-node=...
AdvertiseRoutes--advertise-routes=... / --advertise-exit-node
Hostname--hostname=...
OperatorUser--operator=...
NetfilterMode--netfilter-mode=...
NoSNAT--snat-subnet-routes=false

スクリプトを作成

mkdir -p /opt/lxd-data/script
cd /opt/lxd-data/script
sudo nano tailscale-enable-ssh.sh
sudo chmod +x tailscale-enable-ssh.sh
#!/usr/bin/env bash
# tailscale-enable-ssh.sh
# 現在のTailscale設定を保持しつつ --ssh を追加して再起動する

set -euo pipefail

info()  { echo -e "\033[1;34m[INFO]\033[0m  $*"; }
ok()    { echo -e "\033[1;32m[OK]\033[0m    $*"; }
warn()  { echo -e "\033[1;33m[WARN]\033[0m  $*"; }
die()   { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }

# sudoでPATHが変わる対策: tailscaleのフルパスを探す
TAILSCALE=$(command -v tailscale 2>/dev/null \
  || { for p in /usr/bin/tailscale /usr/local/bin/tailscale /usr/sbin/tailscale; do
         [[ -x "$p" ]] && echo "$p" && break; done; })
[[ -n "$TAILSCALE" ]] || die "tailscale が見つかりません"

command -v jq >/dev/null 2>&1 || die "jq が必要です: sudo apt install jq"

info "現在の Tailscale 設定を取得中..."
PREFS=$(tailscale debug prefs 2>/dev/null || "$TAILSCALE" debug prefs 2>/dev/null) \
  || die "tailscale debug prefs の取得に失敗しました"

jq_bool() { echo "$PREFS" | jq -r "$1 // false"; }
jq_str()  { echo "$PREFS" | jq -r "$1 // empty"; }

OPTS=("--ssh")

[[ "$(jq_bool '.RouteAll')"               == "true" ]] && OPTS+=("--accept-routes")
[[ "$(jq_bool '.CorpDNS')"                == "true" ]] && OPTS+=("--accept-dns")
[[ "$(jq_bool '.ShieldsUp')"              == "true" ]] && OPTS+=("--shields-up")
[[ "$(jq_bool '.ExitNodeAllowLANAccess')" == "true" ]] && OPTS+=("--exit-node-allow-lan-access")

EXIT_NODE=$(jq_str '.ExitNodeID // empty')
[[ -z "$EXIT_NODE" ]] && EXIT_NODE=$(jq_str '.ExitNodeIP // empty')
[[ -n "$EXIT_NODE" ]] && OPTS+=("--exit-node=$EXIT_NODE")

if echo "$PREFS" | jq -e '.AdvertiseRoutes[]? | select(. == "0.0.0.0/0")' >/dev/null 2>&1; then
  OPTS+=("--advertise-exit-node")
fi

ROUTES=$(echo "$PREFS" | jq -r '.AdvertiseRoutes[]? // empty' 2>/dev/null \
  | grep -v '^0\.0\.0\.0/0$' | grep -v '^::/0$' | paste -sd, - || true)
[[ -n "$ROUTES" ]] && OPTS+=("--advertise-routes=$ROUTES")

HOSTNAME=$(jq_str '.Hostname')
[[ -n "$HOSTNAME" ]] && OPTS+=("--hostname=$HOSTNAME")

OPERATOR=$(jq_str '.OperatorUser')
[[ -n "$OPERATOR" ]] && OPTS+=("--operator=$OPERATOR")

case "$(jq_str '.NetfilterMode')" in
  1) OPTS+=("--netfilter-mode=on") ;;
  2) OPTS+=("--netfilter-mode=nodivert") ;;
  3) OPTS+=("--netfilter-mode=off") ;;
esac

[[ "$(jq_bool '.NoStatefulFiltering')" == "true" ]] && OPTS+=("--stateful-filtering=false")
[[ "$(jq_bool '.NoSNAT')"              == "true" ]] && OPTS+=("--snat-subnet-routes=false")

CMD="sudo $TAILSCALE up ${OPTS[*]}"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
info "実行するコマンド:"
echo ""
echo "  $CMD"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# ── ドライランオプション ────────────────────────────────
if [[ "${1:-}" == "--dry-run" ]]; then
  warn "ドライラン: 実際には実行しません"
  exit 0
fi

# ── 実行確認 ───────────────────────────────────────────
read -rp "実行しますか? [y/N] " CONFIRM
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { warn "キャンセルしました"; exit 0; }

# ── Auth Key を使うか確認 ──────────────────────────────
echo ""
echo "Tailscale Auth Key を使用しますか?"
echo "  使う場合   : 認証済みノードとして自動ログイン (tskey-auth-... または tskey-...)"
echo "  使わない場合: 現在のログイン状態のまま継続"
echo ""
read -rp "Auth Key を使用しますか? [y/N] " USE_KEY

if [[ "$USE_KEY" =~ ^[Yy]$ ]]; then
  # 入力中はエコーしない (キーを画面に表示させない)
  echo -n "Auth Key を貼り付けてください: "
  read -rs AUTH_KEY
  echo ""

  # 簡易バリデーション
  if [[ -z "$AUTH_KEY" ]]; then
    die "Auth Key が空です"
  fi
  if [[ ! "$AUTH_KEY" =~ ^tskey- ]]; then
    warn "Auth Key の形式が想定と異なります (tskey- で始まらない)"
    read -rp "このまま続けますか? [y/N] " FORCE
    [[ "$FORCE" =~ ^[Yy]$ ]] || { warn "キャンセルしました"; exit 0; }
  fi

  CMD="$CMD --authkey=$AUTH_KEY"
  info "Auth Key を使用して実行します"
else
  info "Auth Key なしで実行します"
fi

# ── 実行 ──────────────────────────────────────────────
echo ""
info "tailscale up を実行中..."
eval "$CMD"
ok "完了! SSH が有効になりました"
echo ""
"$TAILSCALE" status

まずはコマンド確認(実際には実行しない)

まず確認します。

./tailscale-enable-ssh.sh --dry-run

本番実行

問題なければ本番。なお、TailscaleのAuth Keyを使用していれば、途中で下記の確認があるのでそこで入力出来ます。tskey- で始まらない場合は警告表示し、 誤貼り付けを防ぐようにしています。

実行しますか? [y/N] y

Tailscale Auth Key を使用しますか?
使う場合 : 認証済みノードとして自動ログイン (tskey-auth-... または tskey-...)
使わない場合: 現在のログイン状態のまま継続

Auth Key を使用しますか? [y/N] y
Auth Key を貼り付けてください: ※入力は画面に表示されません

また実行中に次のような最終確認が表示されます。

実行しますか? [y/N] y
[INFO] tailscale up を実行中...
Warning: netfilter=nodivert; add iptables calls to ts-* chains manually.
You are connected over Tailscale; this action will reroute SSH traffic to Tailscale SSH and will result in your session disconnecting.
To skip this warning, use --accept-risk=lose-ssh
Continue? [y/N]

この警告は「現在Tailscale経由でSSH接続中の場合、セッションが切れるよ」という確認です。ローカルで作業している場合は切断は起きません。Tailscale SSH経由で接続中の場合は一度切断されますが、すぐ再接続できます。

./tailscale-enable-ssh.sh
Tailscale SSHで鍵管理を任せてWebからも接続
Tailscaleの便利な機能としてTaildropやTaildriveを紹介しましたが、そのほかにも便利な機能があります。特にサーバ管理などを行ったりする際に便利なSSHに関する機能です。SSHの鍵管理をTailscaleに任せるSSHで…
タイトルとURLをコピーしました