ホームラボを管理しよう。OpenSSHでLinux母艦からWindows 11をAnsible管理する

ホームラボを管理しよう。OpenSSHでLinux母艦からWindows 11をAnsible管理する

AIによるこの記事の要約
ホームラボ環境のLinux母艦からWindows 11をAnsibleで一元管理する際、従来のWinRMではなく標準機能の OpenSSH Server と PowerShell を使って「SSH+鍵認証」で接続する構築メモです。管理者権限(Administrators)特有の鍵参照ルール、icaclsによる厳格なアクセス権設定、PSReadLineとConPTYのフリーズ回避策、OneDrive環境下での $PROFILE パス乖離など、構築時にハマったポイントと対処法を実践的なPlaybook(Fastfetch導入例)とともにまとめています。

はじめに

ホームラボ環境で複数台のLinuxサーバーをAnsibleでコード管理(IaC)していると、どうしても1台だけ浮いてしまいがちなのがWindowsマシンです。
かつてWindowsをAnsibleで管理する際の主流は WinRM1 でした。ただ、WinRMは認証周りの設定やHTTPS化(証明書管理)がなかなか面倒で、Linuxノードと同じ感覚でサクッと管理ノードに追加できないのが地味なストレスでした。
現在では、Windows 10(1809以降)やWindows 11に標準の OpenSSH Server が搭載されているため、WinRMを一切使わずに、Linuxとまったく同じ「SSH + 鍵認証」の仕組みでAnsible管理ができるようになっています。
この記事では、Windows 11をOpenSSH経由でAnsible管理下におさめるセットアップ手順と、実際に試す中でハマった「管理者権限の鍵ルール」「画面フリーズ」「$PROFILEのパス乖離」といった罠の回避策をまとめてみます。

Ansibleのバージョン
WindowsをSSH接続先として正式にサポートしたのは ansible-core 2.18以降 です。それ以前でも動作する構成はありましたが、本記事では ansible-core 2.18以降を前提にします。

全体構成と検証環境

全体構成と検証環境

全体構成と検証環境

  • 制御ノード(Ansible母艦): RHEL系Linux(または macOS)
  • ターゲットノード: Windows 11 Pro(IP: 192.168.X.X など)
  • ターゲット側ユーザー: WINDOWS_USER(管理者権限保持)
  • 接続方式: OpenSSH(Port 22)

1. Windows 10 / 11 側の OpenSSH Server セットアップ

昔はWindowsでSSHサーバーを動かすためにサードパーティ製ツールを入れる必要がありましたが、今はOS標準のオプション機能(Feature on Demand)として OpenSSH Server が提供されています。
まずはWindows 11側で 管理者権限のPowerShell を開き、OpenSSH Server の有効化と、SSH接続時のデフォルトシェルをコマンドプロンプトから PowerShell へ切り替える設定を行います。

1-1. OpenSSH Server のインストールと起動

# OpenSSH Server 機能のインストール
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

# サービスの起動
Start-Service sshd

# OS起動時に自動実行されるよう設定
Set-Service -Name sshd -StartupType 'Automatic'

1-2. デフォルトシェルを PowerShell に変更

初期状態のままSSH接続すると コマンドプロンプト が起動してしまいます。Ansibleからの制御をスムーズに行うため、レジストリを変更してデフォルトシェルを PowerShell に設定します。

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force

2. ハマりポイント①:Administratorsグループの鍵参照ルール

ここが最初の大きなハマりどころでした。WindowsのOpenSSHでは、「管理者権限(Administratorsグループ)を持つユーザーの場合、通常の ~/.ssh/authorized_keys を無視する」 というデフォルト仕様が存在します。
そのため、普通に ~/.ssh/authorized_keys に鍵を置いても無視され、C:\ProgramData\ssh\administrators_authorized_keys を見に行こうとしてログインに失敗します。Linuxと同じ挙動にするため設定を変更します。

2-1. sshd_config の修正

管理者権限で C:\ProgramData\ssh\sshd_config を開きます。

