Удаленное управление iptables (OpenWRT)

У меня есть 25 (скоро будет 100+) узлов (OpenWRT) одноранговая ячеистая сеть L2 (batman-adv) с 4 узлами шлюза. Каждый узел имеет беспроводной интерфейс 5 ГГц и 2,4 ГГц. 2.4 предназначен для клиентского доступа, а сетка работает на интерфейсе 5 ГГц.

Все узлы практически идентичны (программное обеспечение). Широковещательный доступ GW через Интернет (Ethernet) к ячеистой сети, и пара узлов имеют порт LAN (Ethernet), подключенный к одному серверу, предоставляющему услуги DHCP, SQL и http для ячеистой сети. По дизайну нет ограничений относительно того, какие узлы физически предоставляют доступ к Интернету или к серверу DCHP/SQL/HTTP (т.е. совместное размещение не является вариантом дизайна).

В настоящее время беспроводные клиенты имеют доступ к портам WAN GW без ограничений. Следующим этапом проекта является ограничение доступа к интерфейсу WAN на основе информации об учетной записи, размещенной на сервере локальной сети (т. е. информации об учетной записи в базе данных).

Что я хотел бы сделать, так это удаленно манипулировать iptables узлов GW для управления доступом в Интернет на основе информации с серверов, но я не уверен, что это лучший способ получить команды iptables для GW. Моей первой мыслью было выполнять пакетные команды через SSH или потоковые команды для клиента SSH. Я мог бы также написать свой собственный простой сервер TCP/IP. Вероятно, есть и модель RPC.

Есть ли рекомендуемый метод, учитывая вышеизложенное или плюсы и минусы, которые я должен рассмотреть. Спасибо.

РЕДАКТИРОВАТЬ: блокирует ли iptables одновременные вызовы или пользователю необходимо сериализовать использование?


person Donald Chisholm    schedule 26.09.2013    source источник


Ответы (2)


Я предполагаю, что у вас будет одинаковая политика на всех шлюзах. Вероятно, вы поддерживаете политику централизованно. В этом случае я бы просто построил политику в формате «iptables-restore» на каком-то централизованном сервере и выполнил бы одну единственную команду на каждом GW. Возможно, вы захотите выполнить эту команду по защищенному каналу. Я бы просто использовал scp + ssh.

Это точно так же, как вы предложили, за исключением замены полной таблицы новой таблицей. Добавление одного правила за раз может привести к некоторой несогласованности в политике и может привести к появлению дыр. Кроме того, синхронизация всех GW может быть сложной задачей в среде с потерями. Отсюда и стресс на iptables-restore.

person Rajesh Mohan    schedule 27.09.2013
comment
Извините за задержку с ответом. Я думал использовать iptables только для того, чтобы добавить исключения в правило перенаправления для тех, у кого есть учетные данные для доступа в Интернет. Есть ли смысл использовать iptables-restore для небольших изменений в таблицах? т.е. добавить/удалить одно правило MAC? - person Donald Chisholm; 01.10.2013
comment
Если это только одно правило, то неупорядоченные обновления не являются проблемой. Вам просто нужно убедиться, что вы не создаете повторяющиеся записи из-за неудачной/неправильной связи. Чтобы обойти это, я каждый раз создавал цепочку, удалял существующую и добавлял вновь созданную. Поскольку вы сказали, что распределены и много узлов, я просто ищу последовательный способ сделать это. - person Rajesh Mohan; 04.10.2013
comment
Отличная мысль по созданию цепочки. Я пытался выяснить, как я могу обеспечить синхронизацию базы данных и iptables для узлов шлюза. Если я вас правильно понимаю, вы думаете, что создание цепочки в качестве оболочки для правила iptables вызовет исключение, если цепочка уже существует? Значительно ли увеличивает требования к памяти или нагрузку поддержание связи «один к одному» в цепочке для каждого правила? - person Donald Chisholm; 04.10.2013
comment
Что касается моего редактирования, вы случайно не знаете, блокирует ли iptables одновременные вызовы? - person Donald Chisholm; 04.10.2013

В итоге я остановился на использовании php-ssh2, php-mysqli и комбинации iptables и ipset под OpenWRT (пакеты ipset, iptables-mod-ipset и kmod-ipt-ipset).

Я создал ipset, содержащий IP-адреса, которым разрешен полный доступ в Интернет:

ipset create ip_whitelist hash:ip

В OpenWRT я настроил iptables для перенаправления любого http-трафика трафика на мой интернет-сервер, если исходный IP-адрес отсутствует в ip_whitelist:

iptables --table nat --new prerouting_mychain
iptables --table nat --insert PREROUTING -j prerouting_mychain
iptables --table nat --append prerouting_mychain --match set --match-set ip_whitelist src -j RETURN
iptables --table nat --append prerouting_mychain --dport 80 -j DNAT --to-destination <internal http server>

Теперь это будет обрабатывать перенаправление для IP-адресов, не входящих в ipset, но нам все еще нужно блокировать другой несанкционированный трафик. Делается это в таблице фильтров цепочки FORWARD:

