Рейтинг@Mail.ru

Наши друзья и партнеры

купить дешевый 
компьютер родом из Dhgate.com



  • Opi купить
  • Купить прибыльный бизнес! Без комиссий! Бизнес от собственника! Звоните
  • cosmeticstar.ru

Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Безопасность работы с системой Linux
[Photo of the Author]
автор Erdal Mutlu
<erdal(at)linuxfocus.org>

Об авторе:

Erdal - один из турецких редакторов LF. В настоящее время он работает системным администратором в Linotype Library. Будучи большим фанатом Linux с университетских лет, он любит работать и заниматься разработками в этой среде.



Перевод на Русский:
Eugene S. Saenko <caspar(at)pisem.net>

Содержание:

 

Автоматизация системного администрирования с помощью ssh и scp

[Illustration]

Резюме:

Если Вам приходится администрировать большое количество Linux/Unix систем, Вам наверняка нужны скрипты для автоматизации некоторых процессов. В процессе каждодневной работы Вы должны были заметить, что Вам приходится выполнять одни и те же, или похожие действия на каждой системе. Возможно Вы задумывались о способах автоматизации этих процессов. Это особенно верно при обслуживании большого количества одинаково сконфигурированных Linux/Unix систем. В этой статье я покажу, как это можно сделать с помощью утилит ssh.

_________________ _________________ _________________

 

Введение

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

Чтобы воспользоваться этой статьей необходимо понимание основ программирования в оболочке. Информацию о программировании в оболочке см. в статье Кати и Гвидо Сочер (Katja and Guido Socher) в LinuxFocus Shell Programming. Вам, также, потребуется знание утилит ssh, таких как ssh-keygen, ssh-add, ssh, scp или sftp. Имеется свободная реализация протокола SSH под Linux: OpenSSH, содержащая все эти инструменты. Имеются и страницы руководства (man pages).

 

Почему ssh?

Вопросом на вопрос: А почему нет? Можно использовать rsh-rcp или telnet-ftp, но они не подходят для небезопасной среды, такой, как Интернет, а, возможно, и интранет. Ssh обеспечивает защищенную шифрованную связь двух компьютеров в незащищенной сети. Я не собираюсь обсуждать вопросы безопасности при использовании этих инструментов. Прочтите статью Джорджа Тарбурича (Georges Tarbouriech) Through the tunnel (Сквозь туннель).
В действительности я, в прошлом, использовал скрипты на основе telnet/ftp.  

Копирование файлов и каталогов с помощью scp

Чтобы скопировать один файл из локального каталога на удаленный компьютер можно воспользоваться командой:
scp /path/to/the/file/file1 user@remote_host:/remotedir/newfile
В этом примере файл file1 копируется из локального каталога на удаленный компьютер (remote_host может быть или IP или имя удаленного компьютера) в каталог /remotedir под новым именем newfile. Выдается приглашение к аутентификации под именем 'user'. В случае удачной аутентификации и если пользователь имеет соответствующие права, файл будет скопирован. Можно не задавать новое имя файла. В этом случае файл будет скопирован под старым именем. Короче, это означает, что при копировании файл можно переименовать.
Обратная операция тоже возможна: можно скопировать файл с удаленного компьютера в локальный каталог:
scp user@remote_host:/remotedir/file /path/to/local/folder/newfile
Имеется, также, очень удобный вариант команды scp. Указав ключ '-r' Вы можете копировать каталоги рекурсивно.
scp -r user@remote_host:/remotedir .
Эта команда копирует каталог 'remotedir' и все его подкаталоги и файлы с удаленного компьютера в текущий рабочий каталог под тем же именем.

Примечание: Предполагается, что на удаленном компьютере выполняется демон sshd.

 

Удаленная регистрация с помощью ssh

Вместо rlogin или telnet можно воспользоваться более безопасным способом - ssh:
ssh erdal@helvetica.fonts.de
В зависимости от конфигурации Вашей системы Вас попросят ввести или пароль, или парольную фразу. В этом примере мы подключаемся к компьютеру helvetica.fonts.de под именем удаленного пользователя erdal. Команда ssh имеет множество опций, которые можно использовать в соответствии с Вашими потребностями. Просмотрите страницы руководства ssh.  