メモ帳(Notepad)のトラップ
Windows標準のメモ帳で「名前を付けて保存」すると、指定方法によっては末尾に .txt が付与され、設定ファイルが読み込まれなくなることがあります。既存の sshd_config を直接開いて上書きするか、VS Codeが入っているならターミナルから code C:\ProgramData\ssh\sshd_config で開くのが確実です。保存後は Get-ChildItem C:\ProgramData\ssh\sshd_config* でファイル名を確認します。

ファイルの最下部にある以下の2行を探し、先頭に # を付けてコメントアウトします。

# Match Group administrators
#       AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys

2-2. SSH サービスの再起動

設定反映のため、SSHサービスを再起動しておきます。

Restart-Service sshd

3. ハマりポイント②:ファイアウォールとネットワークプロファイル

SSH接続時に Operation timed out になる場合は、sshd の待受状態、Windowsファイアウォール、IPアドレスや経路を順に確認します。タイムアウトだけで原因をファイアウォールに限定することはできません。

3-1. ファイアウォールルールの有効化

OpenSSH Serverをインストールするとルール自体は自動生成されますが、無効のままになっていることがあります。

# OpenSSH インバウンドルールを有効化
Enable-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'

# ルールの確認(Enabled が True になっていればOK)
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' | Select-Object Name, Enabled, Direction, Action

3-2. ファイアウォールルールの適用プロファイルを確認

Public だから必ずSSHが遮断されるわけではなく、ルールの Profile 設定次第です。現在のネットワークカテゴリと、OpenSSHルールが適用されるプロファイルを確認します。

# 現在のネットワークカテゴリを確認
Get-NetConnectionProfile

# OpenSSHルールが適用されるプロファイルを確認
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' |
    Select-Object Name, Enabled, Profile, Direction, Action

信頼できる自宅LANをWindows上でもプライベートネットワークとして扱いたい場合に限り、対象を明示して変更します(公共のネットワークでは変更しません)。

Set-NetConnectionProfile -InterfaceIndex <確認したInterfaceIndex> -NetworkCategory Private

4. SSH公開鍵の配置と厳格なアクセス権限(icacls)の設定

WindowsのOpenSSHはパーミッションチェックが厳格で、authorized_keys ファイルのアクセス権限(ACL)に他のユーザーの読み取り権限が残っていると、安全のために鍵を無視してパスワード認証へフォールバックしてしまいます。

4-1. 公開鍵の配置

Linux/Mac母艦側で作成した公開鍵(id_ecdsa.pub 等)の内容を、Windowsターゲットのユーザーディレクトリ配下に配置します。

PowerShellで実行:

# .ssh ディレクトリの作成
New-Item -ItemType Directory -Path "C:\Users\WINDOWS_USER\.ssh" -Force

# 公開鍵ファイルの作成
# Windows PowerShell 5.1 の -Encoding utf8 はBOMを付けるため、ASCII文字だけで
# 構成されるOpenSSH公開鍵は ascii を指定する
Set-Content -Path "C:\Users\WINDOWS_USER\.ssh\authorized_keys" -Value "ecdsa-sha2-nistp256 AAAAE3NzaC1..." -Encoding ascii

4-2. アクセス権限(ACL)の剥奪と再付与

icacls コマンドを使って、アクセス権を「対象ユーザー本人」と「SYSTEM」のみに絞り込みます。

$path = "C:\Users\WINDOWS_USER\.ssh\authorized_keys"

# 親フォルダからの権限継承を解除し、既存アクセス権をクリア
icacls $path /inheritance:r

# 本人と SYSTEM にのみフルコントロール権限を付与
icacls $path /grant:r "WINDOWS_USER:F"
icacls $path /grant:r "SYSTEM:F"

権限を設定したら、念のため Restart-Service sshd でSSHサービスを再起動しておきます。

5. ハマりポイント③:SSH接続直後に画面が固まるフリーズ対策

一部の環境では、OpenSSH経由でPowerShellを対話起動した際、入力補完モジュールの PSReadLine と端末処理の組み合わせにより、キー入力を受け付けないように見える現象が起きることがあります。これはすべての環境で必要な設定ではありません。まずWindows Update、OpenSSH、PSReadLineを更新し、それでも再現する場合の回避策として、SSHの対話セッションだけでPSReadLineを解除します。