iptables --table filter --new forward_mychain
iptables --table filter --insert FORWARD -j forward_mychain
iptables --table filter --insert forward_mychain -j DROP (this ends up being the last rule)
iptables --table filter --insert forward_mychain --match set --match-set ip_whitelist src -j ACCEPT

В моем случае я хочу всегда разрешать DNS, чтобы браузер не зависал при поиске:

iptables --table filter --insert forward_mychain -p udp --dport 53 -j ACCEPT
iptables --table filter --insert forward_mychain -p udp --sport 53 -j ACCEPT

Теперь цепочка FORWARD настроена на разрешение трафика только с IP-адресов в ipset и трафике DNS.

Авторизованные IP-адреса добавляются в ipset:

ipset add ip_whitelist 10.10.10.10

и удалено с помощью:

ipset del ip_whitelist 10.10.10.10

В моем случае я просто установил срок действия записи, чтобы принудительно перенаправить обратно на внутренний сайт для проверки базы данных:

ipset add ip_whitelist 10.10.10.10 timeout 3600 (секунд)

Этот тайм-аут также можно установить по умолчанию при первом создании ipset.

Все, что осталось сделать, это создать php-страницу для перенаправленного http-трафика, запросить базу данных и отправить команды ipset на шлюзовые маршрутизаторы.

Сначала моя простая база данных:

CREATE DATABASE Testdb; 
CREATE TABLE `Clients` (   
`IP` varchar(15) DEFAULT NULL,   
`Created` datetime DEFAULT NULL,   
`Expiry` datetime DEFAULT NULL,   
`LastAccess` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,   UNIQUE KEY `ipidx` (`IP`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

И redirect.php:

<!DOCTYPE html >
<html>
   <head>
       <title></title>
   </head>
   <body>
       <?php
       $ClientIPAddr = filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP);
       $sqlQuery = "";
       if (($ClientIPAddr === FALSE) || ($ClientIPAddr === NULL)) {
           echo "Failed to obtain client IP [" . $_SERVER["REMOTE_ADDR"] . "]";
           exit;
       }

Запрос/вставка в базу данных:

       $mysqli = new mysqli("<database ip>", "dbuser", "dbpass", "Testdb");
       if ($mysqli->connect_errno) {
           echo "Failed to connect to MySQL: " . $mysqli->connect_error;
           exit;
       } else {
           echo "Connected (" . $mysqli->server_info . ")<br />";
       }
       echo "Now for a query on " . $ClientIPAddr . ":<br />";
       // Check to see if IP is in database and has not expired
       $sqlQuery = "SELECT IP, TIMEDIFF(Expiry, now()) from Clients where IP=\"" . $ClientIPAddr . "\";";
       if (($result = $mysqli->query($sqlQuery)) === FALSE) {
           echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
           exit;
       } else {
           echo "<h2>Query Result(" . $result->num_rows . "):</h2>";
           echo "<table>";
           while (($row = $result->fetch_array(MYSQLI_NUM)) !== NULL) {
               echo "<tr>";
               foreach ($row as $value) {
                   echo "<td>" . $value . "</td>";
               }
               echo "</tr>";
           }
           echo "</table>";
           echo "<h1>Welcome to my Test Page</h1>";
           if ($result->num_rows === 0) {
               echo "<p>New(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
               echo "<p>You now have full Internet access.</p>";
           } else {
               echo "<p>Returning(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
               echo "<p>Your Internet access has been extended.</p>";
           }
           $result->free();
           $sqlQuery = "INSERT INTO Clients (IP, Created, Expiry) VALUES (\"" . $ClientIPAddr . "\", now(),
               timestampadd(hour, 24, now())) ON DUPLICATE KEY UPDATE Expiry=timestampadd(hour, 24, now());";
           if (($result = $mysqli->query($sqlQuery)) === FALSE) {
               echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
               exit;
           } else {

Добавьте IP в ipset для авторизации доступа в интернет:

               // **************** UPDATE IPSET *****************
               //ssh2_connect, ssh2_fingerprint, ssh2_auth_pubkey_file, ssh2_auth_password, ssh2_exec
               if (($sshConnect = ssh2_connect("<GW IP>")) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (ssh2_auth_pubkey_file($sshConnect, "root", "/usr/share/nginx/rsa.pub", "/usr/share/nginx/rsa") === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (($stream = ssh2_exec($sshConnect, "ipset add ip_whitelist " . $ClientIPAddr)) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               $stderr_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
               if ((stream_set_blocking($stream, true) === FALSE) ||
                       (stream_set_blocking($stderr_stream, true) === FALSE)) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (($resultStr = stream_get_contents($stream)) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if ($resultStr === "") { // Likely an error
                   if (($resultStr = stream_get_contents($stderr_stream)) === FALSE) {
                       $err = error_get_last();
                       echo $err["message"];
                       exit;
                   }
               }
               echo "<pre>" . $resultStr . "</pre>";
           }
       }
       $mysqli->close();
       ?>
   </body>
</html>

Как видите, это очень грубо. Однако он включает все функции, которые мне нужны. Я надеюсь, что это оказалось полезным для кого-то еще.

person Donald Chisholm    schedule 26.10.2013