Зворотний тунель для доступу RDP з використанням putty і icinga2



Не завжди є можливість організувати єдину локальну мережу в силу різних обставин, але є необхідність у віддаленому доступі за протоколом RDP до хостам за натом з сірим ip, наприклад невеликий віддалений філіал з каналом зв'язку через стільниковий зв'язок. Так, звичайно можна підняти щось на зразок openVPN або скористатися TeamViewer, але знову ж такі варіанти не завжди прийнятні. Будемо використовувати icinga2, putty, SSH сервер і декілька скриптів на powershell.

Спочатку теорія. Для того що б підключитися до сервера не знаючи його реальний ip адреса і при цьому він ще і за натом, потрібно що б сервер сам створив вихідне підключення. Нам знадобитися зовнішній сервер з постійним ip-адресою і розгорнутий на ньому SSH сервер, для цього можемо скористатися VDS сервером з мінімальною конфігурацією за смішні гроші, або використовувати свій.

Установка сервера SSH:

sudo apt-get install openssh-server

Створення користувача без доступу до шелу:

sudo useradd -d /dev/null -s /dev/null ssh_user
sudo passwd ssh_user

На всякий випадок змінимо порт за замовчуванням з 22 на 2201. Це можна зробити, змінивши параметр Port у файлі /etc/ssh/sshd_config.

Створюємо тунель. На віддаленому сервері необхідно виконати команду:

plink.exe -batch -P 2201 -N -C -v -R 33899:localhost:3389 ssh_user@222.222.222.222 -pw password

А на комп'ютері адміністратора виконати команди:

plink.exe -P 2201 -N -C -v -L 3379:localhost:33899 ssh_user@222.222.222.222 -pw password
mstsc.exe /v localhost:3379

Але це все теорія, на практиці ж ми можемо зіткнутися з різними нюансами. При стабільному каналі може обриватися тунель і використовувані порти залишаються відкритими, віддаленого сервера потрібно якось повідомити, що б він підняв тунель, нам може знадобитися підключення до декількох серверів одночасно.

Найпростіше рішення це завдання створити в планувальнику яка кожні n хвилин буде опитувати якийсь ресурс в інтернеті на наявність певного прапора до дії і при виявленні цього прапора створювати тунель з відправкою email на нашу адресу. Ми ж зробимо все витонченіше, скористаємося системою моніторингу яка у нас встановлена, наприклад такий як icinga2. У цьому випадку віддаленого сервера з встановленим агентом можна буде просто послати команду на створення тунелю в потрібний нам момент часу.

Якщо ви ще не стикалися з цією системою моніторингу, рекомендую подивитися:

Створимо сервіс в icinga2 для створення тунелю:

apply Service "create-rdp-tunnel" {
enable_active_checks = false
max_check_attempts = 2
assign where host.name == Вузла
ignore where host.vars.os == "Linux"
check_command = "powershell"
vars.ps_command = "c:\\ProgramData\\icinga2\\Scripts\\icinga2\\create_rdp_tunnel.ps1"
}

Тепер потрібно поширити скрипт створення тунелю і файл plink.exe на агенти. У попередній статті було описано як це можна зробити з допомогою icinga2.

Скрипт створення тунелю
<#
icinga2scripts
Версія 1.0
Description: Скрипт для Icinga 2 - Створення ssh тунелю з віддаленим хостом
Pavel Satin © 2016
pslater.ru@gmail.com
#>
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$returnStateOK = 0
$returnStateWarning = 1
$returnStateCritical = 2
$returnStateUnknown = 3


$portnum = "338" + (Get-Random -minimum 10 -maximum 99).ToString()


$tunnelcmd = "c:\ProgramData\icinga2\Scripts\icinga2\plink.exe"
$tunnelarg = "-batch -P 2201 -N -C -v -R " + $portnum + ":localhost:3389 ssh_user@222.222.222.222 -pw password"


$regSSHkey = "HKCU:\Software\SimonTatham\PuTTY\SshHostKeys"
$regSSHname = "rsa2@2201:222.222.222.222"
$regSSHval = "0x10001,0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

if (!(Test-Path $regSSHkey -PathType Any)) {
New-Item -Path $regSSHkey -Force | Out-Null
New-ItemProperty -Path $regSSHkey -Name $regSSHName -Value $regSSHval -PropertyType String -Force | Out-Null
} else {
New-ItemProperty -Path $regSSHkey -Name $regSSHName -Value $regSSHval -PropertyType String -Force | Out-Null
}


$process = (start-process $tunnelcmd -argumentlist $tunnelarg -PassThru)

Start-Sleep -s 5

