Fork me on GitHub

The WebDevil

Enjoy development

Server moving adventures

August 7th, 2008

Второй день занимаюсь переездом содержимого одного сервера на другой. Другой – VPS под FreeBSD (привет, ДЦ Воля).

В общем, это последний раз когда я до оплаты сказал что он неплох. Теперь только реальные сервера. Ну и может VDS под Linux… В общем, именно эта реализация ужасна. Меня мало интересует как и что – факт налицо.
Приключения только начались…

В PowerDNS есть пара плюшек, которые я раньше не замечал. Одну из них – записи “URL” я сегодня попробовал, настроил и остался вполне доволен.

Что же такое URL-запись? Это фича PowerDNS, при помощи которой можно делать редирект домена на заданный URL.
Например, запись выглядит как

some.domain.com URL http://other.domain.com/some_domain_com

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

...
fancy-records=yes
urlredirector=77.120.99.180
...

Когда приходит запрос к DNS’у для домена some.domain.com отдается 77.120.99.180, указанный в конфиге, как если бы это была обычная “А”-запись.
Браузер отрезолвив имя идет на http://some.domain.com. На этом адресе висит http-сервер (я повесил lighttpd), у которого скрипт лезет в БД и для указанного домена ищет соответствующую ему URL-запись и редиректит браузер туда. Все просто и тривиально.

Для lighttpd у меня конфиг дополнился такими строками:

$SERVER["socket"] == "77.120.99.180:80" {
    $HTTP["host"] =~ "(.*)" {
        magnet.attract-raw-url-to = ("/var/www/lighttpd/pdns-url-redirector.lua")
    }
}

То есть все что приходит незамедлительно обрабатывается упомянутым lua-скриптом. Почему lua? С ним работает mod_magnet, и мне было интересно написать этот скриптик. Вот он:

require "luasql.mysql"
env = assert (luasql.mysql())
con = assert (env:connect("dnsdb", "dns_user", "dns_pass_for_readonly_records_table", "localhost"))
domain = lighty.request["Host"]

mysql_replacements = {
  ["\0"] = "\\0",
  ["\n"] = "\\n",
  ["\r"] = "\\r",
  ["\'"] = "\\'",
  ["\""] = "\\\"",
  ["\026"] = "\\Z",
  ["\b"] = "\\b",
  ["\t"] = "\\t",
}

function fixsql (s)
  return (string.gsub (tostring (s), "[%z\n\r\'\"\026\b\t]",
    function (str)
        return mysql_replacements [str] or str
    end ))
end -- fixsql

cur = assert (con:execute( string.format("SELECT content FROM records WHERE `type` = 'URL' AND `name` = '%s'", fixsql(domain) ) ) )
result = cur:fetch()
if result then
    redirect_to = string.format("%s", result)
else
    redirect_to = "http://daemons.org.ua/"
end

cur:close()
con:close()
env:close()

--lighty.content = { string.format("SELECT content FROM records WHERE `type` = 'URL' AND `name` = '%s'", fixsql(domain) )  }
--lighty.header["Content-Type"] = "text/plain"
--return 200

lighty.header["Location"] = redirect_to
return 301

Комментарий начинается с “–”. Закоментарены строки для отладки скрипта.
Для соединения с мускулем из lua доставил пакет liblua5.1-sql-mysql-2.

Скрипт писал долго, но это было интересно. И небесполезно =)


UPD: во избежание DoS/DDoS атаки на мускуль возможны два варианта апгрейда метода:
1. По крону раз в 5 минут генерить sqlite-БД состоящую из одной таблицы из двух полей – domain, target
2. Раз в 5 минут генерить сам скрипт с вынесенными в него в виде хеша записями. Тогда скрипт вообще никуда подглядывать не будет.

Lighttpd and execwrap

July 24th, 2008