Исполнение команд с ssh

Имеется возможность с помощью ssh исполнять на удаленном компьютере команды:
ssh erdal@helvetica.fonts.de df -H
Это очень похоже на синтаксис удаленной регистрации. Различия начинаются после имени компьютера. Выдается команда (в этом примере 'df -H') для исполнения на удаленном компьютере. Вывод команды будет отображен на Вашем терминале.  

Подключение к удаленному компьютеру без пароля

Вместо использования аутентификации по паролю можно использовать пару ключей (публичный/частный). Вам надо сгенерировать пару ключей. Есть утилита ssh-keygen, которую можно использовать для генерации ключей для ssh:
ssh-keygen -b 1024 -t dsa
У Вас будет запрошено имя приватного ключа. Имя публичного ключа обычно то же, что и приватного ключа с добавкой '.pub'. Здесь '-b 1024' это количество бит создаваемого ключа. Если это значение не указано, будет использовано значение по умолчанию. '-t dsa' задает тип создаваемого ключа. Возможные значения: 'rsa1' для протокола версии 1 и 'rsa' или 'dsa' для протокола версии 2. Я бы рекомендовал использование версии 2 протокола SSH. Но если у Вас есть старые серверы, поддерживающие только протокол версии 1, Вам надо указать '-t rsa1' для создания другой пары ключей. Указав '-1' или '-2' Вы можете заставить ssh протокол версии 1 или 2 соответственно.

Чтобы воспользоваться ключом, Вам надо установить свой публичный ключ на удаленный компьютер. Содержимое файла публичного ключа должно быть скопировано или добавлено в файл
$HOME/.ssh/authorized_keys или $HOME/.ssh/authorized_keys2. Будьте внимательны и не смешивайте ключи для различных версий протокола. Для протокола версии 1 используется authorized_keys, а для протокола версии 2 используется authorized_keys2. Если Вы правильно установили свой публичный ключ, в первый же раз, когда Вы подключитесь к этому компьютеру, будет запрошена Ваша парольная фраза (passphrase), а в случае отказа, будет запрошен пароль удаленного пользователя. Вы можете ограничить подключение к Вашей системе только использованием аутентификации по публичному ключу, отредактировав конфигурационный файл sshd. Имя файла
/etc/ssh/sshd_config а наименование параметра, который Вам надо изменить 'PasswordAuthentication'. Замените этот параметр на no (PasswordAuthentication no) и перезапустите sshd.

До этого момента все хорошо. У нас есть безопасный способ копирования и выполнения команд на удаленных системах. Но автоматизируя некоторые задачи мы не должны вводить ни паролей, ни парольных фраз. Иначе мы ничего не сможем автоматизировать. Конечно можно было бы вписать в каждый скрипт требуемые пароли, но это не очень хорошая идея. Лучше поручить агенту ключей (key-agent) взять на себя заботу о наших парольных фразах. ssh-agent - это программа, предназначенная для хранения приватных ключей, используемых для аутентификации по публичному ключу. Надо запустить агент ключей:

ssh-agent $BASH
и добавить Ваши приватные ключи, которые следует использовать
ssh-add .ssh/id_dsa
или
ssh-add .ssh/identity
id_dsa - это файл приватного ключа DSA, а identity - файл приватного ключа RSA1. Это - имена файлов по умолчанию, данные при генерации ключей с помощью ssh-keygen. Конечно, у Вас будет запрошена парольная фраза, прежде чем ssh-add добавит Ваш ключ к агенту ключей. Список добавленных ключей можно получить с помощью команды:
ssh-add -l

Теперь при подключении к серверу, имеющему Ваш ключ в авторизованном файле, Вам не придется вводить вообще ничего! О процессе аутентификации позаботится ssh-agent.

При использовании ssh-agent описанным выше способом, им можно пользоваться только с терминала, в котором он был запущен. Чтобы использовать ssh-agent с любого открываемого Вами терминала придется потрудиться немного больше. Я написал такой небольшой скрипт для запуска агента:

#!/bin/sh
#
# Erdal mutlu
#
# Запуск ssh-agent для использования в пакетных заданиях.

agent_info_file=~/.ssh/agent_info

