Ежедневные, еженедельные и ежемесячные резервные копии(бэкапы) на FTP

Выкладываю написанный для конкретных целей perl-скрипт делающий резервные копии на FTP по следующему алгоритму

  1. В определённое время суток делаем архив указанных каталогов
  2. В определённый день недели копируем ежедневный архив, делая из него еженедельный
  3. В определённый день месяца копируем ежедневный архив, делая з него ежемесячный
  4. Сравниваем состав локального хранилища и хранилища на ФТП, по необходимости закачивая на FTP отсутствующие архивы

Continue reading


Backup(бэкап) на FTP(фтп)

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

Вот пример его конфига

% backupper config
%
% i H d M D prefix backupped_path tmp_folder storage_path
%

00 22 * * * fast_mysql /var/backup /tmp agava4***:***@host.ru

00 23 * * * etc /etc /tmp agava4***:***@host.ruu
01 23 * * * usr_local_bin /usr/local/bin /tmp agava4***:***@host.ru
10 23 * * * home_0 /home/0* /tmp agava4***:***@host.ru
20 23 * * * home_1 /home/1* /tmp agava4***:***@host.ru
50 23  * * * home_2 /home/2* /tmp agava4***:***@host.ru
55 23  * * * home_3 /home/3* /tmp agava4***:***@host.ru
15 00  * * * home_4 /home/4* /tmp agava4***:***@host.ru
20 00  * * * home_5 /home/5* /tmp agava4***:***@host.ru
25 00  * * * home_6 /home/6* /tmp agava4***:***@host.ru
45 00  * * * home_7 /home/7* /tmp agava4***:***@host.ru
50 00  * * * home_8 /home/8* /tmp agava4***:***@host.ru
00 01   * * * home_9 /home/9* /tmp agava4***:***@host.ru

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

Вот сам скрипт. Для его работы потребуется консольная программа ftp. Скрипт надо повесить на cron ежеминутно.

#!/usr/bin/perl

$GZIP_PATH = "/bin/gzip";
$TAR_PATH = "/bin/tar";
$FTP_PATH = "/usr/bin/ftp";
$CONFIG_FILE = "/etc/backuper.conf";
$LOG_FILE = "/var/log/backuper.log";
$RM_PATH = "/bin/rm";

open(A, $CONFIG_FILE);
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
READCONFIG: while (<A>) {
    ($c_minute, $c_hour, $c_day, $c_mon, $c_wday, $backup_prefix,
        $backuped_path, $tmp_folder, $storage_path) =
        split /\s+/, $_;
    next READCONFIG if $c_minute=~/\%/ || !$c_minute;
#    print $fields[0]."\n";
    if(
        ($c_minute == $min      || $c_minute eq "*")   &&
        ($c_hour == $hour       || $c_hour eq "*")   &&
        ($mday == $c_day        || $c_day eq "*")
    ){
        backup($backup_prefix, $backuped_path, $tmp_folder, $storage_path,
            #"-".($year+1900)."-".$mon."-".$mday."_".$hour."_".$min
            ""
            );
    }
}
close(A);

sub backup{
    my ($backup_prefix, $backuped_path, $tmp_folder, $storage_path, $suffix) = @_;

    open(B, ">>".$LOG_FILE);
    print B get_date_log()."Start backup $backuped_path(".get_avg().")\n";
    if(get_avg()>1){
        print B get_date_log()."High load avarage. Stopped.\n";
        $command = 'ps -A|grep "[1-9]\.[0-9]"';
        $ps = `$command`;
        print B "\n$ps\n";
        close(B);
        return 0;
    }
    $command = "$TAR_PATH -cf $tmp_folder/$backup_prefix$suffix.tar $backuped_path";
    `$command`;
    print B get_date_log()."Creating $tmp_folder/$backup_prefix$suffix.tar completed(".get_avg().")\n";
    $command = "$GZIP_PATH -9 $tmp_folder/$backup_prefix$suffix.tar";
    `$command`;
    print B get_date_log()."Creating $tmp_folder/$backup_prefix$suffix.tar.gz completed(".get_avg().")\n";
    return 0 if (!$storage_path=~/([a-z0-9\-\.]+):([a-z0-9\-\.]+)\@([a-z0-9\-\.]+)\/([a-z0-9\-\.\_\/]+)/ig);
    my ($username, $password, $host, @path) = split /[\@\:\/]/,$storage_path;$path = join "/", @path;
    $command = "/usr/bin/ftp -i -n <<EOF
open $host
user $username $password
dele $path/$backup_prefix$suffix.tar.gz
put $tmp_folder/$backup_prefix$suffix.tar.gz $path/$backup_prefix$suffix.tar.gz
quit
EOF";
    `$command`;
    print B get_date_log()."Uploading $tmp_folder/$backup_prefix$suffix.tar.gz completed(".get_avg().")\n";
    `$RM_PATH $tmp_folder/$backup_prefix$suffix.tar.gz`;
    print B get_date_log()."Removing $tmp_folder/$backup_prefix$suffix.tar.gz completed(".get_avg().")\n";
    close(B);
}

sub get_date_log{
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    return "[".(1900+$year)."-".$mon."-".$mday." ".$hour.":".$min.":".$sec."]";
}