В качестве вэб-сервера я использую lighttpd. И сегодня я наткнулся на старую проблему – вэб-сервер работает из-под пользователя www-data, и когда пользователь pupkin загружает какой-то файл – этот файл принадлежит не pupkin’у, а www-data. И через ftp удалить его он не может, и вынужден искать некие web-file-manager’ы.

Так вот я погуглил и наткнулся на execwrap. Он очень мелкий, почти ничего ему не нужно стороннего для работы, конфигурировать и использовать его крайне просто (да-да, я знаю про suExec, но он мне не сильно понравился).

В результате конфиг для сайта у меня выглядит так:

$HTTP["host"] == "pupkin.com" {
    server.name = "pupkin.com"
    site-root = "/home/pupkin/www/" + server.name
    server.document-root = site-root + "/www"
    accesslog.filename = site-root + "/log/access.log"
    setenv.add-environment = ( "AWSTATS_FORCE_CONFIG" => server.name )
    auth.backend.htpasswd.userfile = site-root + "/awstats.passwd"
   
    fastcgi.server = ( ".php" => ((
        "bin-path" => "/var/www/lighttpd/execwrap",
        "socket" => "/tmp/" + server.name + ".phpsocket",
        "max-procs" => 10,
        "bin-environment" => (
            "UID" => "9999",
            "GID" => "9999",
            "TARGET" => site-root + "/cgi-bin/php",
            "CHECK_GID" => "1",
            "CGI_BIN_DIR" => site-root + "/cgi-bin/",
            "PHP_FCGI_CHILDREN" => "2",
            "PHP_FCGI_MAX_REQUESTS" => "5000"
        )
    )))
}

где 9999 – ID пользователя и группы (у меня они часто совпадают).

А в директории /home/pupkin/www/pupkin.com/cgi-bin лежит два файла – симлинк на php.ini и исполняемый файлик “php” следующего содержания:

#!/bin/bash

cd $CGI_BIN_DIR
exec /usr/bin/php-cgi -c ./php.ini

Как видите, если надо то конфиг php.ini можно пользователю задать персональный. А файлик php обязательно должен принадлежать этому же пользователю.

Когда все сделано – рестартуем lighttpd и смотрим, нет ли в логах ругательств с кодом ошибки 22 – это, как видно из сорса execwrap, несоответствие прав.

Enjoy =)

UPD: execwrap умер и воскрес тут: http://cgit.stbuehler.de/gitosis/execwrap/

Redmine mail checks

July 15th, 2008

Используемая мною система Redmine обзавелась полезной фичей – она теперь может забирать различным образом почту и из нее создавать новые тикеты или добавлять ответы к уже существующим.

Более подробно об этом тут:
http://www.redmine.org/wiki/1/RedmineReceivingEmails

В моем случае по крону срабатывает rake-task забирающий почту по IMAP.

Буквально перед этим посмотрел еще раз на Trac – наконец-то он разродился версией 0.11. И что-то он мне совсем разонравился – авторизация как была так и осталась по http (что совсем не труъ), настраивается он весьма неудобно, если не сказать больше.

Для меня в роли багтракера прочно утвердился Redmine.

Migration to PowerDNS

July 11th, 2008

Наверняка многие видели сообщение об уязвимости в DNS. Во многих дистрибах линукса весьма скоро появились секьюрити-патчи, которые эту уязвимость исправляли. Уязвимость, может, и не слишком уж страшная, но она напомнила мне о PowerDNS, который я с интересом рассматривал в начале 2008го года.

Расматривал я его в основном из-за родной интеграции с MySQL. Мне весьма понравилось как она реализована. Однако у меня были уже рабочие конфиги BIND9, и я просто ленился испытать PowerDNS на собственной шкуре.

А зря боялся, как оказалось. Упомянутая уязвимость не задела PowerDNS, и этим мое внимание было вновь привлечено к нему. И переход таки состоялся.
Подробнее…

Protecting SSH

July 2nd, 2008