if [ -f $agent_info_file ]; then
 echo "Agent info file : $agent_info_file exists."
 echo "make sure that no ssh-agent is running and then delete this file."
 exit 1
fi

ssh-agent | head -2 > $agent_info_file
chmod 600 $agent_info_file
exit 0


Приведенный выше скрипт проверяет наличие файла agent_info в домашнем каталоге пользователя, в котором обычно расположены ssh файлы пользователя. В нашем случае это каталог '.ssh/'. Если файл существует, пользователю выдается предупреждение о существовании файла и краткая инструкция о том, что нужно предпринять. Если у пользователя не запущен ssh-agent, он должен удалить файл agent_info и перезапустить скрипт. Скрипт запускает ssh-agent и включает первые две строки в файл agent_info. Эта информация, в дальнейшем, будет использоваться утилитами ssh. Следующая строка используется для изменения прав доступа к файлу таким образом, что только владелец файла может читать его и писать в него.

Если агент запущен и выполняется Вы можете добавлять к нему свои ключи. Но перед этим Вы должны выполнить файл agent_info, чтобы утилиты ssh знали, где расположен Ваш агент:

source ~/.ssh/agent_info или . ~/.ssh/agent_info

И добавить свои ключи с помощью ssh-add. Можно добавить в файл .bashrc несколько строк так, чтобы каждый раз при открытии нового терминала выполнялся файл agent_info:

if [ -f .ssh/agent_info ]; then
. .ssh/agent_info
fi

ВНИМАНИЕ: Вы должны обезопасить компьютер, на котором используете ssh-agent и скрипт автоматизации, который я здесь опишу. Иначе, если кто-нибудь получит доступ к Вашей учетной записи, он получит доступ ко всем серверам, с которыми Вы общаетесь с помощью ключей ssh. За все приходится платить!

 

Скрипт

Теперь пора объяснить, как мы собираемся автоматизировать некоторые из задач системного администратора. Идея состоит в том, чтобы выполнить некоторое множество команд на определенном множестве хостов и скопировать некоторые файлы на или с этих хостов. Это то, чем часто приходится заниматься системным администраторам. Вот этот скрипт:

#!/bin/sh

# Установка с использованием Secure SHELL и агента SSH
# Erdal MUTLU
# 11.03.2001


##################################################################
#                     Функции
##################################################################
### Копирование файлов с одного компьютера на другой
copy_files()
{
 if [ $files_file != "files_empty.txt" ];then
  cat $files_file | grep -v "#" | while read -r line
  do
   direction=`echo ${line} | cut -d " " -f 1`
   file1=`echo ${line}     | cut -d " " -f 2`
   file2=`echo ${line}     | cut -d " " -f 3`

   case ${direction} in
      "l2r") :	### С локального компьютера на удаленный
          echo "$file1  --> ${host}:${file2}"
          scp $file1 root@${host}:${file2}
      ;;
      "r2l") :	### С удаленного компьютера на локальный
          echo "${host}:${file2}  --> localhost:${file2}"
          scp root@${host}:${file1} ${file2}
      ;;
      *)
          echo "Неизвестно направление копирования : ${direction}"
          echo "Должно быть local или remote."
      ;;
   esac
  done
 fi
}

### Выполнение команд на удаленных компьютерах
execute_commands()
{
 if [ $commands_file != "commands_empty.txt" ];then
  cat $commands_file | grep -v "#" |  while read -r line
  do
   command_str="${line}"
   echo "Выполняю $command_str ..."
   ssh -x -a root@${host}  ${command_str} &
   wait $!
   echo "Выполнено $command_str OK."
  done
 fi
}

### Функция-оболочка (wrapper function) для функций execute_commands и copy_files
doit()
{
 cat $host_file | grep -v "#" | while read -r host
 do
  echo "host=$host processing..."
  case "${mode}" in
	"1")
		copy_files
		execute_commands
		;;
	"2")
		execute_commands
		copy_files
		;;
	*)
		echo "$0 : Неизвестный режим : ${mode}"
		;;
  esac
  echo "host=$host ok."
  echo "------------------------------------------------------------------"
 done
}

##################################################################
### Программа начинается здесь
##################################################################