対策:$PROFILE に判定処理を追加

Windows側でプロファイルを開き、以下のコードを追加します。

# プロファイルが存在しない場合は作成
if (!(Test-Path $PROFILE)) { New-Item -Type File -Path $PROFILE -Force }
code $PROFILE

追記する内容:

# TTYが割り当てられたSSH対話セッションだけでPSReadLineを無効化
# SSH_CONNECTIONだけで判定すると、Ansibleの非対話実行にも影響する
if ($env:SSH_TTY) {
    Remove-Module PSReadLine -ErrorAction SilentlyContinue
}

これでローカル操作とAnsibleの非対話実行には影響を与えず、SSHの対話操作時だけモジュールを解除できます。

6. 制御ノード(Linux/Mac母艦)からの Ansible 接続設定

ここまで準備できたら、Ansible母艦から接続確認を行っていきます。

6-1. ansible.windows コレクションの導入

Windowsモジュール群を利用するため、ansible.windows コレクションをインストールします。あわせて ansible --version で ansible-core 2.18以降であることを確認してください。

collections/requirements.yml を作成:


collections:
  - name: ansible.windows

インストール実行:

ansible-galaxy collection install -r collections/requirements.yml

6-2. インベントリファイル(hosts.yml)の作成

SSH経由でPowerShellを実行させるため、ansible_connection: sshansible_shell_type: powershell を指定します。

all:
  children:
    windows:
      hosts:
        windows11dev:
          ansible_host: 192.168.X.X  # ターゲットのWindows IPアドレス
          # 実機側で確認したプロファイルの絶対パスを定義
          powershell_profile_path: 'C:\Users\WINDOWS_USER\OneDrive\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1'
      vars:
        ansible_user: WINDOWS_USER
        ansible_connection: ssh
        ansible_ssh_private_key_file: /home/ansible/.ssh/id_ecdsa
        ansible_shell_type: powershell
        ansible_become: false
ポイント
プレイブック全体で become: true(特権昇格)を有効にしている環境の場合、Windowsグループでは ansible_become: false を明示しておかないと認証エラーの原因になります。

6-3. 疎通テスト(win_ping)

win_ping モジュールで疎通を確認してみます。

ansible windows11dev -m ansible.windows.win_ping

成功すると以下のように返ってきます。

windows11dev | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

7. 実践編:WingetでのFastfetch導入と $PROFILE の冪等管理

単なる疎通確認だけで終わるのも味気ないので、実際のPlaybook例として「Winget経由のFastfetch導入」と「SSHログイン時にFastfetchを自動実行するプロファイル設定」を作成してみます。

7-1. ハマりポイント④:対話/非対話での $PROFILE パス乖離問題

PowerShellの $PROFILE は単一の固定値ではなく、ホスト(ConsoleHostなど)・実行ユーザー・対象範囲によって複数あります。また、Ansibleモジュールの非対話実行ではプロファイルを読み込むこと自体を前提にすべきではありません。
特にWindows 11で OneDriveのドキュメントリダイレクト が有効になっていると、実際のプロファイルパスは以下のようにOneDrive配下を指すようになります。

  • 実際の参照先例: C:\Users\WINDOWS_USER\OneDrive\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

そのため、Playbook内で動的に $PROFILE を参照させるのではなく、インベントリ変数(powershell_profile_path)として確認済みの絶対パスを渡すのが一番確実でした。

7-2. ハマりポイント⑤:末尾改行がないファイルへの追記事故

既存の $PROFILE の最終行に改行コードが入っていない状態で単純に文字列を追加すると、前の行の末尾と新しいコマンドが連結してスクリプトが壊れてしまう事故が起きます。

# 事故例:最終行の直後に「fastfetch」が繋がってしまう
Set-Alias csc C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exefastfetch

7-3. 完成した Playbook(install_fastfetch_windows.yml)

改行の自動補正と、マーカーコメントによる冪等性(何度実行しても安全に同じ状態へ収束すること)を考慮したPlaybookです。winget とプロファイル更新の両方で、実際に変更した場合だけAnsibleへ変更ありと通知します。