Я пытался использовать fail2ban под Linux для отстрела ботов. Но через некоторое время он у меня отказался работать. Почему – не знаю, причины я особо не искал. И вчера заменил его на другое решение – sshguard.

Первое впечатление – не слишком хорошее, потому что надо было все настраивать руками. Но немного разобравшись, быстро поставил его на все свои линукс-сервера.

Итак, что потребуется: Ubuntu вместе с UFW, syslog-ng.

apt-get install sshguard

Примечание: на данный момент у меня версия 1.0 которая в пакетах отказалась нормально парсить логи. Потому берем исходники версии 1.1, ./configure –with-firewall=iptables и после сборки стопаем syslog-ng, заменяем /usr/sbin/sshguard новым бинарником (он будет раза в 4 больше) и запускаем syslog-ng вновь.

и в /etc/syslog-ng/syslog-ng.conf дописать в конец примерно следующее:

destination sshguardproc {
    program("/usr/sbin/sshguard"
        template("$DATE $FULLHOST $MESSAGE\n"));
};
# filter LOG_AUTH messages which also contain "ssh"
filter authssh {
        facility(auth, authpriv);
};
# copy filtered logs into the sshguard process
log {
        source(s_all);
        filter(authssh);
        destination(sshguardproc);
};

Это будет передавать все сообщения об аутентификации sshguard’у. Он попытается их блокировать путем создания правил iptables. Однако имея ufw пришлось несколько адаптировать указанный в документации метод.
/etc/ufw/before.rules, сразу же под # End required lines добавил

:sshguard - [0:0]

и далее в списке правил

...
-A ufw-before-input -p tcp --dport 22 -j sshguard
#ufw-not-local
...

Так все на порт 22 будет проходить через эту цепочку. И в ней же sshguard будет отстреливать вредителей.

Аналогичную процедуру делаем и для before6.rules с учетом именования цепочек там.

Для тех кто еще не запускал ufw следует прописать политики по умолчанию в /etc/default/ufw и выполнить

ufw enable
ufw logging off

и после всего

/etc/init.d/syslog-ng reload

Enjoy =)

Bzr rocks

July 2nd, 2008

С первого знакомства мне очень понравился bazaar-ng. И вот почему.

Он нацелен на usability. И это видно сразу. Вот наверняка многие пользовались TortoiseSVN. Там все делается просто, легко и удобно, правда? А вот теперь пойдем в консоль удаленного сервера. И добавим маски для игнорирования файлов (логи, темпы, кеши всякие). А теперь еще добавим в репозитарий те два десятка сорсов в разных папках, которые написали/докопировали в проект.
Все svn это не проблематично, но несколько неудобно. В bzr же все это делается легким движением руки – правим .bzrignore в корне проекта добавляя маски, потом жмем bzr add && bzr commit.

Bzr ориентирован на ветки. И у меня получается ветка == проект. Я выгружаю себе его целиком. В результате в каждой папке нету папок .svn, есть лишь одна папка .bzr в корне проекта. Мне очень редко требовалось делать чекаут определенной папки из проекта.

Проект часто требуется обновить до того, что живет в репозитории. Bzr имеет т.н. легковесные чекауты, которые не содержат подробной истории изменений. Как-раз то, что чаще всего нужно на рабочем сервере.

Также весьма порадовала только-что сложившаяся ситуация: я делал commit, и внезапно пропал линк. bzr стоял и терпеливо ждал, пока я ребутнул модем. Как только пинги появились, он все быстро завершил. Ни обрывов, ни страшных ругательств небыло.

Что касается скорости работы – она вполне приемлема. Да, есть системы и быстрее, но bzr меня вполне устраивает. Кроме того, мне абсолютно неважно, будет коммит выполняться 5 или 10 секунд, или даже 20 – я же не собираюсь безостановочно коммитить.

Навеяно впечатлениями о Git.

Ruby fixed in Ubuntu

June 27th, 2008