if [ $# -ne 4 ]; then
 echo "Использование : $0 mode host_file files_file commands_file"
 echo ""
 echo "режим 1 или 2 "
 echo "    1 : сначала копировать файлы, потом выполнять команды."
 echo "    2 : сначала выполнять команды, потом копировать файлы."
 echo "Если файл files.txt называется files_empty.txt то он не обрабатывается."
 echo "Если файл commands.txt называется commands_empty.txt то он не обрабатывается."
 exit
fi


mode=$1
host_file=$2
files_file=$3
commands_file=$4

agent_info_file=~/.ssh/agent_info
if [ -f $agent_info_file ]; then
 . $agent_info_file
fi

if [ ! -f $host_file ]; then
 echo "Файл хостов : $host_file не существует!"
 exit 1
fi

if [ $files_file != "files_empty.txt" -a ! -f $files_file ]; then
  echo  "Файл файлов : $files_file не существует!"
  exit 1
fi

if [ $commands_file != "commands_empty.txt" -a ! -f $commands_file ]; then
 echo  "Файл команд : $commands_file не существует!"
 exit 1
fi

#### Все выполняется здесь
doit

Давайте запишем скрипт ainstal.sh (automated installation -- автоматизированная установка) и попытаемся выполнить его без параметров. Мы получим сообщение:

./ainstall.sh
Использование : ./ainstall.sh mode host_file files_file commands_file

режим 1 или 2
      1 : сначала копировать файлы, потом выполнять команды.
      2 : сначала выполнять команды, потом копировать файлы.
Если файл files.txt называется files_empty.txt то он не обрабатывается.
Если файл commands.txt называется commands_empty.txt то он не обрабатывается.

В соответствии с этим сообщением, если Вы не хотите выполнять команды, дайте аргументу commands.txt имя commands_empty.txt, а если не хотите пересылать никаких файлов, дайте аргументу files_file имя files_empty.txt. Иногда, ведь, нужно только выполнить несколько команд, а в других случаях только переслать несколько файлов.

Перед тем, как дать построчное объяснение скрипта, дадим пример его использования: Предположим, Вы добавили в сеть вторичный DNS сервер и хотели бы добавить его в файл /etc/resolv.conf. Для простоты предположим, что на всех Ваших компьютерах одинаковый файл resolv.conf. В этом случае единственное, что Вам надо сделать, это скопировать новый файл resolv.conf на все компьютеры.
Первое, что Вам потребуется - это список хостов. Запишем их все в файл hosts.txt. Формат этого файла такой, что каждая строка содержит имя или IP только одного хоста. Вот пример:

#############################################################################
#### Каждая строка содержит одно имя или IP адрес хоста. Строки,
#### начинающиеся со знака # или его содержащие игнорируются.
#############################################################################
helvetica.fonts.de
optima.fonts.de
zaphino
vectora
#10.10.10.162
10.10.10.106
193.103.125.43
10.53.103.120

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

  • С локального хоста на хосты, перечисленные в файле hosts.txt. Это тот тип передачи, который нам нужен.
  • С каждого из хостов, перечисленных в файле hosts.txt на локальный хост. Это в том случае, когда нам надо получить некоторые файлы с каждого из хостов. Например, при использовании простого скрипта резервного копирования, который я опишу ниже в этой статье.

Файлы, которые надо передать, перечислены в другом файле. Давайте назовем этот файл files_file.txt. Формат files_file.txt такой: Каждая строка содержит информацию о копировании только одного файла. Возможны два направления копирования: l2r (local to remote - с локального на удаленный) и r2l (remote to local - с удаленного на локальный). l2r - это когда файл копируется с локального хоста на удаленный. r2l - когда файл с удаленного хоста копируется на локальный. После ключевого слова направления следуют два имени файла. Поля разделяются пробелами или табуляциями. Первый файл копируется во второй в соответствии с ключевым словом направления. Имя файла для удаленного хоста должно быть полностью квалифицированным, иначе файл будет скопирован в домашний каталог пользователя root. Вот наш files_file.txt :

############################################################################
# Структура файла такова :
# - Значения полей : l2r (localhost to remote - с локального хоста на удаленный)
# и r2l (remote computer to local - с удаленного компьютера на локальный).
#       r2l  file1   file2
#         означает копирование файла file1 с удаленного (хосты перечислены в
#         файле hosts.txt) компьютера на локальный под именем file2.
#       l2r     file1   file2
#         означает копирование файла file1 с локального хоста на
#         удаленный (хосты перечислены в файле hosts.txt) компьютер под
#         именем file2
#         file1 и file2 - это файлы на соответствующих хостах.
#
#       Примечание: порядок указания local и remote указывает направление
#       процесса копирования.
############################################################################
l2r     resolv.conf     /etc/resolv.conf

Как видите, я включил в файл описание его структуры. Обычно я включаю это описание в каждый файл files_file.txt, который использую. Это простое, но хорошее решение для документирования. В этом примере мы хотим скопировать файл resolv.conf на удаленный компьютер под именем /etc/resolv.conf. В демонстрационных целях после копирования файла на целевые компьютеры я добавил команды смены владельца и группы файла и отображения содержимого /etc/resolv.conf. Команды, которые надо выполнить, помещаются в отдельный файл. Давайте назовем его commands_file.txt. Вот наш файл commands_file.txt:

###########################################################################
# Структура этого файла : Каждая строка содержит команду, которую надо
# выполнить. Каждая команда рассматривается отдельно.
###########################################################################
chown root.root /etc/resolv.conf
chmod 644 /etc/resolv.conf
cat /etc/resolv.conf

Файл команд содержит команды, которые должны быть выполнены на каждом компьютере, перечисленном в файле hosts.txt. Команды выполняются последовательно, это значит, что сначала выполняется первая команда, затем вторая и так далее.

Хорошо, теперь у нас есть все файлы, необходимые для этого простого примера. Единственное, что осталось, это указать режим, который определяет, какой из двух файлов: commands_file.txt или files_file.txt должен обрабатываться в первую очередь. Можно передать файлы, перечисленные в файле files_file.txt file, а затем выполнить все команды на целевом компьютере, это режим 1. Или наоборот, выполнить команды, а затем передать файлы, это режим 2. Теперь можно выполнить скрипт с необходимыми аргументами, вот так:

./ainstall.sh 1 hosts.txt files_file.txt commands_file.txt

Небольшой совет: обычно имя файла files.txt я начинаю с files_ и добавляю короткое описательное имя, например, files_resolvconf.txt. Подобным же образом я называю hosts.txt и commands.txt.

Теперь пришло время поговорить подробнее о самом скрипте. При запуске программа проверяет количество аргументов и, если оно не равно четырем, выдает сообщение об использовании. При правильном количестве аргументов их значения присваиваются соответствующим переменным. Затем, если существует файл '~/.ssh/agent_info', он выполняется. Этот файл содержит информацию о запущенном у Вас агенте ssh. Если Вы не пользуетесь агентом, Вам придется ввести пароль или парольную фразу вручную, что означает никакой автоматики:). Затем каждый файл (hosts, files и commands) проверяется на существование. Есть, также, специальная проверка на files_empty.txt и commands_empty.txt. Если Вы указали такое имя, то проверка на существование не нужна. Я изменил эту часть скрипта при написании этой статьи. Раньше было только:

if [ -f $host_file -a -f $files_file -a -f $commands_file ]; then
 echo "$host_file $files_file $commands_file"
 doit
else
 echo "$host_file или $files_file или $commands_file не существует"
 exit
fi

В этом случае у меня должны были быть файлы с именами: files_empty.txt и commands_empty.txt. Но с этим не было проблем, поскольку я всегда работал только в одном каталоге.
В конце производится вызов функции 'doit'. Эта функция управляет всем. В этой функции имеется цикл с командами 'cat' и 'while', который для каждого из хостов, перечисленных в '$hosts_file', вызывает, в соответствии с 'mode', функции copy_files и execute_commands. Таким образом обрабатывается каждый хост в задании. Переменная 'host' содержит имя или IP адрес текущего хоста.
Давайте рассмотрим функцию copy_files. Эта функция сначала проверяет, имеет ли файл файлов имя 'files_empty.txt'. Если да, то ничего не делается. Если нет, то в каждой строке '$files_file' переменные 'direction', 'file1' и 'file2' содержат направление копирования, имена первого и второго файла, соответственно. Копирование производится с помощью scp в соответствии с переменной 'direction'.
И, наконец, давайте посмотрим, как работает функция execute_commands. Эта функция сначала проверяет, имеет ли файл команд имя 'commands_empty.txt'. Если да, то ничего не делается. Если нет, то каждая из команд в '$commands_file' выполняется с помощью ssh на удаленном компьютере в фоновом режиме. После вызова команды ssh вызывается команда wait с параметром '$!'. Эта команда обеспечивает то, что все команды выполняются строго друг за другом. Вместо параметра '$!' подставляется идентификатор процесса последней команды, выполненной в фоновом режиме.
Вот так-то. Просто, не правда ли?

 

