После перевода Zabbix-агентов в активный режим появилась другая маета (-: или муда в терминологии кайдзен) - бывает нужно вносить в сетевой фильтр IP-адреса сети, в которых есть активные Zabbix-агенты. До поры до времени это требовалось делать очень редко. Потом сеть стала расти очень быстро и вносить новые IP-адреса и сети в сетевой фильтр стало нужно с завидной регулярностью. С одной стороны, чтобы сэкономить время, можно добавлять сразу целые сети. С другой стороны - в Zabbix нет никаких средств защиты от подделки данных: протокол позволяет запросить конфигурацию любого Zabbix-агента, указав его имя, и отправить в Zabbix данные от имени любого другого Zabbix-агента. Сервер Zabbix не имеет даже средств для определения конфликтующих Zabbix-агентов, которые работают на разных компьютерах, но имеют одно и то же сетевое имя, отправляя поочерёдно разные данные.
Чтобы автоматизировать процесс добавления IP-адресов в сетевой фильтр на сервере Zabbix, а также максимально снизить возможность отправки поддельных данных с любого свободного IP-адреса, решил написать скрипт, который будет извлекать из базы данных Zabbix список IP-адресов интерфейсов из тех сетевых узлов, на которых есть элементы данных, имеющие тип "Zabbix-агент (активный)".
Для Linux с его iptables и ipset получился такой скрипт под названием ipset_auto.sh, который можно поместить в планировщик задач cron:
#!/bin/sh
AWK="/usr/bin/awk"
SORT="/usr/bin/sort"
UNIQ="/usr/bin/uniq"
IPSET="/sbin/ipset"
XARGS="/usr/bin/xargs"
update()
{
SET="$1"
NEED_IPS="$2"
CURRENT_IPS=`$IPSET list $SET | $AWK '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ { print $0; }'`
DIFF_IPS=`(echo "$NEED_IPS" ; echo -n "$CURRENT_IPS") | $SORT | $UNIQ -u`
ADD_IPS=`(echo "$NEED_IPS" ; echo -n "$DIFF_IPS") | $SORT | $UNIQ -d`
DEL_IPS=`(echo "$CURRENT_IPS" ; echo -n "$DIFF_IPS") | $SORT | $UNIQ -d`
if [ -n "$ADD_IPS" ]
then
echo "--- $SET add ---"
echo "$ADD_IPS"
echo "$ADD_IPS" | $XARGS -n1 $IPSET add $SET
fi
if [ -n "$DEL_IPS" ]
then
echo "--- $SET del ---"
echo "$DEL_IPS"
echo "$DEL_IPS" | $XARGS -n1 $IPSET del $SET
fi
}
# ZABBIX
MYSQL=`$AWK '/^DBUser=/ { split($0, a, "=");
user = a[2]; }
/^DBPassword=/ { split($0, a, "=");
password = a[2]; }
/^DBName=/ { split($0, a, "=");
db = a[2]; }
/^DBHost=/ { split($0, a, "=");
host = a[2]; }
END { if (user && password && host && db)
print "/usr/bin/mysql --connect-timeout=5 -u" user " -p" password " -h" host " " db;
else if (user && password && db)
print "/usr/bin/mysql --connect-timeout=5 -u" user " -p" password " " db; }' /etc/zabbix/zabbix_server.conf`
if [ -z "$MYSQL" ]
then
echo "MYSQL not defined"
exit
fi
NEED_IPS=`$MYSQL -N <<END 2>/dev/null
SELECT DISTINCT interface.ip
FROM items
JOIN hosts ON hosts.hostid = items.hostid
AND hosts.status = 0
AND hosts.proxy_hostid IS NULL
JOIN interface ON interface.hostid = items.hostid
AND interface.type = 1
AND interface.ip <> '127.0.0.1'
WHERE items.type = 7
AND items.status = 0;
END
`
ERROR=$?
if [ $ERROR -ne 0 ]
then
echo "Failed to execute SQL-query"
exit
fi
update "zabbix_auto" "$NEED_IPS"Для подключения к базе данных (в данном случае это MySQL, но переделка под другие СУБД тривиальна) скрипт использует настройки из файла конфигурации /etc/zabbix/zabbix_server.conf. Список требуемых IP-адресов в переменной NEED_IPS формируется SQL-запросом, который можно переработать под свои нужды. Например, у меня в скрипте есть ещё пара SQL-запросов, управляющих списками IP-адресов в множествах tftp_auto и ciu_auto. В последней строке скрипта функция update обновляет множество zabbix_auto так, чтобы в нём были только IP-адреса из переменной NEED_IPS.Для создания множества IP-адресов zabbix_auto в ipset можно воспользоваться командой:
# ipset create zabbix_auto hash:ipДля создания правила в iptables, которое разрешит всем IP-адресам из множества zabbix_auto взаимодействовать с сервером Zabbix, можно воспользоваться командой:
# iptables -A INPUT -p tcp -m set --match-set zabbix_auto src -m tcp --dport 10051 -j ACCEPTАналогичный скрипт для ipfw/table называется ipfw_auto.sh и выглядит следующим образом:
#!/bin/sh
AWK="/usr/bin/awk"
SED="/usr/bin/sed"
SORT="/usr/bin/sort"
UNIQ="/usr/bin/uniq"
XARGS="/usr/bin/xargs"
update()
{
TABLE="$1"
NEED_IPS="$2"
IPFW=`$AWK -v TABLE="$TABLE" '{ split($0, a, "=");
if (a[1] == TABLE)
{
table = a[2];
print "/sbin/ipfw table " a[2];
}
}' /etc/firewall.conf`
if [ -z "$IPFW" ]
then
echo "IPFW not defined"
exit
fi
CURRENT_IPS=`$IPFW list | $SED -e 's/\/32 0$//'`
DIFF_IPS=`(echo "$NEED_IPS" ; echo -n "$CURRENT_IPS") | $SORT | $UNIQ -u`
ADD_IPS=`(echo "$NEED_IPS" ; echo -n "$DIFF_IPS") | $SORT | $UNIQ -d`
DEL_IPS=`(echo "$CURRENT_IPS" ; echo -n "$DIFF_IPS") | $SORT | $UNIQ -d`
if [ -n "$ADD_IPS" ]
then
echo "--- $TABLE add ---"
echo "$ADD_IPS"
echo "$ADD_IPS" | $XARGS -n1 $IPFW add
fi
if [ -n "$DEL_IPS" ]
then
echo "--- $TABLE del ---"
echo "$DEL_IPS"
echo "$DEL_IPS" | $XARGS -n1 $IPFW delete
fi
}
MYSQL=`$AWK '/^DBUser=/ { split($0, a, "=");
user = a[2]; }
/^DBPassword=/ { split($0, a, "=");
password = a[2]; }
/^DBName=/ { split($0, a, "=");
db = a[2]; }
/^DBHost=/ { split($0, a, "=");
host = a[2]; }
END { if (user && password && host && db)
print "/usr/local/bin/mysql --connect-timeout=5 -u" user " -p" password " -h" host " " db;
else if (user && password && db)
print "/usr/local/bin/mysql --connect-timeout=5 -u" user " -p" password " " db; }' /usr/local/etc/zabbix34/zabbix_server.conf`
if [ -z "$MYSQL" ]
then
echo "MYSQL not defined"
exit
fi
# ZABBIX
NEED_IPS=`$MYSQL -N <<END 2>/dev/null
SELECT DISTINCT interface.ip
FROM items
JOIN hosts ON hosts.hostid = items.hostid
AND hosts.status = 0
AND hosts.proxy_hostid IS NULL
JOIN interface ON interface.hostid = items.hostid
AND interface.type = 1
AND interface.ip <> '127.0.0.1'
WHERE items.type = 7
AND items.status = 0;
END
`
ERROR=$?
if [ $ERROR -ne 0 ]
then
echo "Failed to execute SQL-query"
exit
fi
update "table_zabbix_auto" "$NEED_IPS"Особенность этого скрипта заключается в том, что в ipfw таблицы не имеют имён, а нумеруются. Номер таблицы выясняется через файл /etc/firewall.conf, в котором переменной с именем таблицы присваивается соответствующий номер. Например, для таблицы table_ssh номер задаётся следующим образом:table_ssh=100
Подробнее о настройке ipfw/table можно прочитать в одной из моих прошлых заметок: Настройка ipfw во FreeBSD.
Активные Zabbix-агенты и база данных Zabbix приведены для примера, а вообще эти скрипты можно приспособить для любых других целей. Можно скачивать список IP-адресов с веб-страницы (главное, чтобы её не подменили и чтобы она не оказалась внезапно пустой), можно воспользоваться в каком-нибудь самодельном биллинге для открытия доступа пользователям, прошедшим авторизацию и закрытия доступа пользователям, превысившим лимит. Можно сочетать одно с другим.
FreeBSD на работе постепенно заменяем на Debian, поэтому скрипт ipfw_auto.sh скоро станет мне не нужным. Что касается Debian, то netfilter/iptables в Debian Buster уже заменён на nftables/nft. Пока что утилита iptables никуда не делась и умеет работать с nftables, но в будущем скрипт ipset_auto.sh тоже утратит актуальность и потребует переработки. Оба скрипта, однако, пока что могут пригодиться кому-нибудь ещё, поэтому решил поделиться ими.
Комментариев нет:
Отправить комментарий