Корона внесла неожиданные изменения в нашу работу, приходится подстраиваться. Крупные организации, или кого не зацепило спокойно скупили ноутбуки сотрудникам и радуются жизни. В нашем случае пришлось искать другие пути.
Кто-то пользуется Teamviewer`ом, ну что ж, флаг им в руки, а я буду держаться подальше по следующим причинам:
Кто-то пользуется Teamviewer`ом, ну что ж, флаг им в руки, а я буду держаться подальше по следующим причинам:
- Программа платная (использование за пределами одной сети = коммерческое использование)
- Использование 3 точки в цепочке
- Повышенная нагрузка на сервис
- Режим работы без блокировки монитора
- и т.д.
- Нужно иметь сервер терминалов или подключаться к пользовательским ПК
- Проброс порта RDP не помеха для сканера, гигантская дыра в безопасности
- Пробросить порты на RDP на 5 машин можно, но на 50+ и без статики на тачках - такое себе
- и т.д.
На ум напрашивается VPN и пущай пользователь как-то подключается к ресурсу (тот же Teamviewer,LiteManager, RDP)
На всех объектах используется роутер Mikrotik.
Я избрал следующий путь для одного объекта:
- Настраиваем ПК пользователей
- Включаем Wake-On-LAN и проверяем
- Персонифицируем имена компьютеров (допустим по фамилии сотрудника)
- Включаем RDP (напоминаю, он есть только в редакциях PRO и выше)
- Проверяем настройки файервола и антивируса, чтобы они не блокировали трафик для удаленного доступа от пула адресов VPN сервера
- Настраиваем роутер:
- Пул DHCP сервера должен быть отличен от 192.168.Х.Х, т.к. у пользователей дома именно такая сеть.
- Фиксируем лизы для компьютеров
- Настраиваем L2TP IPsec PSK (или другой VPN сервер на роутере)
- Создаем пользователей (имя пользователя соответствует имени его ПК)
- Проверяем настройки файервола, чтобы трафик из сети VPN ходил в сеть организации
- В разделе PPP>Profiles>НашПрофиль>вкладка Scripts, здесь мы пишем скрипт в On Up (получаем MAC,шлем магический пакет):
:local macAddress [/ip dhcp-server lease get [/ip dhcp-server lease find host-name=$user] mac-address]; tool wol interface=bridge mac=$macAddress :log info message=("User " . $user . " connetcted. Sending magic packet for MAC=" . $macAddress)
- Настраиваем подключение с личного ПК пользователя, обязательно отключаем "Использовать как основной шлюз"
На другом объекте машин 50+, и DHCP сервер находится на контроллере домена, хоть шлюзом и выступает Mikrotik. Почесав репу, вспомнил что список MAC-NAME_PC у меня есть на сервере для бэкапов. Вот и решил его использовать. Логика остается та же:
- Имя пользователя ПК=имени ПК=имя пользователя VPN
- При подключении находим MAC, отправляем магический пакет
- Если через 60 секунд машина не пингуется, то сбросить подключение
- Иначе добавить правило в файервол, разрешающее трафик на порт 3389 от конкретного пользователя к конкретному ПК
- При отключении удалить правило
Мысль огонь, мне зашло как надо, но в ходе реализации я столкнулся с кучей проблем:
- Язык Mikrotik оставляет желать лучшего, аналога awk нету
- Область действия переменных. Т.к. итоговый скрипт длинный, я решил его записать в разделе System>Script, но для того чтобы его вызвать при подключении пользователя, нужно объявить переменные (параметры для скрипта) как глобальные. Глобальные переменные работают на все подключения и тут возникает гигантская коллизия. Пришлось при получении параметра сразу уничтожать глобальную переменную.
- Попытка разбора файла на сервере с UNIX системой потерпела фиаско из-за требования входить на сервер по сертификату
- и т.д.
В итоге листинг скрипта:
#get name user, resolve, wol, ping, add rule
#Example run
#:global Iuser $user;:global IremoteAddress $"remote-address";/system script run plug_user_local
#Two input param
:global Iuser;
:global IremoteAddress;
:put ("Input: " .$Iuser. " ". $IremoteAddress)
:local user [$Iuser]
:local remoteAddress [$IremoteAddress];
# Remove global var
/system script environment remove IremoteAddress
/system script environment remove Iuser
:local dnsServer "10.20.0.2"
:local HOST ($user . ".mydomain.ru");
:local PINGCOUNT "2";
:local CodeExitPing;
:local ipAdr;
:local macAddr;
:local patern $user;
:local FileName "compoff2.txt";
:local StatusCode "1"
:put ("LocalInput: " .$user. " ". $remoteAddress)
:put ("plug: " . $HOST . " ". $PINGCOUNT . " ". $dnsServer)
#####################
##FUNC##
###GETMAC
:local getMac do={
:local macAddr "";
:do { [/file get [/file find name=$FileName]]} on-error={:set macAddr "0"}
:put ("FN " . $FileName . " " .$patern)
if ([ :len $FileName]!=0 and [ :len $patern ]!=0 and [/file get [/file find name=$FileName] size] != 0 and $macAddr="") do={
:put ("getMac begin")
:local content [/file get [/file find name=$FileName] contents] ;
:local contentLen [ :len $content ] ;
:local lineEnd 0;
:local line "";
:local lastEnd 0;
:do {
:set lineEnd [:find $content "\n" $lastEnd ] ;
:set line [:pick $content $lastEnd $lineEnd] ;
:set lastEnd ( $lineEnd + 1 ) ;
:if ( [:pick $line 0 1] != "#" ) do={
:local entry [:pick $line 0 $lineEnd ]
:if ( [:len $entry ] > 0 ) do={
if ([:find $entry $patern]>0) do={
:set macAddr [:pick $entry ([:len $entry]-20) ([:len $entry]-3)]
#break
:set lastEnd ( $contentLen + 1 ) ;
}
}
}
} while ($lastEnd < $contentLen)
}
if ($macAddr="") do={:set macAddr "0"}
:return $macAddr
}
###checkPing
:local checkPing do={
:put ("ping: " . $HOST ." ". $PINGCOUNT ." ". $dnsServer)
:local ipAdr;
:local CodeExitPing;
if ( $HOST != "" and $PINGCOUNT != "" and $dnsServer != "") do={
:do {:set ipAdr [:resolve $HOST server=$dnsServer]} on-error={:set ipAdr "0"};
if ($ipAdr=0) do={
:set CodeExitPing "0"
} else {
:set CodeExitPing [/ping $ipAdr interval=1 count=$PINGCOUNT];
}
} else={
:set CodeExitPing "0";
:put "Empty param"
}
:return $CodeExitPing
}
###FUNC##
#####################
:set CodeExitPing [$checkPing HOST=$HOST PINGCOUNT=$PINGCOUNT dnsServer=$dnsServer]
:put ("Exit code " . $CodeExitPing);
if ($CodeExitPing=0) do={
set macAddr [$getMac patern=$patern FileName=$FileName]
:put ("macAddr " . $macAddr)
if ($macAddr != 0) do={
:put ("|" . $macAddr . "|")
/tool wol mac=$macAddr interface=LAN_Bridge
:put "Delay 60s"
:log info message=("Send magic packet on mac:".$macAddr.", for user ".$patern)
:delay delay-time=60
:set CodeExitPing [$checkPing HOST=$HOST PINGCOUNT=$PINGCOUNT dnsServer=$dnsServer]
if ($CodeExitPing>0) do={
:set StatusCode "0"
}
}
} else={
:put "if else"
:set StatusCode "0"
}
:put ("Pre error" . $StatusCode)
if ($StatusCode = 0) do={
:set ipAdr [:resolve $HOST server=$dnsServer]
:local email "it@mydomain.ru"
/tool e-mail send to=$email subject="[VPN_RDP] User $user connected" body="User $user connected at $[/system clock get time].\r\nIP-address - $"caller-id".\r\nInfo - http://apps.db.ripe.net/search/query.html?searchtext=$"caller-id""
:log info message=("Conected " . $user . " ip pc_office" . $ipAdr . " ip pc_vpn " . $remoteAddress)
:local p [/ip firewall filter find comment~"VPN_AUTO_END"]; #Специально созданное правило-метка (оно еще разрешает DNS запросы)
:local comm ("VPN_auto.".$user . "." . $remoteAddress) #унифицированная метка правила
/ip firewall filter add action=accept chain=forward comment=$comm dst-port=3389 out-interface=LAN_Bridge protocol=tcp src-address=$remoteAddress dst-address=$ipAdr
/ip firewall filter add action=accept chain=forward comment=$comm dst-port=3389 out-interface=LAN_Bridge protocol=udp src-address=$remoteAddress dst-address=$ipAdr
/ip firewall filter move [find comment=$comm] destination=$p
} else={
:log info message=("PC not found" . $remoteAddress)
/ppp active remove [find where name=$user]
}
И собственно в On up:
:global Iuser $user;
:global IremoteAddress $"remote-address";
/system script run plug_user_local
И собственно в On down:
:local email "it@mydomain.ru"
/tool e-mail send to=$email subject="[VPN_RDP] User $user disconnected" body="User $user disconnected at $[/system clock get time]."
:local comm ("VPN_auto.".$user . "." . $"remote-address"); /ip firewall filter remove [find comment=$comm]
UP 27/05/2020
Забыл добавить, в последнем варианте при помощи GPO включается RDP на клиентских машинах и в локальную группу "Пользователи удаленного рабочего стола" добавляется разрешенный пользователь.
Именно для этого и было создано соответствие Имя Пользователя ПК (домена)=Имени ПК
Политика нацелена на ПК входящие в группу разрешенных и логика выглядит так:
Если ПК в группе Разрешенные, то добавить пользователя с именем mydomain\%computername% в группу
UP 10/12/2022, для отправки wol в другой сегмент широковещательной сети был добавлен запуск удаленной команды на другом Mikrotik, плюс куча лог-выводов для диагностики
#get name user, resolve, wol, ping, add rule
#Example run
#:global Iuser $user;:global IremoteAddress $"remote-address";/system script run plug_user_local
#Two input param
:global Iuser;
:global IremoteAddress;
:global ICallerId;
:global WriteLog 1
:put ("Input: " .$Iuser. " ". $IremoteAddress)
:local user [$Iuser]
:local remoteAddress [$IremoteAddress];
:local CallerId [$ICallerId];
# Remove global var
/system script environment remove IremoteAddress
/system script environment remove Iuser
/system script environment remove ICallerId
#Для теста из консоли
#:local user "etishk"
#:local remoteAddress "10.30.5.10";
:local dnsServer "10.20.0.3"
:local HOST ($user . ".domain.ru");
:local PINGCOUNT "2";
:local CodeExitPing;
:local ipAdr;
:local macAddr;
:local patern $user;
:local FileName "compoff2.txt";
:local StatusCode "1"
#####################
##FUNC##
# Func loging. For debug. Error write forever, other only of WriteLog <0 (0 1 2 - level logging), setup up of global var
# $TYPE - topics (error,info)
# $LEVEL - level logging
# 0 only error
# 1 main
# 2 all
# $MESSAGE - text message
# Example
# $Logging MESSAGE=("My text") TYPE="info" LEVEL=2
# for use in func use global context and declared WriteLog
# Exmaple
# :global WriteLog
# $Logging MESSAGE=("lineEnd=(".$lineEnd.");line=(".$line.");lastEnd=(".$lastEnd.")") TYPE="info" LEVEL=2
:global Logging do={
:global WriteLog
:local TextMessage ("PlugUser => ".$MESSAGE)
:if ($LEVEL>=$WriteLog) do={
:if ($TYPE="info") do={ :log info message=($TextMessage) }
:if ($TYPE="error") do={ :log error message=($TextMessage) }
}
:put $TextMessage
}
###GETMAC
:local getMac do={
:global Logging
:local macAddr "";
:do { [/file get [/file find name=$FileName]]} on-error={:set macAddr "0"}
$Logging MESSAGE=("FN=(" . $FileName . ") pattern=(" .$patern.")") TYPE="info" LEVEL=1
:if ([:len $FileName]!=0 and [ :len $patern ]!=0 and [/file get [/file find name=$FileName] size] != 0 and $macAddr="") do={
$Logging MESSAGE=("Len Filename=(".[:len $FileName]."). Len pattern=(".[:len $patern ]."). FileSize=(".[/file get [/file find name=$FileName] size]."). macAddr can be empty=(".$macAddr.")") TYPE="info" LEVEL=2
:put ("getMac begin")
:local content [/file get [/file find name=$FileName] contents] ;
:local contentLen [ :len $content ] ;
:local lineEnd 0;
:local line "";
:local lastEnd 0;
:do {
:set lineEnd [:find $content "\n" $lastEnd ] ;
:set line [:pick $content $lastEnd $lineEnd] ;
:set lastEnd ( $lineEnd + 1 ) ;
$Logging MESSAGE=("lineEnd=(".$lineEnd.");line=(".$line.");lastEnd=(".$lastEnd.")") TYPE="info" LEVEL=2
:if ( [:pick $line 0 1] != "#" ) do={
:local entry [:pick $line 0 $lineEnd ]
:if ( [:len $entry ] > 0 ) do={
if ([:find $entry $patern]>0) do={
:set macAddr [:pick $entry ([:len $entry]-20) ([:len $entry]-3)]
#break
:set lastEnd ( $contentLen + 1 ) ;
}
}
}
} while ($lastEnd > $contentLen)
} else={
$Logging MESSAGE=("Function getMac can not be exec") TYPE="error" LEVEL=0
}
if ($macAddr="") do={:set macAddr "0"}
$Logging MESSAGE=($macAddr) TYPE="info" LEVEL=1
:return $macAddr
}
###checkPing
:local checkPing do={
:global Logging
$Logging MESSAGE=("Ping: " . $HOST ." ". $PINGCOUNT ." ". $dnsServer) TYPE="info" LEVEL=1
:local ipAdr;
:local CodeExitPing;
if ( $HOST != "" and $PINGCOUNT != "" and $dnsServer != "") do={
:do {:set ipAdr [:resolve $HOST server=$dnsServer]} on-error={:set ipAdr "0"};
if ($ipAdr=0) do={
:set CodeExitPing "0"
$Logging MESSAGE=("Not resolve host: " . $HOST ." on ". $dnsServer) TYPE="info" LEVEL=1
} else={
:set CodeExitPing [/ping $ipAdr interval=1 count=$PINGCOUNT];
$Logging MESSAGE=("Resolve host: " . $HOST ." on ". $dnsServer." to ".$ipAdr) TYPE="info" LEVEL=1
}
} else={
:set CodeExitPing "0";
$Logging MESSAGE=("CheckPing Empty param") TYPE="error" LEVEL=0
}
:return $CodeExitPing
}
###FUNC##
#####################
$Logging MESSAGE=("LocalInput: " .$user. " ". $remoteAddress) TYPE="info" LEVEL=1
:set CodeExitPing [$checkPing HOST=$HOST PINGCOUNT=$PINGCOUNT dnsServer=$dnsServer]
$Logging MESSAGE=("CheckPing return exit code (". $CodeExitPing.")") TYPE="info" LEVEL=1
if ($CodeExitPing=0) do={
:set macAddr [$getMac patern=$patern FileName=$FileName]
if ($macAddr != 0) do={
$Logging MESSAGE=("Send magic packet on mac:".$macAddr.", for user ".$patern) TYPE="info" LEVEL=1
/tool wol mac=$macAddr interface=bridge1_LAN
:local RemoteMessage ("Send magic packet on mac:$macAddr, for user $patern")
do {/system ssh-exec user=vint address=10.21.0.1 command=("tool wol mac=$macAddr interface=bridge1_LAN; :log info message=(\"Send magic packet on mac:$macAddr, for user $patern\")")
} on-error={
$Logging MESSAGE=("SSH Command execution error") TYPE="error" LEVEL=0
}
$Logging MESSAGE=("Delay 60s") TYPE="info" LEVEL=2
:delay delay-time=60
:set CodeExitPing [$checkPing HOST=$HOST PINGCOUNT=$PINGCOUNT dnsServer=$dnsServer]
if ($CodeExitPing>0) do={
:set StatusCode "0"
} else={
$Logging MESSAGE=("Host (".$HOST.") not turning on") TYPE="error" LEVEL=1
}
} else={
$Logging MESSAGE=("User not found (".$patern.")") TYPE="error" LEVEL=0
:set StatusCode "1"
}
} else={
:set StatusCode "0"
}
$Logging MESSAGE=("Status code(".$StatusCode.")") TYPE="info" LEVEL=1
if ($StatusCode = 0) do={
:set ipAdr [:resolve $HOST server=$dnsServer]
:local email "it@domain.ru"
/tool e-mail send to=$email subject="[VPN_RDP] User $user connected" body="User $user connected at $[/system clock get time].\r\nIP-address --- $CallerId.\r\nInfo - http://apps.db.ripe.net/search/query.html?searchtext=$CallerId"
$Logging MESSAGE=("Conected " . $user . " ip pc_office" . $ipAdr . " ip pc_vpn " . $remoteAddress) TYPE="info" LEVEL=1
:local p [/ip firewall filter find comment~"VPN_AUTO_END"];
:local comm ("VPN_auto.".$user . "." . $remoteAddress)
/ip firewall filter add action=accept chain=forward comment=$comm dst-port=3389,5650 protocol=tcp src-address=$remoteAddress dst-address=$ipAdr
/ip firewall filter move [find comment=$comm] destination=$p
} else={
$Logging MESSAGE=("PC (".$remoteAddress.") not found. Name user (".$user.")" ) TYPE="info" LEVEL=1
/ppp active remove [find where name=$user]
}
# Remove global var
/system script environment remove WriteLog
/system script environment remove Logging
Комментариев нет:
Отправить комментарий