- name: Install Fastfetch and Configure PowerShell Profile
  hosts: windows
  gather_facts: false

  tasks:
    - name: Winget経由で Fastfetch をインストール
      ansible.windows.win_powershell:
        script: |
          if (Get-Command fastfetch -ErrorAction SilentlyContinue) {
              $Ansible.Changed = $false
              return
          }

          winget install --id Fastfetch-cli.Fastfetch --exact --source winget `
              --accept-source-agreements --accept-package-agreements `
              --disable-interactivity
          if ($LASTEXITCODE -ne 0) {
              throw "winget install failed (exit code: $LASTEXITCODE)"
          }
          $Ansible.Changed = $true

    - name: PowerShellプロファイルへ Fastfetch を冪等に追記
      ansible.windows.win_powershell:
        parameters:
          ProfilePath: "{{ powershell_profile_path }}"
        script: |
          param([string]$ProfilePath)

          $parent = Split-Path -Parent $ProfilePath
          if (-not (Test-Path -LiteralPath $parent)) {
              New-Item -ItemType Directory -Path $parent -Force | Out-Null
          }

          $content = if (Test-Path -LiteralPath $ProfilePath) {
              Get-Content -Raw -LiteralPath $ProfilePath
          } else {
              ""
          }

          if ($content -match '(?m)^# BEGIN ANSIBLE MANAGED FASTFETCH\s*$') {
              $Ansible.Changed = $false
              return
          }

          if ($content.Length -gt 0 -and -not $content.EndsWith("`n")) {
              $content += "`r`n"
          }

          # AnsibleもSSHを使うため、TTYのある対話ログイン時だけ実行する
          $content += @'
          # BEGIN ANSIBLE MANAGED FASTFETCH
          if ($env:SSH_TTY) {
              fastfetch
          }
          # END ANSIBLE MANAGED FASTFETCH
          '@
          $content += "`r`n"

          Set-Content -LiteralPath $ProfilePath -Value $content -Encoding utf8
          $Ansible.Changed = $true

7-4. 実行と動作確認

作成したPlaybookを実行します。

ansible-playbook install_fastfetch_windows.yml

実行完了後、Ansible母艦から手動でSSHログインしてみます。

ssh -i /home/ansible/.ssh/id_ecdsa WINDOWS_USER@192.168.X.X

ログイン直後にFastfetchが自動起動し、Windows 11のシステム情報とアスキーアートが表示されれば完了です。

Windows 11にSSHした画面

Windows 11にSSHした画面

トラブルシューティングまとめ

症状主な原因対策
Operation timed outsshd停止、22番ポート未待受、ファイアウォール、IP/経路の問題Get-Service sshdGet-NetTCPConnection -LocalPort 22、ファイアウォールルールを順に確認
パスワードを聞かれるAdministrators グループの個別ルール / ACL(権限)が緩いsshd_config のコメントアウト & icacls で権限を本人のみに絞る
接続直後に画面が固まるPSReadLine と OpenSSH(ConPTY)の組み合わせ$PROFILE$env:SSH_TTY がある場合に Remove-Module PSReadLine
設定追記後にスクリプトエラー対象ファイルの最終行に改行がない追記前にファイルが改行で終わっているか判定して補正

おわりに

Windows 11をAnsibleで管理する際、OpenSSHを使うことで「WinRM特有の証明書トラブル」や「独自ポートの設定」から解放され、Linuxサーバーと同じ「SSH+鍵認証」の思考モデルで扱えるようになるのが一番のメリットだと感じました。
アクセス権限(ACL)の設定や $PROFILE のパスなど、Windowsならではの仕様でいくつか罠はありますが、一度設定してPlaybook化してしまえばホームラボの運用効率がぐっと上がります。Windowsマシンの管理に悩んでいる方は、ぜひ試してみてください。


  1. WinRM(Windows Remote Management)は、Windowsをリモート管理するためのWS-Managementプロトコル実装です。 ↩︎

@urchin_hat
Written by
@urchin_hat

とある領域のSaaSサービスでSREとして活動中。現在はインフラの正常化とAIを活用した運用効率化(AIOps)に注力しています。

Back to Memo