Одна история ВПН-а!

Коронавирус в действии.

Как обычно раздался звонок, на той стороне нервно курит системный администратор, просит помочь организовать VPN, думаю не чего страшного, стандартная реализация IPSec ikev2 сертификат и погнали. Начали общаться с заказчиком и начали вылезать очень сомнительная реализация.

Вы только вдумайтесь 400 компьютеров, 400 пользователей нет АД, нет не каких серверов, ну точнее они есть но толку от них мало. Каждый пользователь работает локально на своём компьютере с локальной авторизацией. Мысленно, когда задавал наводящие вопросы я понимал, что это «блудняк», но вспомнил себя в молодости, было что-то похожее, хотя даже тогда из-за отсутствия финансов в компании на лицензии я умудрялся поднимать AD на samba + ldap да и просто было интересно набить шишки и решил вписаться в «блудняк».

И так условия:

  1. 400 пользователей, логинов и паролей нет, ну логины достать ещё можно, а пароли точно нет.
  2. 400 компьютеров.

  3. Каждый пользователей работает только на своём компьютере.

  4. Централизованной аутентификации нет.

  5. Есть админская учётная азпись на компьютерах и включена возможность подключатся по RDP.

Задача:

  1. Необходимо предоставить пользователям возможность подключаться к своим компьютерам.
  2. Сроки два дня.

Конечно, мы начали «топить» за реализация VPN, в принципе любую, но выяснился один момент в локальной сети используется сеть 192.168.0.0/16 да именно 16, не какая одна или две 23 или 22, а именно 16, и компы размазанный по данной сети. Проблема реализации VPN была в том, что внутренняя адресация пересекается 100% с локальными сетями, так как у большинства пользователей дома обязательно стоит 192.168.0.0/24 а отсюда вылезет головная боль с подключением так как connected маршрут всегда выигрывает.

Так как сроки разворачивания данного решения были совсем маленькие всего лишь два дня, было принято решение использовать port knocking, как временную реализацию. Админ пообещал пересадить всех на ВПН со временем ))).

Системному администратору было дано задание написать в файлике excel сопоставление пользователя и IP адреса компьютера за которым пользователь работает и включить RDP, в итогу за пол дня было собранно всего совсем немного чётных данных, в итоге пришлось самим написать простенькую программку которая по psexec обходила все компьютеры и находила список пользователей на компьютере и записывала в файл информацию какие пользователи на каких компьютерах, но это всё мелочи.

Главное реализация VPN и вот перед нами был список почти из 400 записей IP, user в файле csv.

Первое мы должны были определить список knock. Решили использовать только TCP взяли следующий порядок портов 666,777,888,333,55000.

Тут всё просто, начали с самого простого.

Создали в RAW следующую конфигурацию.

/ip firewall raw
add action=add-src-to-address-list address-list=knk-st1 address-list-timeout=1m chain=prerouting dst-port=666 in-interface=ether1 protocol=tcp

на одно минуту добавляем в лист knk-st1 IP адрес, который постучался на порт 666.

/ip firewall raw
add action=jump chain=prerouting jump-target=Knock src-address-list=knk-st1

Вторым правилом мы указали, если IP адрес источника содержится в листе, то прыгаем на кастомную цепочку, тем самым немного снизим нагрузку с ЦПУ.

/ip firewall raw
add action=add-src-to-address-list address-list=knk-st2 address-list-timeout=10s chain=Knock dst-port=777 protocol=tcp src-address-list=knk-st1
add action=add-src-to-address-list address-list=knk-st3 address-list-timeout=10s chain=Knock dst-port=888 protocol=tcp src-address-list=knk-st2
add action=add-src-to-address-list address-list=knk-st4 address-list-timeout=10s chain=Knock dst-port=333 protocol=tcp src-address-list=knk-st3
add action=add-src-to-address-list address-list=knk-final address-list-timeout=10s chain=Knock dst-port=55000 protocol=tcp src-address-list=knk-st4 

Далее мы перебрали все порты knock и нам осталось только каким-либо образом сделать уникальность.

/ip firewall raw
add action=jump chain=prerouting dst-port=10000-10500 jump-target=RDP protocol=tcp src-address-list=knk-final 

Если на маршрутизатор придёт пакет с адресом источника который находится в адрес листе knk-final мы направляем его в костюмную цепочку RDP, так же смотри чтобы протокол был TCP и попадал в некий диапазон портов.

Далее нам необходимы было создать 400 правил такого типа.

/ip firewall raw
add chain=RDP protocol=tcp dst-port=10002 action=add-src-to-address-list address-list=RDP/192.168.0.2 address-list-timeout=120
add chain=RDP protocol=tcp dst-port=10003 action=add-src-to-address-list address-list=RDP/192.168.0.3 address-list-timeout=120

Естественно, мы руками 400 правил не создавали, всё сделали через API в течение 5 минут.

Далее нам необходимо было подготовить 400 правил NAT

/ip firewall nat 
add chain=dstnat src-address-list=RDP/192.168.0.2 protocol=tcp port=3389 action=dst-nat to-addresses=192.168.0.2
add chain=dstnat src-address-list=RDP/192.168.0.3 protocol=tcp port=3389 action=dst-nat to-addresses=192.168.0.3
...

Опять же только через API, я не люблю мозоли.

