🔑

EC2 Windows ServerにSSH接続する構成をPackerとTerraformに落とし込む

2022/08/21に公開

要約

主に以下の事項について、どのようにやったかという点について記載しています。

  • Packerを使用して、AWS EC2 Windows Server 2019, 2022を構築、WinRMではなく、OpenSSHサーバ(Win OpenSSH)に接続にいく
  • Userdata(powershell)内でOpenSSHの設定(鍵の登録、パスワード認証切るとか)、ユーザー作る、その他諸々する
  • Terraformで確認用のインスタンスを立てる

GitHub repo

https://github.com/oniku-2929/packer_aws_windows_ssh
フォルダ構成についてはREADME.mdにも記載していますが、主に以下の構成となります。

  • packer ディレクトリ - Windows ServerのAMI作る為の構成
  • terraform ディレクトリ - 確認用のEC2インスタンス、及びアタッチするセキュリティグループやキーペア関連の構成
  • Makefile, .env - コマンドショートカット(make ami, make ec2 とか)用、使用する場合.envにVPC_IDとSUBNET_IDを書いておく前提

動機

検証用のEC2 Windows Server動かしたいから, PackerでAMI作れるようにして、Terraformで構成を書いておこうと思ったのがきっかけです。

  • PackerでのAMI作成時に、WinRM接続ではなく,SSH接続にしたかった
    Packer公式のチュートリアルだとWinRMを使用した方法が記載されています。

https://learn.hashicorp.com/tutorials/packer/aws-windows-image?in=packer/integrations
https://www.packer.io/docs/communicators/winrm

ここでは,WinRMの設定を実行していますが、「基本認証」「暗号化無効」の設定をしているので文字通り

https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/winrmsecurity?view=powershell-7.2#ongoing-communication

やり取りされるデータは暗号化されません
「手元(ローカルマシン)からWinRM接続で,このままインスタンスの設定をするにはセキュリティ的に不安」
かつ
「当然設定を行う事で暗号化された通信はWinRMでも可能だが、WinRMの設定のチューニングの経験等があまりない」
という状態だったの、しばらくほかの方法を探していました。
そこで、下記のページにたどりつきました。

https://docs.microsoft.com/ja-jp/windows-hardware/manufacture/desktop/features-on-demand-server?view=windows-11

記載されている通り、Windows Server2019から OpenSSH(クライアント/サーバ)がFODを介してインストールできます。

https://docs.microsoft.com/ja-jp/windows-server/administration/openssh/openssh_install_firstuse

ということなので、「これはssh接続もいけそうだな」と思って試してみたというのが事の経緯です。
AMIのビルドはpacker build .で完結するように(正確にはVPC_ID等の環境向けの変数入力が必要ですが)
確認用のインスタンス立てるのとSSHのテストはterraform applyで完結させる為に、この構成にしました。
以下構成別に分けて見ていきたいと思います。

構成毎のポイント

Packer

鍵はプラグイン「sshkey」のお力を借りて生成

最初はssh-keygen叩いて、出力した鍵を同じディレクトリ内に設置していましたが、鍵を一緒に管理してくれるプラグインがあったのでPackerの構成に含めています。
https://www.packer.io/plugins/datasources/sshkey
記載の通り、PACKER_CACHE_DIRの環境変数のパスに鍵を生成してくれるので、これはそのままカレントディレクトリを指定しています。

OpenSSHサーバの起動や、公開鍵の登録、ユーザーの作成諸々は全部userdata内のpowershellで実行する

  • OpenSSHの設定
    OpenSSH、インストール、サーバの起動(サービス起動)、WindowsDefenderのルールでポート空ける等の基本設定は下記のドキュメントに記載されています。

https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell

この内容をベースにuserdataで実行されるPowershellとして記載し、肉付けしたものが
userdata.tplです。

userdata.tpl(PowerShell内での設定内容)

authorized_keysに関しての以下の説明を参考にし
https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration
authorized_keys(administrators_authorized_keys)ファイルを設置し、権限を設定しています。

パスワード認証はOFFに設定しています。

#Disable PasswordAuthentication
(Get-Content -Encoding Ascii  $env:programdata\ssh\sshd_config) | foreach { $_ -replace "#PasswordAuthentication yes","PasswordAuthentication no" } | Set-Content $env:programdata\ssh\sshd_config
  • ユーザーを作成してみて、そちらのユーザーでもSSHログインできるようにする
    ユーザープロファイル(C:\Users\ユーザー)を作成する為に Win32 API CreateProfileを呼び出します。