sub get_avg(){
    $avg=`uptime`;
    $avg=~s/^.*?average.*?:\s*(.*?)[^\d\.\,]+.*/$1/gi;
    $avg=~s/\,/\./gi;
    chomp($avg);
    return $avg;
}

Псевдо-ftp-сервер(ftp-прокси). Лучший способ не выдать пароля — не знать пароля

Предыстория создания

В начале 2007 года в сложилась довольно неприятная ситуация. На продвигаемых сайтах завелся вирус.

После анализа ситуации стал более-менее понятен алгоритм его работы

  1. при заходе на зараженный сайт, содержащий невидимый фрэйм, браузером посылался http-запрос по адресу, указанному в этом фрейме. Страница ответа на запрос содержала вредоносный код, внедряющий через дыру в internetexplorer троянца.
  2. в процессе своей работы троянец регулярно посылает найденные ftp-пароли налево (тут наши мнения разделились — одни утверждали, что троянец мониторит трафик и крадёт передаваемые в незашифрованном виде пароли прямо из уходящих в сеть данных, другие считали, что он просто читает файлы популярных ftp-менеджеров, таких как fartotal commander и пр.)
  3. некий скрип, используя полученные от троянца пароли заражает сайт, через внедрение во всё, имя чего начинается на index. невидимого фрейма со ссылкой на вредоносную страницу.

Проблема решается в 3 этапа:

  1. удаление вредоносного кода со страниц сайта;
  2. смена аттрибутов ftp-доступа;
  3. чистка компьютера несколькими антивирусами, как показывает практика, ни один из существующих антивирусов не обеспечивает 100% результата.

Однако всё гораздо сложнее, если ftp-доступ имеют несколько пользователей с разных компьютеров под управлением ОС windows. После того как проблема была решена в краткосрочном периоде(чистка, смена паролей), была начата работа по выработке методик и разработке средств не позволяющих данной ситуации проявиться вновь.

Во весь рост встала проблема утечки паролей. На тот момент была распространена практика выдачи настоящих реквизитов доступа ftp всем кто работает с сайтом. В силу огромного количества поддерживаемых сайтов, большого разнообразия хостеров (а стало быть и панелей управления) достаточная частая смена паролей была невозможна. Было решено сократить число лиц, соприкосающихся с базой паролей вплоть до того, чтобы непосредственно работающие с сайтом люди их не знали.

Основная проблема, которая и привела к столь сложному решению — невозможность гибко настраивать FTP-серверы.

Механизм взаимодействия ftp-клиента и ftp-сервера

Прежде всего, хочу порекомендовать две довольно приличные статьи про FTP протокол Протокол пересылки файлов FTP Служба FTP. Протокол FTP. Протокол TFTP.

Как известно ftp-протокол использует 2 сокетных соединения, образующих 2 «канала» передачи — канал передачи команд и канал передачи данных.

Канал передачи команд

ftp-сервер, как правило использует 21-порт для приёма подключений клиентов. При подключении он начинает принимать команды от клиента и возвращать ответы. Авторизация так же производится через него.

Канал передачи данных

На какой из сторон будет приниматься подключение для образования этого канала клиент и сервер решают через канал передачи команд — договариваются о режиме передачи и о том какой ip/port для этого ипользовать. При пассивном режиме (есть ещё активный, но он не будет рассматриваться) серверный сокет открывается на ftp-сервере и принимает подключение клиента.

Авторизация

Практически весь процесс авторизации проходит через канал передачи команд.

Вот пример диалога сервера и клиента После подключения по 21-му порту

>>>220 proftpd 1.2.10 server (proftpd default installation) [127.0.0.1]
<<<user r-asian
>>>331 password required for r-asian.
<<<pass xxxx
>>>530 login incorrect.

Как видно, в данном случае авторизация не удалась по причине неверного пароля. А вот пример удачной авторизации:

>>220 proftpd 1.2.10 server (proftpd default installation) [127.0.0.1]
<<<user r-asian
>>>331 password required for r-asian.
<<<pass xxxx
>>>230 user r-asian logged in.
<<<syst
>>>215 unix type: l8
<<<type i
>>>200 type set to i
<<<pwd
>>>257 "/home/r-asian" is current directory.
<<<pasv
>>>227 entering passive mode (127,0,0,1,128,5).
<<<list -al
>>>150 opening ascii mode data connection for file list
>>>226 transfer complete.

Общая идея псевдо-ftp

ftp — клиент авторизуется, передав по каналу передачи данных имя пользователя и пароль. Теперь поставим посредника. При авторизации он принимает от ftp-клиента фальшивые имя пользователя и пароль, находит в своей БД соответствующие им настоящие реквизиты ftp и производит авторизацию на ftp-сервере с последующим открытием сеанса связи. Далее все команды клиента и ответы сервера ретранслируются.

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

Что такое dummy-ftp

dummy-ftp — ftp-прокси, выполняющий авторизацию на удалённом ftp-сервере, заменяя фальшивый пароль, передаваемый ftp-клиентом на реальный и далее ретранслирующий команды и ответы.

На данный момент он представляет из себя скрипт, написанный на языке perl. Первоначально предполагалось на Perl написать некий прототип системы и после тестирования переписать на C. Однако в таком виде он успешно работает почти год (в немного модифицированном виде:там все логи хранятся в БД) и от затеи его облагородить мы почти окончательно отказались.