И так у нас всё готово, теперь осталось дело за софтом возьмём наш knock и немного его модифицируем

Создадим отдельный файл v.go с таким содержанием

package main
var Lastkey = 10002 

Lastkey – это переменная которая буден указывать на порт, который задаёт уникальность для процедуры knock.

И изменим функцию main всё до строки if options.Action == "encrypt" {

options.Action = "knock"
options.Loop = 1
options.Command = "H:1.1.1.1,T:666,T:777,T:888,T:333,T:55000T:" + strconv.Itoa(m.Lastkey)

Что бы пользователя вообще нечего не пришлось делать, мы сгенерируем для каждого пользователя свой исполняемый файл.

Так как Системный администратор хотел сам скомпилировать и проконтролировать процесс сборки, но он владеет немного PHP, предложили ему следующий код для сборки.

Файл содержащий список IP адресов у нас выглядит так

192.168.0.2,ivanova.a
192.168.0.4,ivanova.b
192.168.0.10,bubin.k
...

Далее код PHP с коментариями

<?php

//Получаем контент из файла в массиве
$result = file("import.csv", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
//запишем комманды которые необходимо выполнить на маршрутизаторе
$filecmd = "rscommand.txt";
//путь к исходникам
$spath="/Users/kirillvasilev/go/src/knockkme";
$rules=array();
$template_nat="/ip firewwall nat add chain=dstnat src-address-list=RDP/%s protocol=tcp port=3389 action=dst-nat to-addresses=%s comment=%s";
$template_raw="/ip firewwall raw add chain=RDP protocol=tcp dst-port=%s action=add-src-to-address-list address-list=RDP/%s address-list-timeout=120 comment=%s";

//укажите путь к компилятору go, путь куда сохронять и откуда брать исходники
$build = "GOOS=windows GOARCH=386 /usr/local/Cellar/go/1.13.4/libexec/bin/go build -o /Users/kirillvasilev/Downloads/knockkme/rdpHelper_%s.exe $spath";

//обходим циклом весь массив! $key номер строки, $value строка
foreach ($result as $key => $value) {
    //переменная содержит номер порта, который вычисляется из номера строки
    $port=10000+$key;
    // переменная str содержит контент для файла v.go в котором храниться глобальная переменная послежнего порта в Knock
    $str = <<<EOD
package main
var Lastkey = $port
EOD;

    //разбиваем строку на массив с помощью разделителя запятая
    $explode_str =explode(",",$value); 

    // если второго (начиная от нуля) индекса в массиве не существует, то формат неверный, сразу пропускаем итерацию
    if (!isset($explode_str[1])){ 
         continue;
    }
    //Меняем v.go
    file_put_contents("$spath/v.go",$str);
    // ip адрес
    //непроверяем на синтаксис IP похорошему надо, но не в этот раз, процедура разовая
    $ip = $explode_str[0]; 
    //имя пользователя
    $username = $explode_str[1];
    //комментарий для правил
    $comment=$ip."_".$username;
    //генерируем строки для экcпорта
    $rules[]=sprintf($template_nat,$ip,$ip,$comment);
    $rules[]=sprintf($template_raw,$port,$ip,$comment);
    //запускаем билд
    exec(sprintf($build,$port));
}
    file_put_contents($filecmd,"");
foreach ($rules as $vl){
    file_put_contents($filecmd,$vl."\n",FILE_APPEND);
}

На выходе у нас будет столько бинарниковы сколько правильных строк в csv файле, также файл с командами которые необходимы выполнить консоле для того чтобы правила заработали.

/ip firewwall nat add chain=dstnat src-address-list=RDP/192.168.0.2 protocol=tcp port=3389 action=dst-nat to-addresses=192.168.0.2 comment=192.168.0.2_ivanova.a
/ip firewwall raw add chain=RDP protocol=tcp dst-port=10000 action=add-src-to-address-list address-list=RDP/192.168.0.2 address-list-timeout=120 comment=192.168.0.2_ivanova.a
/ip firewwall nat add chain=dstnat src-address-list=RDP/192.168.0.4 protocol=tcp port=3389 action=dst-nat to-addresses=192.168.0.4 comment=192.168.0.4_ivanova.b
/ip firewwall raw add chain=RDP protocol=tcp dst-port=10001 action=add-src-to-address-list address-list=RDP/192.168.0.4 address-list-timeout=120 comment=192.168.0.4_ivanova.b
/ip firewwall nat add chain=dstnat src-address-list=RDP/192.168.0.10 protocol=tcp port=3389 action=dst-nat to-addresses=192.168.0.10 comment=192.168.0.10_bubin.k
/ip firewwall raw add chain=RDP protocol=tcp dst-port=10002 action=add-src-to-address-list address-list=RDP/192.168.0.10 address-list-timeout=120 comment=192.168.0.10_bubin.k

Собственно на этом всё!! Отдаём клиенту бинарник и файлик rdp, который смотрит на IP MikroTIk порт по умолчанию.

Да я знаю, что это неправильно, что логин можно получить в открытом виде, а точнее первые 9 символов, но мы выполняли требования заказчика.

Заказчик предупреждён, дальше решение только за ним.

Да костыль..., Нат это вообще один большой костыль.

Рассказать друзьям

Чатик телеграм

@mikrotikme