https://docs.microsoft.com/ja-jp/windows/win32/api/userenv/nf-userenv-createprofile

以下が参考になります

https://serverfault.com/questions/946882/how-can-i-programmatically-cause-a-new-windows-users-profile-to-be-created
https://gist.github.com/crshnbrn66/7e81bf20408c05ddb2b4fdf4498477d8#file-user-profile-psm1-L121

要約すると

  1. PowerShell Add-Typeを介して.NET(C#)のクラス定義を読み込み

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-7.2

  1. 上記クラス定義(System.Runtime.InteropServices)を介して, userenv.dllで定義されているCreateProfileの定義を読み込み

https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices?view=net-7.0

  1. 最終的にCreateProfileに引数(ユーザー名)を与えて、呼び出し

というような処理が行われています。
これを見たとき、PowerShellでこんな事もできるんだなと感心しました。

userdataをインスタンスに設定する

あとはuserdata.tplをテンプレートとし、Packerのbuiltin function「templatefile」を使用して
必要な箇所をPackerの変数に置換した最終的な文字列をインスタンスのuserdataとして、設定します。

https://www.packer.io/docs/templates/hcl_templates/functions/file/templatefile

source "amazon-ebs" "windows_ssh" {
  ~~~~~~~~~
  user_data = templatefile(var.userdata_template_path,
    {
      admin_key = data.sshkey.admin_key.public_key,
      user_name = var.normal_user_account,
      user_key  = data.sshkey.normal_user_key.public_key
    }
  )
  ~~~~~~~~~~

この状態でpacker buildを実行する事で
AMI作成開始

OpenSSHが立ち上がるまで待機

ユーザー:Administratorでssh接続

powershellのprovisionerが走る(Administratorのパスワードを設定しています)
という形の動作をする事が確認できます。

Terraform

terraform側は確認用のインスタンスを生成,及びssh接続をテストする為の構成です。

  • 確認用インスタンス生成
    module ec2_instanceのお力を借ります

https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest

インスタンスの設定に関しては、デフォルトでは

  • キーペアはresource "aws_key_pair" "instance_key"の記述にしたがって
    key-windows-sshというキーペアとして登録されます。
    登録される公開鍵はAdministrator用に生成されたキーです。
    Makefile:ADMIN_KEYをご参照ください。

  • セキュリティグループでグローバルからのSSH(22) Inboundトラフィックが許可されます。
    今回検証した、Windows Server2019, 2022のインスタンスはデフォルトでSSM Agentがインストールされているので、
    適切なポリシーを割り振ったIAM Roleのインスタンスプロファイルを割り当てる事でSSM経由でSSH接続が可能です。
    インスタンスのInbound 22番ポートを0.0.0.0/0であけずに、SSH接続が手元からできます。
    ですが、後述するsshの接続確認も.tfの構成内に含めて置きたかった点、及びやりたい事の本筋ではなかった為上記セキュリティグループをアタッチしています。

https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-win.html

https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html

  • ssh接続確認
    以下の箇所でAdministrator、及び作成したユーザー(デフォルトだとuser_a)のssh接続確認をしています
resource "null_resource" "check_ssh_connectivity_admin"{
  ~~~
}
resource "null_resource" "check_ssh_connectivity_normal"{
  ~~~
}

If the platform is set to windows, the default script_path is c:\windows\temp\terraform_%RAND%.cmd, assuming the SSH default shell is cmd.exe. If the SSH default shell is PowerShell, set script_path to "c:/windows/temp/terraform_%RAND%.ps1"

と記載がある通り、もしsshログイン時のデフォルトログインShellをcmdからPowershellに変更した場合
script_pathc:/windows/temp/terraform_%RAND%.ps1と指定が必要です。
今回はcmdのまま動かしているので、target_platformのみ指定しています。

https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#configuring-the-default-shell-for-openssh-in-windows

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
  • ssh経由でコマンド実行する
    単に"echo connected using ssh ${アカウント名}"を実行しています

所感

冷静になって考えてみると「素直にWinRMの暗号化通信設定をする」とか
「手元でPacker動かさず、PVC内で動作する踏み台サーバでPacker走らせる」とか
他の手段でも良かったなという気がします。

Discussion