원격 접속은 2011년에도 쓴 주제다. 쓰는 명령은 그때와 같다. 달라진 것은 길이 하나 더 생겼다는 점이다.
| WinRM | SSH | |
|---|---|---|
| 대상 | Windows | Windows, 리눅스, macOS |
| 매개변수 | -ComputerName | -HostName |
| 포트 | 5985(HTTP), 5986(HTTPS) | 22 |
| 인증 | Kerberos, NTLM, 인증서 | 키, 암호 |
| 쓰기 좋은 곳 | 도메인에 든 Windows 서버 | 그 밖의 전부 |
도메인 안의 Windows 서버끼리는 WinRM이 편하다. 로그인한 계정으로 그냥 들어간다. 도메인이 없거나 리눅스가 섞여 있으면 SSH가 훨씬 덜 번거롭다.
쓰는 명령은 셋
들어가서 일하기. ssh처럼 상대의 프롬프트를 얻는다.
Enter-PSSession -ComputerName server1
Enter-PSSession -HostName server1 -UserName talsu
명령만 보내기. 결과가 개체로 돌아오고, 어느 컴퓨터에서 왔는지 PSComputerName에 적혀 있다.
Invoke-Command -ComputerName web1, web2, web3 -ScriptBlock {
Get-Service W3SVC
} | Select-Object PSComputerName, Status
여러 대를 적으면 차례로가 아니라 동시에 실행한다(기본 32대씩, -ThrottleLimit로 조절).
세션을 만들어 두고 쓰기. 같은 곳에 여러 번 보낼 때는 세션을 만들어 다시 쓴다. 매번 연결하지 않아 빠르고, 앞에서 만든 변수가 남아 있다.
$session = New-PSSession -HostName server1 -UserName talsu
Invoke-Command -Session $session -ScriptBlock { $count = (Get-Process).Count }
Invoke-Command -Session $session -ScriptBlock { $count }
Remove-PSSession $session
세션으로 파일도 옮긴다.
Copy-Item ./app.zip -Destination /tmp/ -ToSession $session
Copy-Item /var/log/app.log -Destination ./ -FromSession $session
내 변수를 원격으로 보내기
스크립트 블록은 상대 컴퓨터에서 실행되므로 내 변수를 모른다. $using:을 붙여 값을 실어 보낸다.
$name = 'nginx'
Invoke-Command -HostName server1 -ScriptBlock { Get-Process -Name $using:name }
돌아온 개체는 복사본이다
원격에서 돌아온 개체는 직렬화했다가 다시 만든 것이다. 속성은 그대로 있지만 메서드는 없다. 프로세스를 끝내는 것처럼 메서드가 필요한 일은 스크립트 블록 안에서 한다.
# 안 됨: 돌아온 복사본에는 Kill()이 없다
(Invoke-Command -HostName server1 { Get-Process app }).Kill()
# 됨
Invoke-Command -HostName server1 { Stop-Process -Name app }
SSH로 준비하기
접속하는 쪽에는 ssh 클라이언트만 있으면 된다. 받는 쪽에는 PowerShell 7과 SSH 서버가 있어야 하고, sshd_config에 PowerShell을 하위 시스템으로 등록한다.
# 리눅스: /etc/ssh/sshd_config
Subsystem powershell /usr/bin/pwsh -sshs -NoLogo
# Windows: %ProgramData%\ssh\sshd_config
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo
sshd를 다시 시작하면 끝이다. 인증은 SSH의 것을 그대로 쓰므로, 키를 등록해 두면 암호 없이 들어간다.
Invoke-Command -HostName server1 -UserName talsu -KeyFilePath ~/.ssh/id_ed25519 -ScriptBlock { $PSVersionTable.OS }
WinRM으로 준비하기 (Windows)
받는 쪽에서 관리자 권한으로 한 번 실행한다. Windows Server는 기본으로 켜져 있다.
Enable-PSRemoting
WinRM 서비스를 시작하고, 수신기를 만들고, 방화벽 예외를 넣는다. 끝점은 이 명령을 실행한 PowerShell의 것이 만들어진다. 상대가 PowerShell 7로 받게 하려면 pwsh에서 실행한다.
도메인 안이면 이것으로 끝이다. 도메인 밖(작업 그룹)이면 서로를 확인할 방법이 없어 연결이 거부된다. 접속하는 쪽에서 상대를 믿는다고 적어 줘야 한다.
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'server1,192.168.0.10'
Get-Item WSMan:\localhost\Client\TrustedHosts
2010년 글에는 귀찮으면 -Value *로 모두 믿으라고 적었다. 그렇게 하지 않는다. TrustedHosts에 든 상대에게는 신원을 확인하지 않고 자격 증명을 보낸다. 필요한 이름만 적는다. 그리고 도메인 밖에서 원격 접속을 꾸릴 일이 생겼다면, TrustedHosts를 손보기 전에 SSH를 먼저 고려한다.
80번 포트로 받기에 대해
2011년에, 방화벽이 80번만 열어 주는 망에서 WinRM을 80번으로 받게 하는 설정을 소개했다.
Set-Item WSMan:\localhost\Service\EnableCompatibilityHttpListener $true
설정은 지금도 있지만 권하지 않는다. 80번은 WinRM 1.x와의 호환을 위한 포트이고, 웹 서버와 겹친다. 포트가 막혀 있다면 망 관리자와 5986(HTTPS)이나 22(SSH)를 여는 것이 바른 길이다.
자격 증명
다른 계정으로 접속하려면 -Credential에 자격 증명 개체를 준다. 가장 단순한 것은 물어보는 것이다.
$cred = Get-Credential
Invoke-Command -ComputerName server1 -Credential $cred -ScriptBlock { whoami }
사람이 없는 자동화에서는 물어볼 수 없다. 2011년 글에서는 평문 암호를 받아 SecureString으로 만드는 New-SecureString 함수를 만들어, 스크립트에 암호를 적어 두고 썼다. 암호를 스크립트에 적지 않는다. 저장소에 올라가고, 기록에 남고, 화면에 찍힌다.
암호는 금고에 넣고 이름으로 꺼낸다. Microsoft가 만든 SecretManagement 모듈이 그 일을 한다.
Install-PSResource Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
Register-SecretVault -Name Local -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
Set-Secret -Name server1-admin -Secret (Get-Credential) # 한 번만
$cred = Get-Secret -Name server1-admin
Invoke-Command -ComputerName server1 -Credential $cred -ScriptBlock { whoami }
금고의 뒤쪽은 바꿔 끼울 수 있다. 로컬 암호화 파일(SecretStore) 대신 Azure Key Vault나 1Password 같은 것을 붙여도 스크립트는 그대로다.
SSH 쪽은 더 간단하다. 키를 쓰면 암호를 다룰 일 자체가 없다.
한 다리 건너는 문제
원격으로 들어간 컴퓨터에서 또 다른 컴퓨터(파일 서버 등)에 접근하면 권한이 없다고 나온다. 첫 번째 컴퓨터가 내 자격 증명을 다음으로 넘기지 못하기 때문이다(이중 홉). 가장 단순한 해결은 스크립트 블록 안에서 $using:cred로 자격 증명을 실어 보내 그쪽에서 직접 쓰는 것이다.
Invoke-Command -ComputerName server1 -ScriptBlock {
New-PSDrive -Name share -PSProvider FileSystem -Root \\files\data -Credential $using:cred
}
다음 글: 네트워크와 웹