Наконец-то в Ubuntu полечили уязвимость в Ruby, обновление доступно из репозитария security.ubuntu.com.

Провериться можно выполнив (хоть в irb) код:

ary = []; ary[0x7fffffff] = "A"

Тут больше и подробнее

Для меня давно уже возникала необходимость подружить сервер с двумя провайдерами и локальной сетью. И вот вчера проблема стала весьма актуальной.

Итак, имеем:
eth0 – 192.168.0.0/24 – LAN
eth1 – 13.13.13.13/24 – ISP#1
eth2 – 6.6.6.6/24 – ISP#2

ISP#2 отдает IP через DHCP
ISP#1 – статика (на самом деле DHCP но удалось договориться на статику)
LAN – его надо NAT’ить в мир через ISP#1 (кое-что можно отправлять и через ISP#2, это будет показано).

Так как при использовании dhclient сетевые настройки не заменялись получеными мы это запретим в файле /etc/dhcp3/dhclient.conf:

request subnet-mask, broadcast-address, time-offset, routers;

Отлично. Теперь мы получим лишь IP и шлюз. Теперь в файле /etc/iproute2/rt_tables добавим таблицу маршрутизации ISP2:

2       ISP2

Теперь надо заносить получаемые значения шлюза в эту таблицу, и определить маршрут, по которому пакеты будут попадать в эту таблицу. Для этого напишем два скрипта:
/etc/dhcp3/dhclient-enter-hooks.d/duallink

if [ x$reason == 'xBOUND' ]; then
        /sbin/ip route flush table ISP2
        /sbin/iptables -F PREROUTING -t mangle
        /sbin/ip route flush cache
        ip rule show | grep ISP2 | while read -r line ; do    
            ip rule del `echo $line | cut -d ':' -f2`
        done

        prefix=`echo $new_ip_address | cut -d . -f 1-2`
        if [ x$prefix == 'x192.168' ]; then
                exit
        fi

        #ISP1
        /sbin/ip route add 13.13.13.13/24 dev eth1 table ISP2
        /sbin/ip route add 172.16.0.0/16 dev eth1 table ISP2
        #LAN
        /sbin/ip route add 192.168.0.0/24 dev eth0 table ISP2

        #ISP2
        /sbin/ip rule add from $new_ip_address table ISP2
        /sbin/ip rule add fwmark 0x2 table ISP2

        #А тут можно пометить пакеты, которые уйдут через ISP2
        /sbin/iptables -I PREROUTING -t mangle -i eth0 -p tcp --dport 443 -j MARK --set-mark 2
        /sbin/iptables -I PREROUTING -t mangle -i eth0 -p tcp --dport 80  -j MARK --set-mark 2
fi

isp2_gateway=$new_routers
new_routers=""

/etc/dhcp3/dhclient-exit-hooks.d/duallink

if [ x$reason == 'xBOUND' ]; then
        my_new_network=`ipcalc -n $new_ip_address/$new_subnet_mask | grep Network | cut -b 12-32`
        /sbin/ip route add $my_new_network dev $interface table ISP2
        /sbin/ip route add default via $isp2_gateway table ISP2
fi

Да, не забудьте установить ipcalc!

Собственно, все. Теперь осталось воткнуть ISP#2 и посмотреть на получившуюся картину командами

ip rule show
ip route show
ip route show table ISP2

статья собрана по материалам
Configuring several Internet connections on the same Computer
Rules – routing policy database
Appendix D. IP Route Management
и других перелистаных в процессе отладки.

На убунтовской вики упоминается /usr/local/sbin/flush_rules.pl, но что внутри этого скрипта – автор умолчал.

Все замечания приветствуются!

FAM and Gamin

March 13th, 2008

Вчера потратил немного времени и произвел скрипт-демон для отлова изменений в указанной папке.
Но вот работает связка только с FAM. А в Linux сейчас уже используется более продвинутый Gamin. Как подружить Ruby с Gamin?
Read the rest of this entry »