if ($process.HasExited) {
Write-Host "Failed to start plink. The process is closed with the code: " $process.ExitCode
[System.Environment]::Exit($returnStateCritical)
} else {

#Відправка pushover повідомлення
$uri = "https://api.pushover.net/1/messages.json"
$parameters = @{
token = "API_TOKEN"
user = "API_USER"
message = "Створений тунель на порту: $portnum з вузлом: $env:computername"
}

$pushoverreq = $parameters | Invoke-RestMethod -Uri $uri -Method Post

Write-Host "OK - The tunnel is created. Port number: $portnum"
Write-Host "To connect:"
Write-Host "plink.exe -P 2201 -N -C -v -L 3379:localhost:$portnum ssh_user@222.222.222.222 -pw password"
[System.Environment]::Exit($returnStateOK)
}



В скрипті врахований такий момент як запит на збереження ключів ssh віддаленого сервера. Якщо на віддаленому сервері жодного разу не виконувалося підключення до сервера SSH, putty буде видавати попередження при підключенні, а plink взагалі відмовиться підключатися.


Що б уникнути цього, потрібно підключитися до сервера SSH з будь-якої іншої машини, висмикнути з реєстру кешовані ключі (HKCU:\Software\SimonTatham\PuTTY\SshHostKeys) і змінити змінну $regSSHval в скрипті. Скрипт створює підключення до псевдовипадкового номером порту, що допоможе уникнути конфліктів при безлічі сполук. Так само скрипт відправляє push повідомлення за допомогою сервісу pushover.net.

Відправимо команду агенту на створення тунелю. Це можна зробити через веб-інтерфейс або в консолі нашого сервера моніторингу:

/bin/echo "[`date +%s`] SCHEDULE_FORCED_SVC_CHECK;ИмяНоды;create-rdp-tunnel;`date +%s`" >> /var/run/icinga2/cmd/icinga2.cmd

Тепер адміністратору на своєму комп'ютері залишиться тільки запустити скрипт із зазначенням імені віддаленого сервера в якості аргументу. Решту зробить скрипт, з'ясує на якому порту створений тунель, встановить тунель з пробросом порту, запустить клієнт rdp з потрібними параметрами.

Скрипт підключення через тунель
<#
icinga2scripts
Версія 1.0
Description: Скрипт для Icinga 2 - Запуск RemoteDesktop через тунель
Pavel Satin © 2016
pslater.ru@gmail.com
#>

$returnStateOK = 0
$returnStateWarning = 1
$returnStateCritical = 2
$returnStateUnknown = 3

#Windows Balloon
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objNotifyIcon = New Object System.Windows.Forms.NotifyIcon 

if ($args[0] -eq $null) {

$objNotifyIcon.Icon = "C:\Scripts\images\icinga.ico"
$objNotifyIcon.BalloonTipIcon = "Error" 
$objNotifyIcon.BalloonTipText = "Параметр з ім'ям хоста не переданий! Робота скрипта завершена."
$objNotifyIcon.BalloonTipTitle = "Підключення через тунель"

$objNotifyIcon.Visible = $True 
$objNotifyIcon.ShowBalloonTip(30000)

Start-Sleep -s 10
$objNotifyIcon.Visible = $false
$script:objNotifyIcon.Dispose()
exit
}

$rdpHost = $args[0]

$plinkPath = "C:\Scripts\bin\"

add-type -TypeDefinition @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New Object TrustAllCertsPolicy

$user = "icinga"
$pass= "password"
$secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force
$credential = New Object System.Management.Automation.PSCredential($user, $secpasswd)
$apiurl = "https://222.222.222.222:5665/v1/objects/services/" + $rdpHost + "!create-rdp-tunnel?attrs=last_check_result"

$apireq = Invoke-WebRequest -Credential $credential -Uri $apiurl -Method Get -UseBasicParsing -ContentType "text/plain; charset=Windows-1251"

$outputresult = $apireq | ConvertFrom-Json | Select -expand Results | Select -expand attrs | Select -expand last_check_result 
$strOutput = $outputresult.output
$indxPlink = $strOutput.IndexOf("plink")

$portnum = "339" + (Get-Random -minimum 10 -maximum 99).ToString()

$strOutput2 = $strOutput.Substring($indxPlink, $strOutput.Length - $indxPlink)

$cmdArgs = "/C " + $strOutput2.Replace("3379", $portnum)
$mstscArgs = "/v localhost:$portnum"

#Запуск процесів
Start-Process cmd.exe $cmdArgs
Start-Process mstsc.exe $mstscArgs

$objNotifyIcon.Icon = "C:\Scripts\images\icinga.ico"
$objNotifyIcon.BalloonTipIcon = "Info" 
$objNotifyIcon.BalloonTipText = "Встановлюємо підключення до $rdpHost"
$objNotifyIcon.BalloonTipTitle = "Підключення через тунель"

$objNotifyIcon.Visible = $True 
$objNotifyIcon.ShowBalloonTip(30000)

Start-Sleep -s 30
$objNotifyIcon.Visible = $false
$script:objNotifyIcon.Dispose()




Посилання

Putty
Використовуємо powershell скрипти в icinga2
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.