Простое резервирование конфигурационных файлов

Здесь представлено более сложное использование этого скрипта. Идея состоит в том, чтобы сделать резервные копии конфигурационных файлов всех Ваших компьютеров или серверов. С этой целью я написал небольшой скрипт, использующий ainstall.sh :

#!/bin/sh

server_dir=${HOME}/erdal/sh/ServerBackups

if [ ! -d $server_dir ]; then
 echo "Каталог : $server_dir не существует."
 exit 1
fi

cd $server_dir

servers=ll_servers.txt
prog=${HOME}/erdal/sh/einstall_sa.sh

cat $servers | grep -v "#" | while read -r host
do
  echo $host > host.txt
  $prog 1 host.txt files_empty.txt
  servers/${host}/commands_make_backup.txt
  $prog 1 host.txt files_getbackup.txt commands_empty.txt
  mv -f backup.tgz servers/${host}/backup/`date +%Y%m%d`.tgz
  rm -f host.txt
done

exit 0

У Вас должен быть создан специальный каталог под именем servers. В этом каталоге должны быть два файла: files_getbackup.txt и ll_servers.txt. Вот файл 'files_getbackup.txt' :

r2l /root/backup.tgz backup.tgz

'll_servers.txt' содержит имена или IP хостов, которые необходимо резервировать. Каждому хосту, перечисленному в файле 'll_servers.txt', должен соответствовать каталог с таким же именем, в котором должен быть файл commands_make_backups.txt, содержащий команды для создания архива /root/backup.tgz из конфигурационных файлов этого хоста. И каталог под именем backup. Все резервные файлы этого хоста будут храниться в этом каталоге. Файл ll_servers.txt содержит:

fileserver
dbserver
10.10.10.1
appserver

структура подкаталогов каталога '$servers' должна быть такой:

servers
|-- files_getbackup.txt
|-- ll_servers.txt
|-- make_server_backups.sh
|-- 10.10.10.1
|   |-- backup
|   `-- commands_make_backup.txt
|-- appserver
|   |-- backup
|   `-- commands_make_backup.txt
|-- dbserver
|   |-- backup
|   `-- commands_make_backup.txt
|-- fileserver
    |-- backup
    `-- commands_make_backup.txt

А вот несколько примеров файлов commands_make_backups.txt:

tar cfz /root/backup.tgz /etc/samba /etc/atalk /etc/named.conf /var/named/zones

Этот файл commands_make_backup.txt используется для резервирования конфигурационных файлов samba, atalk и nameserver и файлов зон.

tar cfz /root/backup.tgz /etc/httpd /usr/local/apache

Этот файл commands_make_backup.txt используется для резервирования конфигурации и файлов сервера apache.

tar cfz /root/backup.tgz /etc/squid /etc/named.conf

Этот файл commands_make_backup.txt используется для резервирования конфигурации прокси-сервера squid и вторичного сервера DNS.

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

 

Заключение

Скрипт ainstall.sh позволяет Вам автоматизировать некоторые задачи системного администрирования. Он основан на простом использовании утилит ssh (ssh tools). Вы оцените этот скрипт, если у Вас имеется большое количество идентичных систем.  

Ссылки

  • SSH, The Secure Shell: The Definitive Guide, by Daniel J. Barrett and Richard Silverman.
  • Through the tunnel, by Georges Tarbouriech.
  • Shell Programming, by Katja and Guido Socher.

Если вам понравилась статья, поделитесь ею с друзьями: