Предисловие
Итак, представим ситуацию — у вас большая инфраструктура и есть ряд серверов, доступ к управлению которыми вы должны предоставлять извне. Скажем — несколько Дата Центров, облачных площадок и любых других приватных сетей, внутри которых есть работающие сервера, к которым должны подключаться Ваши админы, разработчики, тестировщики.
Другая ситуация — Вы один администратор в компании с несколькими площадками (офис, несколько серверов в ЦОДе, удаленная производственная площадка)
Что их объединяет? Необходимо предоставить удаленный, удобный и безопасный доступ к этим самым серверам ( и другому оборудованию) по различному набору протоколов — SSH, RDP, VNC, HTTP и еще много чего. Что в такой ситуации обычно предлагается сделать? — Да, поднять vpn сервер. Что обычно подразумевает под собой что нам нужно следить еще за одной службой — устанавливать ее, настраивать, разбираться в ее тонкостях, логировании и т.д. Если вы используете централизованное управление пользователями, как например Microsoft Active Directory, необходимо позаботиться о том, чтобы выбранное Вами vpn решение имело возможность интеграции.
Ну и наконец последнее — давайте вспомним, что означает буква N в слове VPN. N это Networks — вам необходимо предусмотреть выделенное IP пространство под VPN тунели, заниматься поддержкой маршрутизации. Все эти пункты по отдельности не кажутся страшными, но собираясь вместе — нарастают как страшный снежный ком.
Эта статья посвящена тому, какое альтернативное решение можно использовать, не неся подобных технических затрат.
Обращаю Ваше внимание — это не критика VPN как решения, это лишь попытка показать более простую альтернативу.
Приступим….
До фортификации
Представим следующую картину — есть ряд удаленных площадок (т.е. доступ к ним идет не по локальной сети, не по частным каналам связи, а через публичную сеть Интернет — и не важно, находится эта площадка в другой стране или в соседнем здании), на которых размещены группы серверов, доступ к которым нужен как Вам, так и еще кому то — вашим коллегам например. В таком случае, эти сервера должны быть размещены в DMZ (демилитаризованной зоне), за корпоративным фаерволом, но с возможностью достучаться до них. Как это выглядит в жизни? Как то вот так:
Вот так примерно выглядит ситуация — каждый сервер торчит портом доступа во внешнюю сеть и вынужден отбиваться сам по себе от атак и не легитимных подключений. Вам нужно следить, чтобы все они были обновлены, проверять на них логи, мониторить нездоровую активность.
Хочется остановить все это и спрятать их всех за прочную стену. Но как тогда обеспечить доступ? Нам нужны ворота, нам нужна башня, защищающая эти ворота и строгий привратник. Все это зовется просто — Бастион Сервер. Так же его порой называют Access gateway.
Проектируем нашу защиту
Сейчас я хочу показать, как такое решение можно создать самому, используя opensource инструменты на базе Linux и не прибегая к платным проприетарным решениям.
Суть решения заключается в том, что мы закрываем прямой доступ к любым хостам внутри нашей DMZ напрямую из вне и оставляем доступ только к этому серверу, уже с которого Вы можете получить доступ к внутренним инфраструктурным единицам.
Мы ставим 1 сервер, открываем наружу ( через корпоративный фаервол, или он сам может защитить себя своим фаерволом) 1 сервер, только ему даем доступ внутрь нашей DMZ. Что нам это дает:
- Active directory base аутентификация. SSH как сервис, нативно используем PAM для аутентфикации и авторизации пользователей. Соедините его через sssd и realmd с Active Directory — получите нативную аутентификацию через доменные учетные записи, получите разграничение по группам, возможность включать и выключать в AD пользователю возможность подключаться к нему ( а значит и через него) извне.
- Привычный в настройке и эксплуатации сервис. SSH — хорошо известный всем Linux ( да и не только Linux) администраторам сервис, привычный в настройке, с стойким шифрованием, способный пробрасывать порты и соединения, передавать файлы и многое другое!
- Легко доступные и настраиваемые средства защиты. Помимо встроенных в саму реализацию средств обеспечения безопасности ( минимальные привилегии, ограничение числа подключений, доступ по ключам, стойкое шифрование) существуют так же дополнительные меры которые крайне легко настроить — например тот же Fail2ban.
- Простота настройки доступа для нового сервера. Если раньше, чтобы обеспечить доступ извне к новому серверу, вам нужно было накрутить на нем методы безопасности, настроить правила фаервола для предоставления доступа извне, сейчас достаточно просто подключить интерфейс нового сервера в DMZ.
Можно произносить еще много интересных и красивых слов, но я думаю смысл понятен.
Строим
Так, наша задача:
- Централизованная аутентификация
- SSH харденинг
- Fail2ban
- Мониторинг
- Настройки внутреннего Firewall
Централизованная аутентификация
Используем для этого SSSD. Это пакет приложений для управления аутентификацией и авторизацией в операционных системах на базе Linux. SSSD является отличной альтернативой монструозной Samba, позволяя подключить Linux машину к уже имеющемуся домену Active Directory.
Эта система использует LDAP и Kerberos (собственно как и Windows) для того чтобы связываться с контроллером домена AD. Умеет матчить юзеров AD в линуксовы, автоматом создавать домашний каталог, подтягивать группы из AD и т.д.
Короче получаем легко настраиваемые идентификацию аутентификацию и авторизацию. Настраиваем…
Первым делом проверяем, что в качестве DNS у нас установлен домен контроллер. И в принципе контроллеры домена должны быть доступны по dns, ldap и kerberos портам с этой машины. В данном примере мы используем 1 DC.
1 2 3 4 5 |
cat /etc/resolv.conf . . . nameserver 10.0.1.20 options edns0 search example.com |
Устанавливаем реалм
1 |
apt-get install realmd |
Проверяем доступность домена
1 |
realm discover example.com --verbose |
В файл «/etc/realmd.conf» добавляем информацию об ОС
1 2 3 |
vim /etc/realmd.conf #добавляем os-name = Ubuntu Server os-version = 18.04.2 LTS (Bionic Beaver) |
Ставим все необходимое
1 |
apt install -y sssd-tools sssd libnss-sss libpam-sss adcli packagekit libsss-sudo krb5-user |
Вгоняем в домен
Указываем учетную запись любого пользователя из домена, не обязательно админа
1 |
realm join --verbose --user=domainadmin --user-principal="host/bastion.example.com@example.com" --computer-ou="OU=Services,OU=Servers,DC=example,DC=com" example.com |
-
user – логин доменного пользователя с правами, достаточными для ввода компьютера в домен AD. От имени этого пользователя будет выполняться присоединение компьютера к домену и модификация атрибутов доменной учётной записи компьютера. В классической ситуации — это пользователь с правами администратора домена, так как именно администраторы домена по умолчанию в AD могут обновлять значение атрибута servicePrincipalName. Однако я не очень люблю для «попсовых» задач использовать привилегии такого уровня, поэтому здесь я использую рядового доменного пользователя, у которого есть права на создание и модификацию объектов типа Computer в OU указанном в опции computer-ou;
-
user-principal – строка регистрации принципала Kerberos, которая будет использоваться для генерации keytab-файла и регистрации значений атрибута userPrincipalName в свойствах учётной записи компьютера в домене AD.
-
computer-ou – имя OU, в котором мы хотим создать учётную запись компьютера в домене. Если учётная запись уже существует, то она будет обновлена.
-
verbose – понятное дело, подразумевает расширенный вывод информации о всех происходящих действиях.
Все почти готово, остались последние шаги
Немного исправляем файл /etc/sssd/sssd.conf, приводя это к следующему виду:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[sssd] domains = example.com config_file_version = 2 services = nss, pam default_domain_suffix = example.com [domain/example.com] ad_domain = example.com krb5_realm = EXAMPLE.COM realmd_tags = manages-system joined-with-adcli cache_credentials = True id_provider = ad krb5_store_password_if_offline = True default_shell = /bin/bash ldap_id_mapping = True use_fully_qualified_names = True fallback_homedir = /home/%u default_shell = /bin/bash access_provider = simple simple_allow_groups = linux_users@example.com, linux_admins@example.com |
+ ставим права на файл иначе без этого не заведется
1 |
chmod 400 /etc/sssd/sssd.conf |
В каталоге /etc/sudoers.d/ создаем файл ( имя не важно но пусть будет linux_admin) и Добавляем туда разрешение тем, кто входим в группу linux_admins вызывать sudo
1 2 |
cat /etc/sudoers.d/linux_admin %linux_admins@example.com ALL=(ALL) ALL |
Чтобы при логине нового юзера у нас автоматически создавался домашний каталог, в файл /etc/pam.d/common-session в конец добавляем строчку
1 |
session optional pam_mkhomedir.so |
Все сохраняем и перезапускаем sssd:
1 2 |
service sssd restart service sssd status |
Как теперь разрешить доступ к серверам? В active directory у нас две группы:
-
linux_users
-
linux_admins
Будучи добавленным в любую из них, пользователь получает возможность логиниться на Linux сервер но только linux_admins может вызывать sudo
Усиление SSH
Так как это протокол у нас будет выступать основным каналом связи, не можем пройти мимо него.
Некоторый список best practices:
Но мне больше всего понравилась вот эта: https://www.atraining.ru/ssh-armoring/
В итоге у меня родился примерно следующий конфиг:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
################################################# # Managed by git # Please don't change anything manually # Kazarin K.A. 2019 ################################################ # # Сетевые настройки # # Включаем только протокол версии 2 Protocol 2 # указываем какой порт использовать Port 22 # параметр нужен чтобы на сетевом уровне определять отключившихся пользователей и прекращать их сеансы. # Выключение этого механизма, которое иногда рекомендуется “для экономии трафика” (слёзы, а не экономия) приведёт к ситуации, # когда ssh не сможет в ряде случаев понять, что пользователь не просто неактивен, # а уже никогда не сможет продолжить работу в данной сессии. Поэтому включаем. TCPKeepAlive yes # Разрешаем только IPv4 AddressFamily inet # отключает древний механизм создания списка узлов (обычно в файле.rhosts), с которых возможен вход без аутентификации. IgnoreRhosts yes # Опция нужна, чтобы отключить проверку PTR-записи у подключающегося абонента – помимо того, что во внутренней сети, # да и при подключении из внешних тоже наличие PTR совершенно необязательно, данная мера лишь замедляет подключение, # а уровень безопасности не поднимает – максимум, что делает – пишет в журнал warning про “подключающийся не сказал своё настоящее FQDN-имя”. UseDNS no # отключает механизм SSHFP, который практически не используется в сетях, а вот запросы на тему “а есть ли такая экзотическая запись в вашем DNS” – отправляются. # VerifyHostKeyDNS no # выключает поддержку роуминга – экспериментального расширения OpenSSH, которое должно было обрабатывать сценарии вида “начал админить из одного места, # потом перешёл в другое и продолжил оттуда”. По факту не работает # UseRoaming no # Параметр MaxAuthTries говорит, через сколько попыток неудачной аутентификации (например, ввода неверного пароля) сессия будет отключена сервером. # По умолчанию это 6, многовато. Трёх раз хватит. MaxAuthTries 3 # Параметр MaxSessions показывает, сколько сессий внутри одного SSH-подключения можно инициализировать. # Это не ограничение на “параллельные сессии с одного хоста”! MaxSessions 3 # Пара настроек ClientAliveCountMax и ClientAliveInterval будет нужна, чтобы определять, когда надо отключать неактивного клиента. # ClientAliveInterval – время неактивности в секундах, через которое клиента отключат, # а ClientAliveCountMax – количество попыток “разбудить” клиента. В нашем случае клиента отключат через полчаса неактивности. ClientAliveInterval 1800 ClientAliveCountMax 0 # разрешаем тунели через этот сервер PermitTunnel yes # разрешаем форвардинг параметров ссх агента AllowAgentForwarding yes # разрешаем проброс портов AllowTcpForwarding yes # запрещаем проброс Х11 ( не надо нам графику) X11Forwarding no # первое и последние значения – это стартовое количество подключений (речь только про подключения, которые не прошли аутентификацию), # начиная с которых механизм начнёт работать и максимальное количество подключений, возможных вообще (в нашем случае механизм включится, # когда к серверу будет 10 подключений, а 21е подключение будет технически невозможно), а средний параметр – это вероятность в процентах. # У нас она 50, т.е. мы будем отказывать в половине случаев, когда количество “висящих на фазе аутентификации” сессий будет от 10 до 20. MaxStartups 10:50:20 # Командой ShowPatchLevel no мы выключим публикацию детальной информации о версии SSH подключающемуся клиенту. # ShowPatchLevel no # # Логирование # SyslogFacility AUTH LogLevel VERBOSE # # Криптография # # пересогласовывать ключи после каждой пачки переданных данных RekeyLimit 256M # используем только эти ключи как стойкие HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_ecdsa_key HostKey /etc/ssh/ssh_host_ed25519_key # тюнингуем криптогафические алгоритмы # какие алгоритмы использовать для генерации сессионных ключей KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512 # какие алгоритмы шифрования использовать Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr # алгоритмы проверки целостности MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512 # алгоритм формирования отпечатка ключа FingerprintHash sha256 # выключаем сжатие ( мы же админить будем а не данные перекачивать) Compression no # # Аутентификация # # Параметр LoginGraceTime говорит о том, сколько времени пользователю можно потратить на процедуру входа. # По умолчанию – 2 минуты. Это очень много. Очень медленный пользователь должен быть, чтобы столько времени ему требовалось на вход. # Поэтому этот параметр выставляется в 15 – за 15 секунд войти можно. # Если надо дольше – можно увеличить, но разумно. # Важнее то, что сервер будет быстрее “сбрасывать” сессии, которые начались, но ещё не завершили аутентификацию, и экономить ресурсы. LoginGraceTime 15 # запретить логин под рутом PermitRootLogin no # запретить логиниться локальным пользователям- будем позволять только доменную авторизацию DenyUsers nixadmin root # Check user folder permissions before allowing access StrictModes yes # Set this to 'yes' to enable PAM authentication UsePAM yes # используемые методы аутентификации # RhostsRSAAuthentication no PubkeyAuthentication yes # HostbaseAuthentication no ChallengeResponseAuthentication no KerberosAuthentication no GSSAPIAuthentication no PasswordAuthentication yes PermitEmptyPasswords no # UsePrivilegeSeparation sandbox # где следует искать ключи пользователя AuthorizedKeysFile .ssh/authorized_keys # # Баннер при входе # PrintMotd no # no default banner path Banner none #PrintLastLog yes ######## # # Прочие параметры # #PermitTTY yes #UseLogin no #PermitUserEnvironment no #Compression delayed #ChrootDirectory none #VersionAddendum none # override default of no subsystems Subsystem sftp /usr/lib/openssh/sftp-server |
Fail2ban
Статей по базовой настройке множество, что взять за основу, я уже успешно описывал в своей предыдущей статье. Поэтому немного процитирую сам себя:
Одной из дополнительных мер защиты, которую я настоятельно рекомендую применять в любом случае, является установка и настройка службы fail2ban. Это очень хороший инструмент борьбы с различными брутфорсерами и сканировщиками в автоматическом режиме без вашего участия. Инструмент парсит логи различных служб ( поддерживется очень большое число популярных — начиная от ssh и заканчивая различными ftp сервисами) на предмет нездоровой активности, вычисляет источник и банит его по ip адресу, добавляя в специально созданное правило firewall. Для общего развития советую так же ознакомиться вот с такой статьей на хабре.
По умолчанию, fail2ban готов защищать ваш ssh сервер ( только в нашем случае нужно будет в настройках сменить порт с стандартного под псевдонимом ssh на тот который Вы указали в настройках sshd)
Небольшое дополнение для этой статьи — он должен быть настроен игнорировать подключения с 127.0.0.1 и с внутренней сети. В остальном банить должен быстро и надолго:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[INCLUDES] before = paths-debian.conf [DEFAULT] ignoreip = 127.0.0.1/8 10.0.0.0/16 ignorecommand = bantime = 600m findtime = 10m maxretry = 10 backend = auto usedns = warn logencoding = auto enabled = false mode = normal ... |
Как разбанивать пострадавших, так же описано в предыдущей статье.
Мониторинг
Если вы еще не выбрали себе систему мониторинга, я настоятельно рекомендую начать с Zabbix. В случае сложных и серьезных проектов его первенство спорно — возможно лучше взять Prometheus или TICK/TIG стэк. Но в целом я ставлю под сомнение ситуацию, когда Вы решили ставить бастион сервер а системы мониторинга у вас нет.
Что нам нужно будет мониторить:
- Целостность файлов с паролями, например /etc/shadow. Это zabbix делает по умолчанию.
- Целостность файлов с ssh ключами сервера и конфигурационными файлами sshd и sssd. Сделать это можно аналогично как это сделано для /etc/shadow.
- Статистику fail2ban и статистику ssh по подключениям. Я для себя реализовал мониторинг ssh через логи, для f2b это можно сделать схожим образом.
Вот так выглядят элементы данных:
Настройки Firewall
Использовать по умолчанию будем UFW, так как настройка должна быт примитивно проста. Входящий и исходящий трафик для этого сервера строго ограничивается.
Разрешенный входящий трафик:
-
порт 22/tpc — ssh
-
порт 10050/tpc — zabbix (тк в моем примере я ссылаюсь именно на него, однако никто не запрещает заменить его чем-то другим.) — опционально можно разрешить весь входящий трафик с сервера мониторинга ( в нашем примере это 10.0.1.13)
Разрешенный исходящий трафик:
-
порт 22/tpc — ssh
-
порт 53/udp — DNS
-
порты 67,68/udp — dhcp
-
порты 80,443/tpc — http/https — скачивание обновлений из интернет
-
любые порты на 10.0.1.20 — обращение к домен контроллеру
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ufw enable ufw allow 22/tcp ufw allow 10050/tcp #ufw allow from 10.0.1.13 #опционально ufw allow out to 10.0.1.20 ufw allow out 22/tcp ufw allow out 80/tcp ufw allow out 443/tcp ufw allow out 53/udp ufw allow out 67/udp ufw allow out 68/udp ufw default deny incoming ufw default deny outgoing ufw status verbose |
Подключение через бастион
Как же нам теперь работать с внутренними сервисами, спросите Вы. А очень просто — я нарисую лишь варианты для ssh, однако один из них ( с пробросом порта), подойдет и для RDP и для VNC и для HTTP.
Сессия внутри сессии
Самый простой вариант — называется заходим на бастион сервер а потом заходим на нужный сервер. То есть сессия внутри сесии.
1 2 3 4 5 6 7 8 |
kirill@xxx:~$ ssh -q kirill.kazarin@bastion.example.com kirill.kazarin@example.com@bastion:~$ kirill.kazarin@example.com@bastion:~$ ssh kirill.kazarin@wiki kirill.kazarin@wiki's password: Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-143-generic x86_64) kirill.kazarin@wiki:~$ |
Как видно из лога выше, вначале произошла авторизация на бастион сервере, потом я залогинился на вики сервер. Вся авторизация прошла с данными моей учетной записи AD.
Проброс портов
Основная схема:
1 2 3 4 5 |
ssh -f -N -L [локальный_адрес:]локальный_порт:удаленный_адрес:удаленный_порт [пользователь@]сервер -p порт_удаленного_сервера -f - Запросит ssh перейти в фоновый режим только перед выполнением команды. Это полезно если ssh собирается запросить пароль или парольную фразу, но пользователь хочет сделать это в фоновом режиме. -N - Не выполнять удаленную команду. Это полезно если вы хотите только перенаправить порты -L - Определяет заданный порт на локальной (клиентской) машине который будет перенаправлен к заданной машине и порт на удаленной машине. |
Пример:
Jump через сервер
Основная статья вот она: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts#Jump_Hosts_—_Passing_Through_a_Gateway_or_Two
Я только покажу пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
kirill@xxx:~$ head -n 5 ~/.ssh/config Host stage04 HostName stage04 ProxyJump kirill.kazarin@bastion.example.com User kirill.kazarin kirill@xxx:~$ ssh stage04 kirill.kazarin@bastion.example.com's password: kirill.kazarin@stage04's password: Last login: Fri May 3 00:59:31 2019 from bastion [kirill.kazarin@example.com@stage04 ~]$ [kirill.kazarin@example.com@stage04 ~]$ hostname stage04 [kirill.kazarin@example.com@stage04 ~]$ who kirill.kazarin@example.com pts/0 2019-05-03 01:19 (bastion) [kirill.kazarin@example.com@stage04 ~]$ exit logout Connection to stage04 closed. kirill@xxx:~$ |
А если при этом закинуть на бастион сервер свой ssh ключ и добавить в настройках ssh клиента директиву IdentityFile с полным путем до ключа — то вообще получается сказка!
Заключение
На этом все. Я постарался описать минимальный и при этом достаточно функциональный вариант ssh-бастион сервера для Вас, коллеги. надеюсь будет полезно!
П.С. Подписывайтесь на мой Telegram канал